From 3fdf8dc386125522ce1c025a8d52fc276b987da7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 21 Feb 2020 16:41:52 +0300 Subject: [PATCH 001/192] Recolour main background --- osu.Game/Overlays/ChangelogOverlay.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 6a8cb29d3e..d13ac5c2de 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -13,7 +13,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Online.API.Requests; @@ -42,14 +41,14 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuColour colour) + private void load(AudioManager audio) { Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = colour.PurpleDarkAlternative, + Colour = ColourProvider.Background4, }, new OsuScrollContainer { From e9f69d2c23706e3668bb9457f771e9c86955248a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 21 Feb 2020 16:55:12 +0300 Subject: [PATCH 002/192] Adjust date for ChangelogSingleBuild --- osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs index 73f9466179..4d2c8346a5 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs @@ -80,6 +80,8 @@ namespace osu.Game.Overlays.Changelog { } + private OsuSpriteText date; + protected override FillFlowContainer CreateHeader() { var fill = base.CreateHeader(); @@ -89,11 +91,10 @@ namespace osu.Game.Overlays.Changelog existing.Scale = new Vector2(1.25f); existing.Action = null; - existing.Add(new OsuSpriteText + existing.Add(date = new OsuSpriteText { - Text = Build.CreatedAt.Date.ToString("dd MMM yyyy"), + Text = Build.CreatedAt.Date.ToString("dd MMMM yyyy"), Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 14), - Colour = OsuColour.FromHex(@"FD5"), Anchor = Anchor.BottomCentre, Origin = Anchor.TopCentre, Margin = new MarginPadding { Top = 5 }, @@ -113,6 +114,12 @@ namespace osu.Game.Overlays.Changelog return fill; } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + date.Colour = colourProvider.Light1; + } } private class NavigationIconButton : IconButton From 70eb2ed09e6ee46f84708bd9ae228238cd1bc949 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 21 Feb 2020 17:11:29 +0300 Subject: [PATCH 003/192] Adjust ChangelogBuild appearance --- osu.Game/Overlays/Changelog/ChangelogBuild.cs | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index 5a3ce6291e..00bd36649a 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -51,28 +51,27 @@ namespace osu.Game.Overlays.Changelog } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, OverlayColourProvider colourProvider) { foreach (var categoryEntries in Build.ChangelogEntries.GroupBy(b => b.Category).OrderBy(c => c.Key)) { ChangelogEntries.Add(new OsuSpriteText { Text = categoryEntries.Key, - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 24), + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 18), Margin = new MarginPadding { Top = 35, Bottom = 15 }, }); - var fontLarge = OsuFont.GetFont(size: 18); - var fontMedium = OsuFont.GetFont(size: 14); - var fontSmall = OsuFont.GetFont(size: 12); + var fontLarge = OsuFont.GetFont(size: 16); + var fontMedium = OsuFont.GetFont(size: 12); - foreach (APIChangelogEntry entry in categoryEntries) + foreach (var entry in categoryEntries) { var entryColour = entry.Major ? colours.YellowLight : Color4.White; LinkFlowContainer title; - Container titleContainer = new Container + var titleContainer = new Container { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, @@ -83,7 +82,7 @@ namespace osu.Game.Overlays.Changelog { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreRight, - Size = new Vector2(fontSmall.Size), + Size = new Vector2(10), Icon = entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus, Colour = entryColour, Margin = new MarginPadding { Right = 5 }, @@ -123,9 +122,9 @@ namespace osu.Game.Overlays.Changelog }); } - title.AddText(" by ", t => + title.AddText(" by ", t => { - t.Font = fontMedium; + t.Font = fontMedium.With(italics: true); t.Colour = entryColour; }); @@ -137,7 +136,7 @@ namespace osu.Game.Overlays.Changelog Id = entry.GithubUser.UserId.Value }, t => { - t.Font = fontMedium; + t.Font = fontMedium.With(italics: true); t.Colour = entryColour; }); } @@ -145,7 +144,7 @@ namespace osu.Game.Overlays.Changelog { title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t => { - t.Font = fontMedium; + t.Font = fontMedium.With(italics: true); t.Colour = entryColour; }); } @@ -153,7 +152,7 @@ namespace osu.Game.Overlays.Changelog { title.AddText(entry.GithubUser.DisplayName, t => { - t.Font = fontSmall; + t.Font = fontMedium.With(italics: true); t.Colour = entryColour; }); } @@ -162,7 +161,7 @@ namespace osu.Game.Overlays.Changelog if (!string.IsNullOrEmpty(entry.MessageHtml)) { - TextFlowContainer message = new TextFlowContainer + var message = new TextFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, @@ -171,8 +170,8 @@ namespace osu.Game.Overlays.Changelog // todo: use markdown parsing once API returns markdown message.AddText(WebUtility.HtmlDecode(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty)), t => { - t.Font = fontSmall; - t.Colour = new Color4(235, 184, 254, 255); + t.Font = fontMedium; + t.Colour = colourProvider.Foreground1; }); ChangelogEntries.Add(message); From 491906d53416125deb9a753772b7512afe187802 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 21 Feb 2020 17:19:49 +0300 Subject: [PATCH 004/192] Adjust ChangelogListing appearance --- osu.Game/Overlays/Changelog/ChangelogListing.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogListing.cs b/osu.Game/Overlays/Changelog/ChangelogListing.cs index 41d8228475..19bc5975f1 100644 --- a/osu.Game/Overlays/Changelog/ChangelogListing.cs +++ b/osu.Game/Overlays/Changelog/ChangelogListing.cs @@ -24,13 +24,13 @@ namespace osu.Game.Overlays.Changelog } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { - DateTime currentDate = DateTime.MinValue; + var currentDate = DateTime.MinValue; if (entries == null) return; - foreach (APIChangelogBuild build in entries) + foreach (var build in entries) { if (build.CreatedAt.Date != currentDate) { @@ -49,10 +49,9 @@ namespace osu.Game.Overlays.Changelog { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Margin = new MarginPadding { Top = 15 }, - Text = build.CreatedAt.Date.ToString("dd MMM yyyy"), + Margin = new MarginPadding { Top = 20 }, + Text = build.CreatedAt.Date.ToString("dd MMMM yyyy"), Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 24), - Colour = OsuColour.FromHex(@"FD5"), }); currentDate = build.CreatedAt.Date; @@ -68,7 +67,7 @@ namespace osu.Game.Overlays.Changelog Child = new Box { RelativeSizeAxes = Axes.Both, - Colour = new Color4(32, 24, 35, 255), + Colour = colourProvider.Background6, } }); } From 48a9b465ef473e8ac5495132a1aac7f46776f94c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 21 Feb 2020 17:22:46 +0300 Subject: [PATCH 005/192] Adjust icons colour --- osu.Game/Overlays/Changelog/ChangelogBuild.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index 00bd36649a..94bb8b5abe 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -16,6 +16,7 @@ using osuTK.Graphics; using osu.Framework.Allocation; using System.Net; using osuTK; +using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Overlays.Changelog { @@ -84,7 +85,7 @@ namespace osu.Game.Overlays.Changelog Origin = Anchor.CentreRight, Size = new Vector2(10), Icon = entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus, - Colour = entryColour, + Colour = entryColour.Darken(0.7f), Margin = new MarginPadding { Right = 5 }, }, title = new LinkFlowContainer From c49074dde36776c3c627d2db3b2d30daa03feba3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 21 Feb 2020 17:29:51 +0300 Subject: [PATCH 006/192] Adjust comments placement --- osu.Game/Overlays/Changelog/ChangelogContent.cs | 1 - osu.Game/Overlays/Changelog/ChangelogListing.cs | 2 +- osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs | 13 +++++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogContent.cs b/osu.Game/Overlays/Changelog/ChangelogContent.cs index 2f6c1ae902..49dd9bb835 100644 --- a/osu.Game/Overlays/Changelog/ChangelogContent.cs +++ b/osu.Game/Overlays/Changelog/ChangelogContent.cs @@ -19,7 +19,6 @@ namespace osu.Game.Overlays.Changelog RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Direction = FillDirection.Vertical; - Padding = new MarginPadding { Bottom = 50 }; } } } diff --git a/osu.Game/Overlays/Changelog/ChangelogListing.cs b/osu.Game/Overlays/Changelog/ChangelogListing.cs index 19bc5975f1..55c79e7e79 100644 --- a/osu.Game/Overlays/Changelog/ChangelogListing.cs +++ b/osu.Game/Overlays/Changelog/ChangelogListing.cs @@ -40,7 +40,7 @@ namespace osu.Game.Overlays.Changelog { RelativeSizeAxes = Axes.X, Height = 2, - Colour = new Color4(17, 17, 17, 255), + Colour = colourProvider.Background6, Margin = new MarginPadding { Top = 30 }, }); } diff --git a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs index 4d2c8346a5..8b89d63aab 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -31,7 +32,7 @@ namespace osu.Game.Overlays.Changelog } [BackgroundDependencyLoader] - private void load(CancellationToken? cancellation, IAPIProvider api) + private void load(CancellationToken? cancellation, IAPIProvider api, OverlayColourProvider colourProvider) { bool complete = false; @@ -63,10 +64,14 @@ namespace osu.Game.Overlays.Changelog Children = new Drawable[] { new ChangelogBuildWithNavigation(build) { SelectBuild = SelectBuild }, - comments = new CommentsContainer + new Box { - Margin = new MarginPadding { Top = 10 } - } + RelativeSizeAxes = Axes.X, + Height = 2, + Colour = colourProvider.Background6, + Margin = new MarginPadding { Top = 30 }, + }, + comments = new CommentsContainer() }; comments.ShowComments(CommentableType.Build, build.Id); From 8593642a045df159c965a2baa6683c1eccd426b2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 21 Feb 2020 18:12:23 +0300 Subject: [PATCH 007/192] Adjust StreamBadgeArea --- .../Overlays/Changelog/UpdateStreamBadge.cs | 125 ++++++++---------- .../Changelog/UpdateStreamBadgeArea.cs | 13 +- 2 files changed, 64 insertions(+), 74 deletions(-) diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs index 52b77604d9..99929058e2 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs @@ -3,8 +3,6 @@ using Humanizer; using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,99 +14,90 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays.Changelog { public class UpdateStreamBadge : TabItem { - private const float badge_height = 66.5f; private const float badge_width = 100; private const float transition_duration = 100; - private readonly ExpandingBar expandingBar; - private SampleChannel sampleClick; - private SampleChannel sampleHover; - - private readonly FillFlowContainer text; - public readonly Bindable SelectedTab = new Bindable(); - private readonly Container fadeContainer; + private readonly APIUpdateStream stream; + + private Container fadeContainer; + private FillFlowContainer text; + private ExpandingBar expandingBar; public UpdateStreamBadge(APIUpdateStream stream) : base(stream) { - Size = new Vector2(stream.IsFeatured ? badge_width * 2 : badge_width, badge_height); - Padding = new MarginPadding(5); - - Child = fadeContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - text = new FillFlowContainer - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new[] - { - new OsuSpriteText - { - Text = stream.DisplayName, - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 12), - Margin = new MarginPadding { Top = 6 }, - }, - new OsuSpriteText - { - Text = stream.LatestBuild.DisplayVersion, - Font = OsuFont.GetFont(weight: FontWeight.Light, size: 16), - }, - new OsuSpriteText - { - Text = stream.LatestBuild.Users > 0 ? $"{stream.LatestBuild.Users:N0} {"user".Pluralize(stream.LatestBuild.Users == 1)} online" : null, - Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 10), - Colour = new Color4(203, 164, 218, 255), - }, - } - }, - expandingBar = new ExpandingBar - { - Anchor = Anchor.TopCentre, - Colour = stream.Colour, - ExpandedSize = 4, - CollapsedSize = 2, - IsCollapsed = true - }, - } - }; - - SelectedTab.BindValueChanged(_ => updateState(), true); + this.stream = stream; } [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load(OverlayColourProvider colourProvider) { - sampleClick = audio.Samples.Get(@"UI/generic-select-soft"); - sampleHover = audio.Samples.Get(@"UI/generic-hover-soft"); + Size = new Vector2(stream.IsFeatured ? badge_width * 2 : badge_width, 60); + Padding = new MarginPadding(5); + + AddRange(new Drawable[] + { + fadeContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + text = new FillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Margin = new MarginPadding { Top = 6 }, + Children = new[] + { + new OsuSpriteText + { + Text = stream.DisplayName, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black), + }, + new OsuSpriteText + { + Text = stream.LatestBuild.DisplayVersion, + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), + }, + new OsuSpriteText + { + Text = stream.LatestBuild.Users > 0 ? $"{stream.LatestBuild.Users:N0} {"user".Pluralize(stream.LatestBuild.Users == 1)} online" : null, + Font = OsuFont.GetFont(size: 10), + Colour = colourProvider.Foreground1 + }, + } + }, + expandingBar = new ExpandingBar + { + Anchor = Anchor.TopCentre, + Colour = stream.Colour, + ExpandedSize = 4, + CollapsedSize = 2, + IsCollapsed = true + }, + } + }, + new HoverClickSounds() + }); + + SelectedTab.BindValueChanged(_ => updateState(), true); } protected override void OnActivated() => updateState(); protected override void OnDeactivated() => updateState(); - protected override bool OnClick(ClickEvent e) - { - sampleClick?.Play(); - return base.OnClick(e); - } - protected override bool OnHover(HoverEvent e) { - sampleHover?.Play(); updateState(); - return base.OnHover(e); } diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs index ca57ba24e2..ccb1314885 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs @@ -9,33 +9,34 @@ using System.Linq; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osuTK.Graphics; +using osu.Framework.Allocation; namespace osu.Game.Overlays.Changelog { public class UpdateStreamBadgeArea : TabControl { - public UpdateStreamBadgeArea() + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; AddInternal(new Box { - Colour = Color4.Black, - Alpha = 0.12f, RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, }); } public void Populate(List streams) { - foreach (APIUpdateStream updateStream in streams) + foreach (var updateStream in streams) AddItem(updateStream); } protected override bool OnHover(HoverEvent e) { - foreach (UpdateStreamBadge streamBadge in TabContainer.Children.OfType()) + foreach (var streamBadge in TabContainer.Children.OfType()) streamBadge.EnableDim(); return base.OnHover(e); @@ -43,7 +44,7 @@ namespace osu.Game.Overlays.Changelog protected override void OnHoverLost(HoverLostEvent e) { - foreach (UpdateStreamBadge streamBadge in TabContainer.Children.OfType()) + foreach (var streamBadge in TabContainer.Children.OfType()) streamBadge.DisableDim(); base.OnHoverLost(e); From 44ddc585daf94f81b7c48f916bd8e9e4ffc73cbe Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 21 Feb 2020 18:40:42 +0300 Subject: [PATCH 008/192] Remove unused usings --- osu.Game/Overlays/Changelog/ChangelogListing.cs | 1 - osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogListing.cs b/osu.Game/Overlays/Changelog/ChangelogListing.cs index 55c79e7e79..9b74a8da6d 100644 --- a/osu.Game/Overlays/Changelog/ChangelogListing.cs +++ b/osu.Game/Overlays/Changelog/ChangelogListing.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; -using osuTK.Graphics; namespace osu.Game.Overlays.Changelog { diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs index ccb1314885..639c0d9780 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osuTK.Graphics; using osu.Framework.Allocation; namespace osu.Game.Overlays.Changelog From f248c0a98eed52098eb364ed66c3ba3a37cd2b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Feb 2020 00:56:17 +0100 Subject: [PATCH 009/192] Fix regressed direct panel test scene Due to unnoticed past changes TestSceneDirectPanel has regressed in that clicking the preview track button would crash due to an unregistered IPreviewTrackOwner dependency. Make the test scene itself implement that empty interface and cache itself as IPreviewTrackOwner so that preview tracks lower down can resolve the dependency. As the test is purely visual and has no assertions, and the important logic in that area (preview track playing logic) is already well-covered enough elsewhere, no further changes were made. --- osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs index 731cb62518..ccd428fe68 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Overlays.Direct; using osu.Game.Rulesets; @@ -14,7 +15,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Online { - public class TestSceneDirectPanel : OsuTestScene + public class TestSceneDirectPanel : OsuTestScene, IPreviewTrackOwner { public override IReadOnlyList RequiredTypes => new[] { @@ -23,6 +24,13 @@ namespace osu.Game.Tests.Visual.Online typeof(IconPill) }; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(this); + return dependencies; + } + private BeatmapSetInfo getUndownloadableBeatmapSet() => new BeatmapSetInfo { OnlineBeatmapSetID = 123, From 894b50f95572899305df5868236acdf8723d70ac Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 22 Feb 2020 03:24:50 +0300 Subject: [PATCH 010/192] Basic implementation of BeatmapSetCommentsContainer --- osu.Game/Overlays/BeatmapSet/Info.cs | 11 ---- osu.Game/Overlays/BeatmapSetOverlay.cs | 76 ++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 0f2d6a9e35..bac658b76e 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -3,17 +3,14 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet { @@ -43,14 +40,6 @@ namespace osu.Game.Overlays.BeatmapSet RelativeSizeAxes = Axes.X; Height = base_height; - Masking = true; - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.25f), - Type = EdgeEffectType.Shadow, - Radius = 3, - Offset = new Vector2(0f, 1f), - }; Children = new Drawable[] { diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index d29997f7e5..5b5580f286 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -4,8 +4,10 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Beatmaps; @@ -13,8 +15,10 @@ using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests; using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; +using osu.Game.Overlays.Comments; using osu.Game.Rulesets; using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays { @@ -40,6 +44,7 @@ namespace osu.Game.Overlays { OsuScrollContainer scroll; Info info; + BeatmapSetCommentsContainer comments; Children = new Drawable[] { @@ -51,28 +56,38 @@ namespace osu.Game.Overlays { RelativeSizeAxes = Axes.Both, ScrollbarVisible = false, - Child = new ReverseChildIDFillFlowContainer + Child = new ReverseChildIDFillFlowContainer
{ RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 20), - Children = new Drawable[] + Children = new[] { - new ReverseChildIDFillFlowContainer + new Section { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Children = new Drawable[] + Child = new ReverseChildIDFillFlowContainer { - Header = new Header(), - info = new Info() + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + Header = new Header(), + info = new Info() + } + }, + }, + new Section + { + Child = new ScoresContainer + { + Beatmap = { BindTarget = Header.Picker.Beatmap } } }, - new ScoresContainer + new Section { - Beatmap = { BindTarget = Header.Picker.Beatmap } + Child = comments = new BeatmapSetCommentsContainer() } }, }, @@ -81,6 +96,7 @@ namespace osu.Game.Overlays Header.BeatmapSet.BindTo(beatmapSet); info.BeatmapSet.BindTo(beatmapSet); + comments.BeatmapSet.BindTo(beatmapSet); Header.Picker.Beatmap.ValueChanged += b => { @@ -143,5 +159,43 @@ namespace osu.Game.Overlays beatmapSet.Value = set; Show(); } + + private class Section : Container + { + public Section() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Masking = true; + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.25f), + Type = EdgeEffectType.Shadow, + Radius = 3, + Offset = new Vector2(0f, 1f), + }; + } + } + + private class BeatmapSetCommentsContainer : CommentsContainer + { + public readonly Bindable BeatmapSet = new Bindable(); + + public BeatmapSetCommentsContainer() + { + BeatmapSet.BindValueChanged(beatmapSet => + { + if (beatmapSet.NewValue?.OnlineBeatmapSetID.HasValue != true) + { + Hide(); + } + else + { + Show(); + ShowComments(CommentableType.Beatmapset, beatmapSet.NewValue.OnlineBeatmapSetID.Value); + } + }, true); + } + } } } From 63006e8672276d44ccd85ae12a163087e6d84cb5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 22 Feb 2020 03:40:59 +0300 Subject: [PATCH 011/192] Refactor to avoid visual inconsistency for beatmaps with no leaderboard --- .../BeatmapSet/BeatmapSetLayoutSection.cs | 29 +++++++++++ .../BeatmapSet/Scores/ScoresContainer.cs | 17 +++---- osu.Game/Overlays/BeatmapSetOverlay.cs | 48 +++++-------------- 3 files changed, 49 insertions(+), 45 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapSet/BeatmapSetLayoutSection.cs diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetLayoutSection.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetLayoutSection.cs new file mode 100644 index 0000000000..e6d433f7bc --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetLayoutSection.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class BeatmapSetLayoutSection : Container + { + public BeatmapSetLayoutSection() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Masking = true; + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.25f), + Type = EdgeEffectType.Shadow, + Radius = 3, + Offset = new Vector2(0f, 1f), + }; + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index e831c8ce42..7607eac1f8 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -19,7 +19,7 @@ using osu.Game.Users; namespace osu.Game.Overlays.BeatmapSet.Scores { - public class ScoresContainer : CompositeDrawable + public class ScoresContainer : BeatmapSetLayoutSection { private const int spacing = 15; @@ -34,7 +34,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly LoadingLayer loading; private readonly LeaderboardModSelector modSelector; private readonly NoScoresPlaceholder noScoresPlaceholder; - private readonly FillFlowContainer content; private readonly NotSupporterPlaceholder notSupporterPlaceholder; [Resolved] @@ -76,15 +75,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public ScoresContainer() { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - InternalChildren = new Drawable[] + AddRange(new Drawable[] { background = new Box { RelativeSizeAxes = Axes.Both, }, - content = new FillFlowContainer + new FillFlowContainer { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -164,8 +161,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } } - }, - }; + } + }); } [BackgroundDependencyLoader] @@ -223,7 +220,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (Beatmap.Value?.OnlineBeatmapID.HasValue != true || Beatmap.Value.Status <= BeatmapSetOnlineStatus.Pending) { Scores = null; - content.Hide(); + Hide(); return; } @@ -237,7 +234,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores notSupporterPlaceholder.Hide(); - content.Show(); + Show(); loading.Show(); getScoresRequest = new GetScoresRequest(Beatmap.Value, Beatmap.Value.Ruleset, scope.Value, modSelector.SelectedMods); diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 5b5580f286..efa9e27f5c 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -4,10 +4,8 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Beatmaps; @@ -18,7 +16,6 @@ using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.Comments; using osu.Game.Rulesets; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays { @@ -44,7 +41,7 @@ namespace osu.Game.Overlays { OsuScrollContainer scroll; Info info; - BeatmapSetCommentsContainer comments; + CommentsSection comments; Children = new Drawable[] { @@ -56,7 +53,7 @@ namespace osu.Game.Overlays { RelativeSizeAxes = Axes.Both, ScrollbarVisible = false, - Child = new ReverseChildIDFillFlowContainer
+ Child = new ReverseChildIDFillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -64,7 +61,7 @@ namespace osu.Game.Overlays Spacing = new Vector2(0, 20), Children = new[] { - new Section + new BeatmapSetLayoutSection { Child = new ReverseChildIDFillFlowContainer { @@ -78,17 +75,11 @@ namespace osu.Game.Overlays } }, }, - new Section + new ScoresContainer { - Child = new ScoresContainer - { - Beatmap = { BindTarget = Header.Picker.Beatmap } - } + Beatmap = { BindTarget = Header.Picker.Beatmap } }, - new Section - { - Child = comments = new BeatmapSetCommentsContainer() - } + comments = new CommentsSection() }, }, }, @@ -160,29 +151,16 @@ namespace osu.Game.Overlays Show(); } - private class Section : Container - { - public Section() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Masking = true; - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.25f), - Type = EdgeEffectType.Shadow, - Radius = 3, - Offset = new Vector2(0f, 1f), - }; - } - } - - private class BeatmapSetCommentsContainer : CommentsContainer + private class CommentsSection : BeatmapSetLayoutSection { public readonly Bindable BeatmapSet = new Bindable(); - public BeatmapSetCommentsContainer() + public CommentsSection() { + CommentsContainer comments; + + Add(comments = new CommentsContainer()); + BeatmapSet.BindValueChanged(beatmapSet => { if (beatmapSet.NewValue?.OnlineBeatmapSetID.HasValue != true) @@ -192,7 +170,7 @@ namespace osu.Game.Overlays else { Show(); - ShowComments(CommentableType.Beatmapset, beatmapSet.NewValue.OnlineBeatmapSetID.Value); + comments.ShowComments(CommentableType.Beatmapset, beatmapSet.NewValue.OnlineBeatmapSetID.Value); } }, true); } From c9d600b69c0320967e65aff73d8fd53d7c76cafe Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 22 Feb 2020 09:39:17 +0100 Subject: [PATCH 012/192] Show RankingsOverlay when clicking on charts button --- osu.Game/Screens/Charts/ChartInfo.cs | 9 --------- osu.Game/Screens/Charts/ChartListing.cs | 16 ---------------- osu.Game/Screens/Menu/MainMenu.cs | 5 ++--- 3 files changed, 2 insertions(+), 28 deletions(-) delete mode 100644 osu.Game/Screens/Charts/ChartInfo.cs delete mode 100644 osu.Game/Screens/Charts/ChartListing.cs diff --git a/osu.Game/Screens/Charts/ChartInfo.cs b/osu.Game/Screens/Charts/ChartInfo.cs deleted file mode 100644 index d9a9ad5eeb..0000000000 --- a/osu.Game/Screens/Charts/ChartInfo.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Screens.Charts -{ - public class ChartInfo : ScreenWhiteBox - { - } -} diff --git a/osu.Game/Screens/Charts/ChartListing.cs b/osu.Game/Screens/Charts/ChartListing.cs deleted file mode 100644 index 18bba6433f..0000000000 --- a/osu.Game/Screens/Charts/ChartListing.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; - -namespace osu.Game.Screens.Charts -{ - public class ChartListing : ScreenWhiteBox - { - protected override IEnumerable PossibleChildren => new[] - { - typeof(ChartInfo) - }; - } -} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index c70fbb67a4..af5db4ce8c 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -18,7 +18,6 @@ using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; using osu.Game.Screens.Backgrounds; -using osu.Game.Screens.Charts; using osu.Game.Screens.Edit; using osu.Game.Screens.Multi; using osu.Game.Screens.Select; @@ -73,7 +72,7 @@ namespace osu.Game.Screens.Menu private SongTicker songTicker; [BackgroundDependencyLoader(true)] - private void load(DirectOverlay direct, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics) + private void load(DirectOverlay direct, SettingsOverlay settings, RankingsOverlay rankings, OsuConfigManager config, SessionStatics statics) { holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); loginDisplayed = statics.GetBindable(Static.LoginOverlayDisplayed); @@ -101,7 +100,6 @@ namespace osu.Game.Screens.Menu { buttons = new ButtonSystem { - OnChart = delegate { this.Push(new ChartListing()); }, OnEdit = delegate { this.Push(new Editor()); }, OnSolo = onSolo, OnMulti = delegate { this.Push(new Multiplayer()); }, @@ -136,6 +134,7 @@ namespace osu.Game.Screens.Menu buttons.OnSettings = () => settings?.ToggleVisibility(); buttons.OnDirect = () => direct?.ToggleVisibility(); + buttons.OnChart = () => rankings?.ToggleVisibility(); LoadComponentAsync(background = new BackgroundScreenDefault()); preloadSongSelect(); From a512226036b43be8bd7a8d04f8630848d2aca2d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Feb 2020 13:47:42 +0100 Subject: [PATCH 013/192] Use attribute instead of CreateChildDependencies --- osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs index ccd428fe68..cb08cded37 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs @@ -15,6 +15,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Online { + [Cached(typeof(IPreviewTrackOwner))] public class TestSceneDirectPanel : OsuTestScene, IPreviewTrackOwner { public override IReadOnlyList RequiredTypes => new[] @@ -24,13 +25,6 @@ namespace osu.Game.Tests.Visual.Online typeof(IconPill) }; - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(this); - return dependencies; - } - private BeatmapSetInfo getUndownloadableBeatmapSet() => new BeatmapSetInfo { OnlineBeatmapSetID = 123, From ab3cc9ada49a6ff7ab63e6e8a13acef7d34fe0d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Feb 2020 16:25:00 +0100 Subject: [PATCH 014/192] Add log file location to issue templates --- .github/ISSUE_TEMPLATE/01-bug-issues.md | 5 ++++- .github/ISSUE_TEMPLATE/02-crash-issues.md | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/01-bug-issues.md b/.github/ISSUE_TEMPLATE/01-bug-issues.md index c8c41e5a78..0aff276d03 100644 --- a/.github/ISSUE_TEMPLATE/01-bug-issues.md +++ b/.github/ISSUE_TEMPLATE/01-bug-issues.md @@ -8,4 +8,7 @@ about: Issues regarding encountered bugs. **osu!lazer version:** -**Logs:** +**Logs:** +*please attach logs here, which are located at:* +- `%AppData%/osu/logs` *(on Windows),* +- `~/.local/share/osu/logs` *(on Linux & macOS).* diff --git a/.github/ISSUE_TEMPLATE/02-crash-issues.md b/.github/ISSUE_TEMPLATE/02-crash-issues.md index 8ad27e9e31..9c3ae33161 100644 --- a/.github/ISSUE_TEMPLATE/02-crash-issues.md +++ b/.github/ISSUE_TEMPLATE/02-crash-issues.md @@ -8,6 +8,9 @@ about: Issues regarding crashes or permanent freezes. **osu!lazer version:** -**Logs:** +**Logs:** +*please attach logs here, which are located at:* +- `%AppData%/osu/logs` *(on Windows),* +- `~/.local/share/osu/logs` *(on Linux & macOS).* **Computer Specifications:** From f696724c1d0354dfa4724cdea7d96af43f0fb134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Feb 2020 16:26:10 +0100 Subject: [PATCH 015/192] Mention issue templates in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f61f118ea8..ae57b1d954 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the This project is under heavy development, but is in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update. -We are accepting bug reports (please report with as much detail as possible). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project: +We are accepting bug reports (please report with as much detail as possible and follow the existing issue templates). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project: - Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer). - You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management). From b8aef4ad4b6ea7ca8ef10ddd24108fd3b395fd1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Feb 2020 16:50:56 +0100 Subject: [PATCH 016/192] Remove mobile issue template --- .github/ISSUE_TEMPLATE/00-mobile-issues.md | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/00-mobile-issues.md diff --git a/.github/ISSUE_TEMPLATE/00-mobile-issues.md b/.github/ISSUE_TEMPLATE/00-mobile-issues.md deleted file mode 100644 index f171e80b8b..0000000000 --- a/.github/ISSUE_TEMPLATE/00-mobile-issues.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: Mobile Report -about: ⚠ Due to current development priorities we are not accepting mobile reports at this time (unless you're willing to fix them yourself!) ---- - -⚠ **PLEASE READ** ⚠: Due to prioritising finishing the client for desktop first we are not accepting reports related to mobile platforms for the time being, unless you're willing to fix them. -If you'd like to report a problem or suggest a feature and then work on it, feel free to open an issue and highlight that you'd like to address it yourself in the issue body; mobile pull requests are also welcome. -Otherwise, please check back in the future when the focus of development shifts towards mobile! From 5aa5a1bbdd0aab61055ff5d4d9224b201e32e860 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Feb 2020 03:36:15 +0900 Subject: [PATCH 017/192] Reduce transform time of judgement lines (visually looks almost the same) --- osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 208bdd17ad..d54d4abd14 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -228,7 +228,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private class JudgementLine : CompositeDrawable { - private const int judgement_fade_duration = 10000; + private const int judgement_fade_duration = 5000; public JudgementLine() { @@ -255,7 +255,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Width = 0; this.ResizeWidthTo(1, 200, Easing.OutElasticHalf); - this.FadeTo(0.8f, 150).Then().FadeOut(judgement_fade_duration, Easing.OutQuint).Expire(); + this.FadeTo(0.8f, 150).Then().FadeOut(judgement_fade_duration).Expire(); } } } From 3daa49f1bd2eb6be3881995c337cb3c8a57fc22e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Feb 2020 03:39:12 +0900 Subject: [PATCH 018/192] Clean up old judgement lines if too many are already present --- osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index d54d4abd14..48c74886a1 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -207,11 +207,16 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private double floatingAverage; private Container colourBars; + private const int max_concurrent_judgements = 50; + public override void OnNewJudgement(JudgementResult judgement) { if (!judgement.IsHit) return; + if (judgementsContainer.Count >= max_concurrent_judgements) + judgementsContainer.FirstOrDefault(j => j.LifetimeEnd > Clock.CurrentTime + 100)?.FadeOut(100).Expire(); + judgementsContainer.Add(new JudgementLine { Y = getRelativeJudgementPosition(judgement.TimeOffset), From e8ebb315175c994c47e1c53f70ee2c93efd532a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Feb 2020 03:53:51 +0900 Subject: [PATCH 019/192] Expire old judgement lines if too many exist --- .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 17 +++++++++++++++++ .../Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 12 +++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 8904b54b0d..5f2200b049 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Judgements; using osu.Framework.Utils; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Threading; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Mania.Scoring; @@ -43,6 +44,22 @@ namespace osu.Game.Tests.Visual.Gameplay AddRepeatStep("New max negative", () => newJudgement(-hitWindows.WindowFor(HitResult.Meh)), 20); AddRepeatStep("New max positive", () => newJudgement(hitWindows.WindowFor(HitResult.Meh)), 20); AddStep("New fixed judgement (50ms)", () => newJudgement(50)); + + AddStep("Judgement barrage", () => + { + int runCount = 0; + + ScheduledDelegate del = null; + + del = Scheduler.AddDelayed(() => + { + newJudgement(runCount++ / 10f); + + if (runCount == 500) + // ReSharper disable once AccessToModifiedClosure + del?.Cancel(); + }, 10, true); + }); } [Test] diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 48c74886a1..c297ddc981 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -215,7 +215,17 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters return; if (judgementsContainer.Count >= max_concurrent_judgements) - judgementsContainer.FirstOrDefault(j => j.LifetimeEnd > Clock.CurrentTime + 100)?.FadeOut(100).Expire(); + { + const double quick_fade_time = 100; + + var old = judgementsContainer.FirstOrDefault(j => j.LifetimeEnd > Clock.CurrentTime + quick_fade_time); + + if (old != null) + { + old.ClearTransforms(); + old.FadeOut(quick_fade_time).Expire(); + } + } judgementsContainer.Add(new JudgementLine { From cb9a7ee0bb9366caf92e4994825d9554d73a8862 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Feb 2020 04:05:37 +0900 Subject: [PATCH 020/192] Give FollowPointConnections a valid lifetime --- .../Connections/FollowPointConnection.cs | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index a5e89210f6..7165697022 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private const int spacing = 32; private const double preempt = 800; + public override bool RemoveWhenNotAlive => false; + /// /// The start time of . /// @@ -79,27 +81,31 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections drawableObject.HitObject.DefaultsApplied += scheduleRefresh; } - private void scheduleRefresh() => Scheduler.AddOnce(refresh); + private void scheduleRefresh() + { + Scheduler.AddOnce(refresh); + } private void refresh() { ClearInternal(); - if (End == null) - return; - OsuHitObject osuStart = Start.HitObject; - OsuHitObject osuEnd = End.HitObject; + double startTime = osuStart.GetEndTime(); - if (osuEnd.NewCombo) - return; + LifetimeStart = startTime; - if (osuStart is Spinner || osuEnd is Spinner) + OsuHitObject osuEnd = End?.HitObject; + + if (osuEnd == null || osuEnd.NewCombo || osuStart is Spinner || osuEnd is Spinner) + { + // ensure we always set a lifetime for full LifetimeManagementContainer benefits + LifetimeEnd = LifetimeStart; return; + } Vector2 startPosition = osuStart.EndPosition; Vector2 endPosition = osuEnd.Position; - double startTime = osuStart.GetEndTime(); double endTime = osuEnd.StartTime; Vector2 distanceVector = endPosition - startPosition; @@ -107,6 +113,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); double duration = endTime - startTime; + double finalTransformEndTime = startTime; + for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing) { float fraction = (float)d / distance; @@ -131,10 +139,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out); fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out); fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn); - } - fp.Expire(true); + finalTransformEndTime = fadeOutTime + osuEnd.TimeFadeIn; + } } + + LifetimeStart = startTime; + LifetimeEnd = finalTransformEndTime; } } } From 090d9d93506a8a8e658b1e0bd990f6f9107eebc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Feb 2020 04:37:04 +0900 Subject: [PATCH 021/192] Make FollowPointRenderer a LifetimeManagementContainer --- .../Objects/Drawables/Connections/FollowPointRenderer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index be192080f9..4d73e711bb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// Visualises connections between s. /// - public class FollowPointRenderer : CompositeDrawable + public class FollowPointRenderer : LifetimeManagementContainer { /// /// All the s contained by this . @@ -45,8 +45,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// The index of in . private void addConnection(FollowPointConnection connection) { - AddInternal(connection); - // Groups are sorted by their start time when added such that the index can be used to post-process other surrounding connections int index = connections.AddInPlace(connection, Comparer.Create((g1, g2) => g1.StartTime.Value.CompareTo(g2.StartTime.Value))); @@ -74,6 +72,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections FollowPointConnection previousConnection = connections[index - 1]; previousConnection.End = connection.Start; } + + AddInternal(connection); } /// From 8dbcdebd287b5d1a9b73c4d6f499a481c0d47eeb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Feb 2020 04:58:52 +0900 Subject: [PATCH 022/192] Use LoadingLayer at player loading screen --- osu.Game/Graphics/UserInterface/LoadingLayer.cs | 2 +- osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/LoadingLayer.cs b/osu.Game/Graphics/UserInterface/LoadingLayer.cs index 25a62acaba..35b33c3d03 100644 --- a/osu.Game/Graphics/UserInterface/LoadingLayer.cs +++ b/osu.Game/Graphics/UserInterface/LoadingLayer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Graphics.UserInterface protected override void Update() { base.Update(); - MainContents.Size = new Vector2(Math.Min(100, Math.Min(DrawWidth, DrawHeight) * 0.25f)); + MainContents.Size = new Vector2(Math.Clamp(Math.Min(DrawWidth, DrawHeight) * 0.25f, 30, 100)); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index 77ee52f23e..a84a85ea47 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -15,7 +15,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play.HUD; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Play { @@ -63,15 +62,9 @@ namespace osu.Game.Screens.Play set { if (value) - { loading.Show(); - backgroundSprite.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint); - } else - { loading.Hide(); - backgroundSprite.FadeColour(Color4.White, 400, Easing.OutQuint); - } } } @@ -138,7 +131,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, FillMode = FillMode.Fill, }, - loading = new LoadingSpinner { Scale = new Vector2(1.3f) } + loading = new LoadingLayer(backgroundSprite) } }, new OsuSpriteText From aaa888a7c14b25aef453e4de785b835b73d26013 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Feb 2020 05:00:38 +0900 Subject: [PATCH 023/192] Adjust spin duration to make rotation more variable --- osu.Game/Graphics/UserInterface/LoadingSpinner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/LoadingSpinner.cs b/osu.Game/Graphics/UserInterface/LoadingSpinner.cs index b5a235f9d8..36d429b8c1 100644 --- a/osu.Game/Graphics/UserInterface/LoadingSpinner.cs +++ b/osu.Game/Graphics/UserInterface/LoadingSpinner.cs @@ -93,7 +93,7 @@ namespace osu.Game.Graphics.UserInterface private void rotate() { - spinner.Spin(spin_duration * 4, RotationDirection.Clockwise); + spinner.Spin(spin_duration * 3.5f, RotationDirection.Clockwise); MainContents.RotateTo(0).Then() .RotateTo(90, spin_duration, Easing.InOutQuart).Then() From 560cf21b12af41a100aa276f1a589263b7baed75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Feb 2020 05:28:59 +0900 Subject: [PATCH 024/192] Add lenience to comparison Noticed that they could still stack up on maps with hitcircles at the same point in time (centipede). --- osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index c297ddc981..9edbddc0b1 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -214,11 +214,12 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters if (!judgement.IsHit) return; - if (judgementsContainer.Count >= max_concurrent_judgements) + if (judgementsContainer.Count > max_concurrent_judgements) { const double quick_fade_time = 100; - var old = judgementsContainer.FirstOrDefault(j => j.LifetimeEnd > Clock.CurrentTime + quick_fade_time); + // check with a bit of lenience to avoid precision error in comparison. + var old = judgementsContainer.FirstOrDefault(j => j.LifetimeEnd > Clock.CurrentTime + quick_fade_time * 1.1); if (old != null) { From 74c7e29108e9bc610ccd635c133211115d7a50c7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 23 Feb 2020 01:22:37 +0300 Subject: [PATCH 025/192] Apply suggestions --- osu.Game/Overlays/Changelog/ChangelogBuild.cs | 5 +++-- osu.Game/Overlays/Changelog/UpdateStreamBadge.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index 94bb8b5abe..8aee76cb08 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -85,7 +85,7 @@ namespace osu.Game.Overlays.Changelog Origin = Anchor.CentreRight, Size = new Vector2(10), Icon = entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus, - Colour = entryColour.Darken(0.7f), + Colour = entryColour.Opacity(0.5f), Margin = new MarginPadding { Right = 5 }, }, title = new LinkFlowContainer @@ -123,10 +123,11 @@ namespace osu.Game.Overlays.Changelog }); } - title.AddText(" by ", t => + title.AddText("by ", t => { t.Font = fontMedium.With(italics: true); t.Colour = entryColour; + t.Padding = new MarginPadding { Left = 10 }; }); if (entry.GithubUser.UserId != null) diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs index 99929058e2..10aca31441 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs @@ -69,7 +69,7 @@ namespace osu.Game.Overlays.Changelog }, new OsuSpriteText { - Text = stream.LatestBuild.Users > 0 ? $"{stream.LatestBuild.Users:N0} {"user".Pluralize(stream.LatestBuild.Users == 1)} online" : null, + Text = stream.LatestBuild.Users > 0 ? $"{"user".ToQuantity(stream.LatestBuild.Users, "N0")} online" : null, Font = OsuFont.GetFont(size: 10), Colour = colourProvider.Foreground1 }, From ffc7eaa3f212d50a44bf1692b6111bd007f8eb72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Feb 2020 13:01:30 +0900 Subject: [PATCH 026/192] Fix hitobjects with unknown lifetimes by enforcing non-null judgement We've seen multiple cases where DrawableHitObject are stuck in the lifetime management container due to not implementing a judgement (meaning they are never "hit" or "missed"). To avoid this going forward CreateJudgement() must be implemented and return a non-null judgement. This fixes BananaShower and JuiceStreams in osu!catch. This also makes HitObject abstract and cleans up convert HitObject implementations. --- .../Objects/BananaShower.cs | 3 +++ osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 3 +++ .../TestSceneDrawableJudgement.cs | 2 +- .../Judgements/HoldNoteJudgement.cs | 14 -------------- osu.Game.Rulesets.Mania/Objects/BarLine.cs | 3 +++ osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 3 +-- .../TestSceneDrawableJudgement.cs | 2 +- .../Judgements/OsuSliderTailJudgement.cs | 14 -------------- .../Objects/SliderTailCircle.cs | 3 +-- .../TestSceneSwellJudgements.cs | 3 +-- .../TestSceneTaikoPlayfield.cs | 8 ++++---- osu.Game.Rulesets.Taiko/Objects/SwellTick.cs | 3 +-- .../TestSceneDrainingHealthProcessor.cs | 1 + .../Gameplay/TestSceneHitObjectAccentColour.cs | 2 +- .../Gameplay/TestSceneHitObjectContainer.cs | 12 ++++++------ .../TestSceneDrawableScrollingRuleset.cs | 4 ++-- .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 2 +- .../Gameplay/TestSceneScrollingHitObjects.cs | 4 ++-- .../Visual/Gameplay/TestSceneSongProgress.cs | 4 ++-- .../SongSelect/TestSceneBeatmapInfoWedge.cs | 2 +- .../Rulesets/Judgements/IgnoreJudgement.cs | 6 +++--- osu.Game/Rulesets/Objects/ConvertHitObject.cs | 18 ++++++++++++++++++ osu.Game/Rulesets/Objects/HitObject.cs | 11 ++++++----- .../Objects/Legacy/Catch/ConvertHit.cs | 2 +- .../Objects/Legacy/Catch/ConvertSpinner.cs | 2 +- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 2 +- .../Objects/Legacy/Mania/ConvertHit.cs | 5 +---- .../Objects/Legacy/Mania/ConvertHold.cs | 5 +---- .../Objects/Legacy/Mania/ConvertSlider.cs | 3 --- .../Objects/Legacy/Mania/ConvertSpinner.cs | 5 +---- .../Rulesets/Objects/Legacy/Osu/ConvertHit.cs | 5 +---- .../Objects/Legacy/Osu/ConvertSlider.cs | 3 --- .../Objects/Legacy/Osu/ConvertSpinner.cs | 5 +---- .../Objects/Legacy/Taiko/ConvertHit.cs | 5 +---- .../Objects/Legacy/Taiko/ConvertSlider.cs | 3 --- .../Objects/Legacy/Taiko/ConvertSpinner.cs | 5 +---- 36 files changed, 72 insertions(+), 105 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs delete mode 100644 osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs rename osu.Game.Rulesets.Taiko/Judgements/TaikoSwellTickJudgement.cs => osu.Game/Rulesets/Judgements/IgnoreJudgement.cs (63%) create mode 100644 osu.Game/Rulesets/Objects/ConvertHitObject.cs diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 0c754412e5..c3488aec11 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.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 osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Catch.Objects @@ -11,6 +12,8 @@ namespace osu.Game.Rulesets.Catch.Objects public override bool LastInCombo => true; + public override Judgement CreateJudgement() => new IgnoreJudgement(); + protected override void CreateNestedHitObjects() { base.CreateNestedHitObjects(); diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 11e2466275..642ff0246e 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -7,6 +7,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -19,6 +20,8 @@ namespace osu.Game.Rulesets.Catch.Objects /// private const float base_scoring_distance = 100; + public override Judgement CreateJudgement() => new IgnoreJudgement(); + public int RepeatCount { get; set; } public double Velocity; diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs index 692d079c16..7b71f2feda 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Tests foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1)) { AddStep("Show " + result.GetDescription(), () => SetContents(() => - new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null) + new DrawableManiaJudgement(new JudgementResult(new ConvertHitObject(), new Judgement()) { Type = result }, null) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs deleted file mode 100644 index e8b48768a1..0000000000 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Mania.Judgements -{ - public class HoldNoteJudgement : ManiaJudgement - { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) => 0; - } -} diff --git a/osu.Game.Rulesets.Mania/Objects/BarLine.cs b/osu.Game.Rulesets.Mania/Objects/BarLine.cs index 0981b028b2..09a746042b 100644 --- a/osu.Game.Rulesets.Mania/Objects/BarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/BarLine.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 osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mania.Objects @@ -8,5 +9,7 @@ namespace osu.Game.Rulesets.Mania.Objects public class BarLine : ManiaHitObject, IBarLine { public bool Major { get; set; } + + public override Judgement CreateJudgement() => new IgnoreJudgement(); } } diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 86d3d2b2b0..049bf55f90 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -4,7 +4,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; @@ -103,7 +102,7 @@ namespace osu.Game.Rulesets.Mania.Objects } } - public override Judgement CreateJudgement() => new HoldNoteJudgement(); + public override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 02d4406809..8f72e2f60d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Tests foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1)) { AddStep("Show " + result.GetDescription(), () => SetContents(() => - new DrawableOsuJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null) + new DrawableOsuJudgement(new JudgementResult(new ConvertHitObject(), new Judgement()) { Type = result }, null) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs deleted file mode 100644 index 5104d9494b..0000000000 --- a/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Osu.Judgements -{ - public class OsuSliderTailJudgement : OsuJudgement - { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) => 0; - } -} diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index c17d2275b8..127c36fcc0 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -4,7 +4,6 @@ using osu.Framework.Bindables; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects @@ -23,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects pathVersion.BindValueChanged(_ => Position = slider.EndPosition); } - public override Judgement CreateJudgement() => new OsuSliderTailJudgement(); + public override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs index f27e329e8e..ccacc50de1 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Screens.Play; using osu.Game.Tests.Visual; @@ -28,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Tests public void TestZeroTickTimeOffsets() { AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted); - AddAssert("all tick offsets are 0", () => Player.Results.Where(r => r.Judgement is TaikoSwellTickJudgement).All(r => r.TimeOffset == 0)); + AddAssert("all tick offsets are 0", () => Player.Results.Where(r => r.HitObject is SwellTick).All(r => r.TimeOffset == 0)); } protected override bool Autoplay => true; diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs index c01eef5252..c8c4d004d6 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new ConvertHitObject(), new TaikoJudgement()) { Type = hitResult }); } private void addStrongHitJudgement(bool kiai) @@ -163,13 +163,13 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new ConvertHitObject(), new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new ConvertHitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great }); } private void addMissJudgement() { - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new ConvertHitObject(), new TaikoJudgement()) { Type = HitResult.Miss }); } private void addBarLine(bool major, double delay = scroll_time) diff --git a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs index 91f4d3dbe7..bdc0478195 100644 --- a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs @@ -3,13 +3,12 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects { public class SwellTick : TaikoHitObject { - public override Judgement CreateJudgement() => new TaikoSwellTickJudgement(); + public override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index 244e37f017..885abb61b5 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs @@ -154,6 +154,7 @@ namespace osu.Game.Tests.Gameplay private class JudgeableHitObject : HitObject { public override Judgement CreateJudgement() => new Judgement(); + protected override HitWindows CreateHitWindows() => new HitWindows(); } } } diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs index 17dc27543d..e99a10524e 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Gameplay } } - private class TestHitObjectWithCombo : HitObject, IHasComboInformation + private class TestHitObjectWithCombo : ConvertHitObject, IHasComboInformation { public bool NewCombo { get; } = false; public int ComboOffset { get; } = 0; diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs index f2bfccb6de..6452857bad 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs @@ -27,9 +27,9 @@ namespace osu.Game.Tests.Gameplay { DrawableHitObject hitObject = null; - AddStep("setup", () => container.Add(new TestDrawableHitObject(new HitObject { StartTime = 500 }))); + AddStep("setup", () => container.Add(new TestDrawableHitObject(new ConvertHitObject { StartTime = 500 }))); - AddStep("add late hitobject", () => container.Add(hitObject = new TestDrawableHitObject(new HitObject { StartTime = 1000 }))); + AddStep("add late hitobject", () => container.Add(hitObject = new TestDrawableHitObject(new ConvertHitObject { StartTime = 1000 }))); AddAssert("hitobject index is 0", () => container.IndexOf(hitObject) == 0); } @@ -39,9 +39,9 @@ namespace osu.Game.Tests.Gameplay { DrawableHitObject hitObject = null; - AddStep("setup", () => container.Add(new TestDrawableHitObject(new HitObject { StartTime = 500 }))); + AddStep("setup", () => container.Add(new TestDrawableHitObject(new ConvertHitObject { StartTime = 500 }))); - AddStep("add early hitobject", () => container.Add(hitObject = new TestDrawableHitObject(new HitObject()))); + AddStep("add early hitobject", () => container.Add(hitObject = new TestDrawableHitObject(new ConvertHitObject()))); AddAssert("hitobject index is 0", () => container.IndexOf(hitObject) == 1); } @@ -54,8 +54,8 @@ namespace osu.Game.Tests.Gameplay AddStep("setup", () => { - container.Add(firstObject = new TestDrawableHitObject(new HitObject())); - container.Add(secondObject = new TestDrawableHitObject(new HitObject { StartTime = 1000 })); + container.Add(firstObject = new TestDrawableHitObject(new ConvertHitObject())); + container.Add(secondObject = new TestDrawableHitObject(new ConvertHitObject { StartTime = 1000 })); }); AddStep("move first object after second", () => firstObject.HitObject.StartTime = 2000); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 46f62b9541..d350c3d58d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -163,7 +163,7 @@ namespace osu.Game.Tests.Visual.Gameplay var beatmap = new Beatmap { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } }; for (int i = 0; i < 10; i++) - beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range }); + beatmap.HitObjects.Add(new ConvertHitObject { StartTime = i * time_range }); return beatmap; } @@ -289,7 +289,7 @@ namespace osu.Game.Tests.Visual.Gameplay #region HitObject - private class TestHitObject : HitObject, IHasEndTime + private class TestHitObject : ConvertHitObject, IHasEndTime { public double EndTime { get; set; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 8904b54b0d..494611d414 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void newJudgement(double offset = 0) { - var judgement = new JudgementResult(new HitObject(), new Judgement()) + var judgement = new JudgementResult(new ConvertHitObject(), new Judgement()) { TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset, Type = HitResult.Perfect, diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs index d03716db2e..c2a71eadae 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs @@ -224,7 +224,7 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestDrawableControlPoint : DrawableHitObject { public TestDrawableControlPoint(ScrollingDirection direction, double time) - : base(new HitObject { StartTime = time }) + : base(new ConvertHitObject { StartTime = time }) { Origin = Anchor.Centre; @@ -255,7 +255,7 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestDrawableHitObject : DrawableHitObject { public TestDrawableHitObject(double time) - : base(new HitObject { StartTime = time }) + : base(new ConvertHitObject { StartTime = time }) { Origin = Anchor.Custom; OriginPosition = new Vector2(75 / 4.0f); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index b9b13d7bd8..6a60ae110b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var objects = new List(); for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000) - objects.Add(new HitObject { StartTime = i }); + objects.Add(new ConvertHitObject { StartTime = i }); replaceObjects(objects); } @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var objects = new List(); for (double i = 0; i < 5000; i++) - objects.Add(new HitObject { StartTime = i }); + objects.Add(new ConvertHitObject { StartTime = i }); replaceObjects(objects); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index f49d7a14a6..5cac897200 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -194,7 +194,7 @@ namespace osu.Game.Tests.Visual.SongSelect public new BufferedWedgeInfo Info => base.Info; } - private class TestHitObject : HitObject, IHasPosition + private class TestHitObject : ConvertHitObject, IHasPosition { public float X { get; } = 0; public float Y { get; } = 0; diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellTickJudgement.cs b/osu.Game/Rulesets/Judgements/IgnoreJudgement.cs similarity index 63% rename from osu.Game.Rulesets.Taiko/Judgements/TaikoSwellTickJudgement.cs rename to osu.Game/Rulesets/Judgements/IgnoreJudgement.cs index b28b6a0d17..1871249c94 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellTickJudgement.cs +++ b/osu.Game/Rulesets/Judgements/IgnoreJudgement.cs @@ -1,11 +1,11 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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.Taiko.Judgements +namespace osu.Game.Rulesets.Judgements { - public class TaikoSwellTickJudgement : TaikoJudgement + public class IgnoreJudgement : Judgement { public override bool AffectsCombo => false; diff --git a/osu.Game/Rulesets/Objects/ConvertHitObject.cs b/osu.Game/Rulesets/Objects/ConvertHitObject.cs new file mode 100644 index 0000000000..81a88615a3 --- /dev/null +++ b/osu.Game/Rulesets/Objects/ConvertHitObject.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Objects +{ + /// + /// A hit object only used for conversion, not actual gameplay. + /// + public class ConvertHitObject : HitObject + { + public override Judgement CreateJudgement() => new IgnoreJudgement(); + + protected override HitWindows CreateHitWindows() => HitWindows.Empty; + } +} diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index c09844e0c6..bef48f0ced 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Objects /// HitObjects may contain more properties for which you should be checking through the IHas* types. /// /// - public class HitObject + public abstract class HitObject { /// /// A small adjustment to the start time of control points to account for rounding/precision errors. @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Objects [JsonIgnore] public IReadOnlyList NestedHitObjects => nestedHitObjects; - public HitObject() + protected HitObject() { StartTimeBindable.ValueChanged += time => { @@ -144,9 +144,10 @@ namespace osu.Game.Rulesets.Objects /// /// Creates the that represents the scoring information for this . - /// May be null. + /// Used to decide on drawable object lifetimes. /// - public virtual Judgement CreateJudgement() => null; + [NotNull] + public abstract Judgement CreateJudgement(); /// /// Creates the for this . @@ -156,7 +157,7 @@ namespace osu.Game.Rulesets.Objects /// /// [NotNull] - protected virtual HitWindows CreateHitWindows() => new HitWindows(); + protected abstract HitWindows CreateHitWindows(); } public static class HitObjectExtensions diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs index febfd3696c..19722fb796 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// /// Legacy osu!catch Hit-type, used for parsing Beatmaps. /// - internal sealed class ConvertHit : HitObject, IHasCombo, IHasXPosition + internal sealed class ConvertHit : ConvertHitObject, IHasCombo, IHasXPosition { public float X { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs index 0089d1eb88..9de311c9d7 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// /// Legacy osu!catch Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasXPosition, IHasCombo + internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition, IHasCombo { public double EndTime { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 53cdf457c4..924182b265 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -9,7 +9,7 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Objects.Legacy { - internal abstract class ConvertSlider : HitObject, IHasCurve, IHasLegacyLastTickOffset + internal abstract class ConvertSlider : ConvertHitObject, IHasCurve, IHasLegacyLastTickOffset { /// /// Scoring distance with a speed-adjusted beat length of 1 second. diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs index 883ef55df0..0b69817c13 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs @@ -2,17 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Objects.Legacy.Mania { /// /// Legacy osu!mania Hit-type, used for parsing Beatmaps. /// - internal sealed class ConvertHit : HitObject, IHasXPosition + internal sealed class ConvertHit : ConvertHitObject, IHasXPosition { public float X { get; set; } - - protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs index 69e6f8379d..1d92d638dd 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs @@ -2,18 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Objects.Legacy.Mania { - internal sealed class ConvertHold : HitObject, IHasXPosition, IHasEndTime + internal sealed class ConvertHold : ConvertHitObject, IHasXPosition, IHasEndTime { public float X { get; set; } public double EndTime { get; set; } public double Duration => EndTime - StartTime; - - protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs index 4486c5d906..84cde5fa95 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Objects.Legacy.Mania { @@ -12,7 +11,5 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition { public float X { get; set; } - - protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs index c6d1f1922c..7dc13e27cd 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs @@ -2,21 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Objects.Legacy.Mania { /// /// Legacy osu!mania Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasXPosition + internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition { public double EndTime { get; set; } public double Duration => EndTime - StartTime; public float X { get; set; } - - protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs index e40b5b4505..069366bad3 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Rulesets.Objects.Legacy.Osu @@ -10,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Hit-type, used for parsing Beatmaps. /// - internal sealed class ConvertHit : HitObject, IHasPosition, IHasCombo + internal sealed class ConvertHit : ConvertHitObject, IHasPosition, IHasCombo { public Vector2 Position { get; set; } @@ -21,7 +20,5 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public bool NewCombo { get; set; } public int ComboOffset { get; set; } - - protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs index a163329d47..e947690668 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Rulesets.Objects.Legacy.Osu @@ -21,7 +20,5 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public bool NewCombo { get; set; } public int ComboOffset { get; set; } - - protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index 5d96a61633..8b21aab411 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Rulesets.Objects.Legacy.Osu @@ -10,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasPosition, IHasCombo + internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasPosition, IHasCombo { public double EndTime { get; set; } @@ -22,8 +21,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public float Y => Position.Y; - protected override HitWindows CreateHitWindows() => HitWindows.Empty; - public bool NewCombo { get; set; } public int ComboOffset { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs index efb9810927..cb5178ce48 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs @@ -1,15 +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.Objects.Legacy.Taiko { /// /// Legacy osu!taiko Hit-type, used for parsing Beatmaps. /// - internal sealed class ConvertHit : HitObject + internal sealed class ConvertHit : ConvertHitObject { - protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs index b365fd34ae..821554f7ee 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Rulesets.Scoring; - namespace osu.Game.Rulesets.Objects.Legacy.Taiko { /// @@ -10,6 +8,5 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko /// internal sealed class ConvertSlider : Legacy.ConvertSlider { - protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs index 840ba51ac2..8e28487f2f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs @@ -2,19 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Objects.Legacy.Taiko { /// /// Legacy osu!taiko Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : HitObject, IHasEndTime + internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime { public double EndTime { get; set; } public double Duration => EndTime - StartTime; - - protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } From 0e29d3c4a2173eecc9681b36d66985518dcd2cda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Feb 2020 13:50:05 +0900 Subject: [PATCH 027/192] Correctly expire bar lines in osu!taiko and osu!mania --- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs | 4 +--- osu.Game.Rulesets.Taiko/Objects/BarLine.cs | 3 +++ osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs | 2 ++ osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs index 56bc797c7f..08b5b75f9c 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs @@ -71,8 +71,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { } - protected override void UpdateStateTransforms(ArmedState state) - { - } + protected override void UpdateStateTransforms(ArmedState state) => this.FadeOut(150); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs index 2afbbc737c..6306195704 100644 --- a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/BarLine.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 osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Taiko.Objects @@ -8,5 +9,7 @@ namespace osu.Game.Rulesets.Taiko.Objects public class BarLine : TaikoHitObject, IBarLine { public bool Major { get; set; } + + public override Judgement CreateJudgement() => new IgnoreJudgement(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs index 1a5a797f28..e9caabbcc8 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs @@ -54,5 +54,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Alpha = 0.75f }); } + + protected override void UpdateStateTransforms(ArmedState state) => this.FadeOut(150); } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 67fe18e8dd..d8614c9d3d 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -265,7 +265,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } } - if (state.Value != ArmedState.Idle && LifetimeEnd == double.MaxValue) + if (state.Value != ArmedState.Idle && LifetimeEnd == double.MaxValue || HitObject.HitWindows == null) Expire(); } else From 401bf1c92811158cf752aac264953324d3b2f761 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Feb 2020 14:30:08 +0900 Subject: [PATCH 028/192] Remove unnecessary checks on NotNull attributes --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 9 +++------ osu.Game/Rulesets/Scoring/JudgementProcessor.cs | 2 -- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 67fe18e8dd..369bda3fc2 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -99,12 +99,9 @@ namespace osu.Game.Rulesets.Objects.Drawables { var judgement = HitObject.CreateJudgement(); - if (judgement != null) - { - Result = CreateResult(judgement); - if (Result == null) - throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); - } + Result = CreateResult(judgement); + if (Result == null) + throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); loadSamples(); } diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index 3016007f98..334b95f808 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs @@ -125,8 +125,6 @@ namespace osu.Game.Rulesets.Scoring simulate(nested); var judgement = obj.CreateJudgement(); - if (judgement == null) - return; var result = CreateResult(obj, judgement); if (result == null) From 575946d92367f6d98fd198d1e3ddfa236718eca7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Feb 2020 20:47:14 +0900 Subject: [PATCH 029/192] Expose save and export options in editor to non-dekstop platforms --- osu.Game/Screens/Edit/Editor.cs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3bec4b8f6f..d84151f5af 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -20,8 +20,6 @@ using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Design; using osuTK.Input; -using System.Collections.Generic; -using osu.Framework; using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; using osu.Game.Graphics.Cursor; @@ -93,17 +91,6 @@ namespace osu.Game.Screens.Edit EditorMenuBar menuBar; - var fileMenuItems = new List(); - - if (RuntimeInfo.IsDesktop) - { - fileMenuItems.Add(new EditorMenuItem("Save", MenuItemType.Standard, saveBeatmap)); - fileMenuItems.Add(new EditorMenuItem("Export package", MenuItemType.Standard, exportBeatmap)); - fileMenuItems.Add(new EditorMenuItemSpacer()); - } - - fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit)); - AddInternal(new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -134,7 +121,13 @@ namespace osu.Game.Screens.Edit { new MenuItem("File") { - Items = fileMenuItems + Items = new[] + { + new EditorMenuItem("Save", MenuItemType.Standard, saveBeatmap), + new EditorMenuItem("Export package", MenuItemType.Standard, exportBeatmap), + new EditorMenuItemSpacer(), + new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit), + } } } } From 71c34a36edd568f4140f71d21788df9fe4197905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Feb 2020 14:46:42 +0100 Subject: [PATCH 030/192] Use pattern matching instead of .(Has)?Value --- osu.Game/Overlays/BeatmapSetOverlay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index efa9e27f5c..0d16c4842d 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -163,14 +163,14 @@ namespace osu.Game.Overlays BeatmapSet.BindValueChanged(beatmapSet => { - if (beatmapSet.NewValue?.OnlineBeatmapSetID.HasValue != true) + if (beatmapSet.NewValue?.OnlineBeatmapSetID is int onlineBeatmapSetID) { - Hide(); + Show(); + comments.ShowComments(CommentableType.Beatmapset, onlineBeatmapSetID); } else { - Show(); - comments.ShowComments(CommentableType.Beatmapset, beatmapSet.NewValue.OnlineBeatmapSetID.Value); + Hide(); } }, true); } From 0cfe1ac823e2e9414637405e97024f5cbd57f2ec Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 24 Feb 2020 05:47:21 +0300 Subject: [PATCH 031/192] Implement UserSortTabControl component --- .../TestSceneUserSortTabControl.cs | 41 +++++++++++++++++++ osu.Game/Overlays/Comments/CommentsHeader.cs | 3 ++ .../Home/Friends/UserSortTabControl.cs | 19 +++++++++ osu.Game/Overlays/OverlaySortTabControl.cs | 4 +- 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneUserSortTabControl.cs create mode 100644 osu.Game/Overlays/Home/Friends/UserSortTabControl.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUserSortTabControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUserSortTabControl.cs new file mode 100644 index 0000000000..de7a78d355 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUserSortTabControl.cs @@ -0,0 +1,41 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osu.Game.Overlays.Home.Friends; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneUserSortTabControl : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(UserSortTabControl), + typeof(OverlaySortTabControl<>), + }; + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + public TestSceneUserSortTabControl() + { + UserSortTabControl control; + OsuSpriteText current; + + Add(control = new UserSortTabControl + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + + Add(current = new OsuSpriteText()); + + control.Current.BindValueChanged(criteria => current.Text = $"Criteria: {criteria.NewValue}", true); + } + } +} diff --git a/osu.Game/Overlays/Comments/CommentsHeader.cs b/osu.Game/Overlays/Comments/CommentsHeader.cs index 1aa40201f1..ff5602e289 100644 --- a/osu.Game/Overlays/Comments/CommentsHeader.cs +++ b/osu.Game/Overlays/Comments/CommentsHeader.cs @@ -11,6 +11,8 @@ using osu.Framework.Graphics.Sprites; using osuTK; using osu.Framework.Input.Events; using osu.Game.Graphics.Sprites; +using System.ComponentModel; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Overlays.Comments { @@ -109,6 +111,7 @@ namespace osu.Game.Overlays.Comments public enum CommentsSortCriteria { + [Description(@"Recent")] New, Old, Top diff --git a/osu.Game/Overlays/Home/Friends/UserSortTabControl.cs b/osu.Game/Overlays/Home/Friends/UserSortTabControl.cs new file mode 100644 index 0000000000..2479fa4638 --- /dev/null +++ b/osu.Game/Overlays/Home/Friends/UserSortTabControl.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; + +namespace osu.Game.Overlays.Home.Friends +{ + public class UserSortTabControl : OverlaySortTabControl + { + } + + public enum UserSortCriteria + { + [Description(@"Recently Active")] + LastVisit, + Rank, + Username + } +} diff --git a/osu.Game/Overlays/OverlaySortTabControl.cs b/osu.Game/Overlays/OverlaySortTabControl.cs index 5a713ab08e..395f3aec4c 100644 --- a/osu.Game/Overlays/OverlaySortTabControl.cs +++ b/osu.Game/Overlays/OverlaySortTabControl.cs @@ -15,6 +15,8 @@ using osu.Game.Graphics.Sprites; using osuTK.Graphics; using osu.Game.Overlays.Comments; using JetBrains.Annotations; +using System; +using osu.Framework.Extensions; namespace osu.Game.Overlays { @@ -132,7 +134,7 @@ namespace osu.Game.Overlays Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.GetFont(size: 12), - Text = value.ToString() + Text = (value as Enum)?.GetDescription() ?? value.ToString() } } }); From b0b52146eafd10c442c6809d1f4d9f6cb0e1ee56 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 24 Feb 2020 05:53:33 +0300 Subject: [PATCH 032/192] Fix crash when clicking on ShowMore button --- osu.Game/Overlays/Comments/CommentsPage.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/CommentsPage.cs b/osu.Game/Overlays/Comments/CommentsPage.cs index 4f82ee5a19..591400f741 100644 --- a/osu.Game/Overlays/Comments/CommentsPage.cs +++ b/osu.Game/Overlays/Comments/CommentsPage.cs @@ -96,12 +96,17 @@ namespace osu.Game.Overlays.Comments { var orphaned = new List(); + // Exclude possible duplicated comments. foreach (var topLevel in bundle.Comments) + { + if (commentDictionary.ContainsKey(topLevel.Id)) + continue; + addNewComment(topLevel); + } foreach (var child in bundle.IncludedComments) { - // Included comments can contain the parent comment, which already exists in the hierarchy. if (commentDictionary.ContainsKey(child.Id)) continue; From e6cfafffe9966aee9fc9cb68b4b6d8a373c143fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Feb 2020 12:24:15 +0900 Subject: [PATCH 033/192] Fix incorrect LifetimeStart and add todo regarding Expire usage --- .../Objects/Drawables/Connections/FollowPointConnection.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 7165697022..921b23cb13 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -113,6 +113,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); double duration = endTime - startTime; + double? firstTransformStartTime = null; double finalTransformEndTime = startTime; for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing) @@ -133,6 +134,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections Scale = new Vector2(1.5f * osuEnd.Scale), }); + if (firstTransformStartTime == null) + firstTransformStartTime = fadeInTime; + using (fp.BeginAbsoluteSequence(fadeInTime)) { fp.FadeIn(osuEnd.TimeFadeIn); @@ -144,7 +148,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections } } - LifetimeStart = startTime; + // todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed. + LifetimeStart = firstTransformStartTime ?? startTime; LifetimeEnd = finalTransformEndTime; } } From 7bc9a9b3d8b9e00b7fa09e7710c47fe42ea01d46 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 24 Feb 2020 07:28:33 +0300 Subject: [PATCH 034/192] Implement OverlayPanelDisplayStyleControl and UserListToolbar components --- .../UserInterface/TestSceneUserListToolbar.cs | 57 +++++++++++ .../TestSceneUserSortTabControl.cs | 41 -------- .../Overlays/Home/Friends/UserListToolbar.cs | 45 +++++++++ .../OverlayPanelDisplayStyleControl.cs | 97 +++++++++++++++++++ 4 files changed, 199 insertions(+), 41 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneUserListToolbar.cs delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneUserSortTabControl.cs create mode 100644 osu.Game/Overlays/Home/Friends/UserListToolbar.cs create mode 100644 osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUserListToolbar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUserListToolbar.cs new file mode 100644 index 0000000000..02b8839922 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUserListToolbar.cs @@ -0,0 +1,57 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osu.Game.Overlays.Home.Friends; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneUserListToolbar : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(UserSortTabControl), + typeof(OverlaySortTabControl<>), + typeof(OverlayPanelDisplayStyleControl), + typeof(UserListToolbar), + }; + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + public TestSceneUserListToolbar() + { + UserListToolbar toolbar; + OsuSpriteText sort; + OsuSpriteText displayStyle; + + Add(toolbar = new UserListToolbar + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + + Add(new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + sort = new OsuSpriteText(), + displayStyle = new OsuSpriteText() + } + }); + + toolbar.SortCriteria.BindValueChanged(criteria => sort.Text = $"Criteria: {criteria.NewValue}", true); + toolbar.DisplayStyle.BindValueChanged(style => displayStyle.Text = $"Style: {style.NewValue}", true); + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUserSortTabControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUserSortTabControl.cs deleted file mode 100644 index de7a78d355..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUserSortTabControl.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Overlays; -using osu.Game.Overlays.Home.Friends; - -namespace osu.Game.Tests.Visual.UserInterface -{ - public class TestSceneUserSortTabControl : OsuTestScene - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(UserSortTabControl), - typeof(OverlaySortTabControl<>), - }; - - [Cached] - private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - - public TestSceneUserSortTabControl() - { - UserSortTabControl control; - OsuSpriteText current; - - Add(control = new UserSortTabControl - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }); - - Add(current = new OsuSpriteText()); - - control.Current.BindValueChanged(criteria => current.Text = $"Criteria: {criteria.NewValue}", true); - } - } -} diff --git a/osu.Game/Overlays/Home/Friends/UserListToolbar.cs b/osu.Game/Overlays/Home/Friends/UserListToolbar.cs new file mode 100644 index 0000000000..f7c5e9f4fd --- /dev/null +++ b/osu.Game/Overlays/Home/Friends/UserListToolbar.cs @@ -0,0 +1,45 @@ +// 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 osuTK; +using osu.Framework.Bindables; + +namespace osu.Game.Overlays.Home.Friends +{ + public class UserListToolbar : CompositeDrawable + { + public Bindable SortCriteria => sortControl.Current; + + public Bindable DisplayStyle => styleControl.Current; + + private readonly UserSortTabControl sortControl; + private readonly OverlayPanelDisplayStyleControl styleControl; + + public UserListToolbar() + { + AutoSizeAxes = Axes.Both; + + AddInternal(new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + sortControl = new UserSortTabControl + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + styleControl = new OverlayPanelDisplayStyleControl + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + } + }); + } + } +} diff --git a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs new file mode 100644 index 0000000000..952b277cac --- /dev/null +++ b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs @@ -0,0 +1,97 @@ +// 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; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osuTK; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; +using osu.Framework.Allocation; +using osuTK.Graphics; +using osu.Framework.Graphics.Cursor; + +namespace osu.Game.Overlays +{ + public class OverlayPanelDisplayStyleControl : OsuTabControl + { + protected override Dropdown CreateDropdown() => null; + + protected override TabItem CreateTabItem(OverlayPanelDisplayStyle value) => new PanelDisplayTabItem(value); + + protected override bool AddEnumEntriesAutomatically => false; + + public OverlayPanelDisplayStyleControl() + { + AutoSizeAxes = Axes.Both; + + AddTabItem(new PanelDisplayTabItem(OverlayPanelDisplayStyle.Card) + { + Icon = FontAwesome.Solid.Square + }); + AddTabItem(new PanelDisplayTabItem(OverlayPanelDisplayStyle.List) + { + Icon = FontAwesome.Solid.Bars + }); + } + + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal + }; + + private class PanelDisplayTabItem : TabItem, IHasTooltip + { + public IconUsage Icon + { + set => icon.Icon = value; + } + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + public string TooltipText => $@"{Value} view"; + + private readonly SpriteIcon icon; + + public PanelDisplayTabItem(OverlayPanelDisplayStyle value) + : base(value) + { + Size = new Vector2(11); + Add(icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit + }); + } + + protected override void OnActivated() => updateState(); + + protected override void OnDeactivated() => updateState(); + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateState(); + base.OnHoverLost(e); + } + + private void updateState() => icon.Colour = Active.Value || IsHovered ? colourProvider.Light1 : Color4.White; + } + } + + public enum OverlayPanelDisplayStyle + { + Card, + List + } +} From 6c28fd21c7bb7b61676c21c9ec11c8a84ef9e629 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 24 Feb 2020 20:52:15 +0900 Subject: [PATCH 035/192] osu-side changes --- .../Mods/ManiaModFlashlight.cs | 14 +++----------- .../Objects/Drawables/Pieces/BodyPiece.cs | 14 ++++---------- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 14 ++++---------- .../Mods/TaikoModFlashlight.cs | 16 ++++------------ osu.Game/Graphics/UserInterface/LineGraph.cs | 14 ++++---------- .../UI/Scrolling/ScrollingHitObjectContainer.cs | 13 ++++--------- .../Edit/Compose/Components/DistanceSnapGrid.cs | 14 ++++---------- osu.Game/Screens/Play/SquareGraph.cs | 16 +++++++--------- 8 files changed, 34 insertions(+), 81 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 6893e1e73b..86a00271e9 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -3,8 +3,8 @@ using System; using osu.Framework.Bindables; -using osu.Framework.Caching; using osu.Framework.Graphics; +using osu.Framework.Layout; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; using osuTK; @@ -22,21 +22,13 @@ namespace osu.Game.Rulesets.Mania.Mods private class ManiaFlashlight : Flashlight { - private readonly Cached flashlightProperties = new Cached(); + private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); public ManiaFlashlight() { FlashlightSize = new Vector2(0, default_flashlight_size); - } - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.DrawSize) > 0) - { - flashlightProperties.Invalidate(); - } - - return base.Invalidate(invalidation, source, shallPropagate); + AddLayout(flashlightProperties); } protected override void Update() diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs index 31a4857805..43f9ae2783 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs @@ -2,13 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Caching; using osuTK.Graphics; 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.Framework.Layout; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces @@ -65,6 +65,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces } } }; + + AddLayout(subtractionCache); } protected override void LoadComplete() @@ -100,15 +102,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces } } - private readonly Cached subtractionCache = new Cached(); - - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.DrawSize) > 0) - subtractionCache.Invalidate(); - - return base.Invalidate(invalidation, source, shallPropagate); - } + private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize); protected override void Update() { diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 4e86662ec6..9a23a2be30 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -5,7 +5,6 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; using osu.Framework.Allocation; -using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Batches; using osu.Framework.Graphics.OpenGL.Vertices; @@ -14,6 +13,7 @@ using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.Layout; using osu.Framework.Timing; using osuTK; using osuTK.Graphics; @@ -43,6 +43,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor // -1 signals that the part is unusable, and should not be drawn parts[i].InvalidationID = -1; } + + AddLayout(partSizeCache); } [BackgroundDependencyLoader] @@ -72,20 +74,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } } - private readonly Cached partSizeCache = new Cached(); + private readonly LayoutValue partSizeCache = new LayoutValue(Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence); private Vector2 partSize => partSizeCache.IsValid ? partSizeCache.Value : (partSizeCache.Value = new Vector2(Texture.DisplayWidth, Texture.DisplayHeight) * DrawInfo.Matrix.ExtractScale().Xy); - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & (Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence)) > 0) - partSizeCache.Invalidate(); - - return base.Invalidate(invalidation, source, shallPropagate); - } - /// /// The amount of time to fade the cursor trail pieces. /// diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index b7db3307ad..1253b7c8ae 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Caching; using osu.Framework.Graphics; +using osu.Framework.Layout; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; @@ -30,13 +30,15 @@ namespace osu.Game.Rulesets.Taiko.Mods private class TaikoFlashlight : Flashlight { - private readonly Cached flashlightProperties = new Cached(); + private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); private readonly TaikoPlayfield taikoPlayfield; public TaikoFlashlight(TaikoPlayfield taikoPlayfield) { this.taikoPlayfield = taikoPlayfield; FlashlightSize = new Vector2(0, getSizeFor(0)); + + AddLayout(flashlightProperties); } private float getSizeFor(int combo) @@ -56,16 +58,6 @@ namespace osu.Game.Rulesets.Taiko.Mods protected override string FragmentShader => "CircularFlashlight"; - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.DrawSize) > 0) - { - flashlightProperties.Invalidate(); - } - - return base.Invalidate(invalidation, source, shallPropagate); - } - protected override void Update() { base.Update(); diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index 6d65b77cbf..42b523fc5c 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -4,11 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Caching; using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; +using osu.Framework.Layout; using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface @@ -83,17 +83,11 @@ namespace osu.Game.Graphics.UserInterface PathRadius = 1 } }); + + AddLayout(pathCached); } - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.DrawSize) > 0) - pathCached.Invalidate(); - - return base.Invalidate(invalidation, source, shallPropagate); - } - - private readonly Cached pathCached = new Cached(); + private readonly LayoutValue pathCached = new LayoutValue(Invalidation.DrawSize); protected override void Update() { diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 83a7f7289f..108f98d5fc 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; +using osu.Framework.Layout; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; @@ -19,11 +20,13 @@ namespace osu.Game.Rulesets.UI.Scrolling [Resolved] private IScrollingInfo scrollingInfo { get; set; } - private readonly Cached initialStateCache = new Cached(); + private readonly LayoutValue initialStateCache = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo); public ScrollingHitObjectContainer() { RelativeSizeAxes = Axes.Both; + + AddLayout(initialStateCache); } [BackgroundDependencyLoader] @@ -55,14 +58,6 @@ namespace osu.Game.Rulesets.UI.Scrolling return result; } - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & (Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo)) > 0) - initialStateCache.Invalidate(); - - return base.Invalidate(invalidation, source, shallPropagate); - } - private float scrollLength; protected override void Update() diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 479de64eab..3a42938fc1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -3,10 +3,10 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Layout; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osuTK; @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private BindableBeatDivisor beatDivisor { get; set; } - private readonly Cached gridCache = new Cached(); + private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); private readonly double? endTime; /// @@ -67,6 +67,8 @@ namespace osu.Game.Screens.Edit.Compose.Components StartTime = startTime; RelativeSizeAxes = Axes.Both; + + AddLayout(gridCache); } protected override void LoadComplete() @@ -92,14 +94,6 @@ namespace osu.Game.Screens.Edit.Compose.Components gridCache.Invalidate(); } - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.RequiredParentSizeToFit) > 0) - gridCache.Invalidate(); - - return base.Invalidate(invalidation, source, shallPropagate); - } - protected override void Update() { base.Update(); diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index a667466965..36ce131411 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using osu.Framework; -using osu.Framework.Caching; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -14,6 +13,7 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Allocation; +using osu.Framework.Layout; using osu.Framework.Threading; namespace osu.Game.Screens.Play @@ -22,6 +22,11 @@ namespace osu.Game.Screens.Play { private BufferedContainer columns; + public SquareGraph() + { + AddLayout(layout); + } + public int ColumnCount => columns?.Children.Count ?? 0; private int progress; @@ -68,14 +73,7 @@ namespace osu.Game.Screens.Play } } - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.DrawSize) > 0) - layout.Invalidate(); - return base.Invalidate(invalidation, source, shallPropagate); - } - - private readonly Cached layout = new Cached(); + private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawSize); private ScheduledDelegate scheduledCreate; protected override void Update() From fe1f2858c19e8e5bf7726abe121b61c8fc9d13ca Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 24 Feb 2020 23:10:37 +0300 Subject: [PATCH 036/192] Refactor to avoid duplicated code --- osu.Game/Overlays/Comments/CommentsPage.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Comments/CommentsPage.cs b/osu.Game/Overlays/Comments/CommentsPage.cs index 591400f741..1fb6032924 100644 --- a/osu.Game/Overlays/Comments/CommentsPage.cs +++ b/osu.Game/Overlays/Comments/CommentsPage.cs @@ -97,20 +97,12 @@ namespace osu.Game.Overlays.Comments var orphaned = new List(); // Exclude possible duplicated comments. - foreach (var topLevel in bundle.Comments) + foreach (var comment in bundle.Comments.Concat(bundle.IncludedComments)) { - if (commentDictionary.ContainsKey(topLevel.Id)) + if (commentDictionary.ContainsKey(comment.Id)) continue; - addNewComment(topLevel); - } - - foreach (var child in bundle.IncludedComments) - { - if (commentDictionary.ContainsKey(child.Id)) - continue; - - addNewComment(child); + addNewComment(comment); } // Comments whose parents were seen later than themselves can now be added. From 4cbb2a2f591f07f6a748aa57434bf78554a9c4ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Feb 2020 21:30:27 +0100 Subject: [PATCH 037/192] Move comment to more pertinent place --- osu.Game/Overlays/Comments/CommentsPage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/CommentsPage.cs b/osu.Game/Overlays/Comments/CommentsPage.cs index 1fb6032924..f3a774908c 100644 --- a/osu.Game/Overlays/Comments/CommentsPage.cs +++ b/osu.Game/Overlays/Comments/CommentsPage.cs @@ -96,9 +96,9 @@ namespace osu.Game.Overlays.Comments { var orphaned = new List(); - // Exclude possible duplicated comments. foreach (var comment in bundle.Comments.Concat(bundle.IncludedComments)) { + // Exclude possible duplicated comments. if (commentDictionary.ContainsKey(comment.Id)) continue; From a020566b1e69c0aca51d3a30df5fa191b760d607 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 24 Feb 2020 23:37:46 +0300 Subject: [PATCH 038/192] Add HoverClickSounds --- .../Overlays/OverlayPanelDisplayStyleControl.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs index 952b277cac..7269007b41 100644 --- a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs +++ b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs @@ -60,12 +60,16 @@ namespace osu.Game.Overlays : base(value) { Size = new Vector2(11); - Add(icon = new SpriteIcon + AddRange(new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit + icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit + }, + new HoverClickSounds() }); } From d8413c558bac3aa767ed51f3e1b60be446219374 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 24 Feb 2020 23:41:34 +0300 Subject: [PATCH 039/192] Use fully qualified name for enum item description in CommentsHeader --- osu.Game/Overlays/Comments/CommentsHeader.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Comments/CommentsHeader.cs b/osu.Game/Overlays/Comments/CommentsHeader.cs index ff5602e289..83f44ccd80 100644 --- a/osu.Game/Overlays/Comments/CommentsHeader.cs +++ b/osu.Game/Overlays/Comments/CommentsHeader.cs @@ -11,8 +11,6 @@ using osu.Framework.Graphics.Sprites; using osuTK; using osu.Framework.Input.Events; using osu.Game.Graphics.Sprites; -using System.ComponentModel; -using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Overlays.Comments { @@ -111,7 +109,7 @@ namespace osu.Game.Overlays.Comments public enum CommentsSortCriteria { - [Description(@"Recent")] + [System.ComponentModel.Description(@"Recent")] New, Old, Top From f71c45cb1bb82b5f606e4465d2b514b6c02bda62 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 25 Feb 2020 11:30:33 +0900 Subject: [PATCH 040/192] Remove shallPropagate --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 2 +- osu.Game/Graphics/Backgrounds/Triangles.cs | 2 +- osu.Game/Screens/Menu/LogoVisualisation.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 9a23a2be30..37df5ec540 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { base.Update(); - Invalidate(Invalidation.DrawNode, shallPropagate: false); + Invalidate(Invalidation.DrawNode); const int fade_clock_reset_threshold = 1000000; diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index b9c7b26e3e..590e4b2a5c 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -129,7 +129,7 @@ namespace osu.Game.Graphics.Backgrounds { base.Update(); - Invalidate(Invalidation.DrawNode, shallPropagate: false); + Invalidate(Invalidation.DrawNode); if (CreateNewTriangles) addTriangles(false); diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index dcc68296f6..67537fa9df 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -150,7 +150,7 @@ namespace osu.Game.Screens.Menu frequencyAmplitudes[i] = 0; } - Invalidate(Invalidation.DrawNode, shallPropagate: false); + Invalidate(Invalidation.DrawNode); } protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(this); From c1455be85592c3ccf8c0ab95b27c23830927ca53 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 25 Feb 2020 10:29:03 +0300 Subject: [PATCH 041/192] Add tests --- .../Visual/Online/TestSceneCommentsPage.cs | 52 +++++++++++++++++-- osu.Game/Overlays/Comments/CommentsPage.cs | 16 +++--- 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsPage.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsPage.cs index 0ed8864860..a28a0107a1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsPage.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsPage.cs @@ -13,6 +13,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osuTK; +using JetBrains.Annotations; +using NUnit.Framework; namespace osu.Game.Tests.Visual.Online { @@ -30,6 +32,8 @@ namespace osu.Game.Tests.Visual.Online private readonly BindableBool showDeleted = new BindableBool(); private readonly Container content; + private TestCommentsPage commentsPage; + public TestSceneCommentsPage() { Add(new FillFlowContainer @@ -57,15 +61,29 @@ namespace osu.Game.Tests.Visual.Online } } }); + } - AddStep("load comments", () => createPage(getCommentBundle())); - AddStep("load empty comments", () => createPage(getEmptyCommentBundle())); + [Test] + public void TestAppendDuplicatedComment() + { + AddStep("Create page", () => createPage(getCommentBundle())); + AddAssert("Dictionary length is 10", () => commentsPage?.DictionaryLength == 10); + AddStep("Append existing comment", () => commentsPage?.AppendComments(getCommentSubBundle())); + AddAssert("Dictionary length is 10", () => commentsPage?.DictionaryLength == 10); + } + + [Test] + public void TestEmptyBundle() + { + AddStep("Create page", () => createPage(getEmptyCommentBundle())); + AddAssert("Dictionary length is 0", () => commentsPage?.DictionaryLength == 0); } private void createPage(CommentBundle commentBundle) { + commentsPage = null; content.Clear(); - content.Add(new CommentsPage(commentBundle) + content.Add(commentsPage = new TestCommentsPage(commentBundle) { ShowDeleted = { BindTarget = showDeleted } }); @@ -182,5 +200,33 @@ namespace osu.Game.Tests.Visual.Online } }, }; + + private CommentBundle getCommentSubBundle() => new CommentBundle + { + Comments = new List + { + new Comment + { + Id = 1, + Message = "Simple test comment", + LegacyName = "TestUser1", + CreatedAt = DateTimeOffset.Now, + VotesCount = 5 + }, + }, + IncludedComments = new List(), + }; + + private class TestCommentsPage : CommentsPage + { + public TestCommentsPage(CommentBundle commentBundle) + : base(commentBundle) + { + } + + public new void AppendComments([NotNull] CommentBundle bundle) => base.AppendComments(bundle); + + public int DictionaryLength => CommentDictionary.Count; + } } } diff --git a/osu.Game/Overlays/Comments/CommentsPage.cs b/osu.Game/Overlays/Comments/CommentsPage.cs index f3a774908c..9b146b0a7d 100644 --- a/osu.Game/Overlays/Comments/CommentsPage.cs +++ b/osu.Game/Overlays/Comments/CommentsPage.cs @@ -61,15 +61,15 @@ namespace osu.Game.Overlays.Comments return; } - appendComments(commentBundle); + AppendComments(commentBundle); } private DrawableComment getDrawableComment(Comment comment) { - if (commentDictionary.TryGetValue(comment.Id, out var existing)) + if (CommentDictionary.TryGetValue(comment.Id, out var existing)) return existing; - return commentDictionary[comment.Id] = new DrawableComment(comment) + return CommentDictionary[comment.Id] = new DrawableComment(comment) { ShowDeleted = { BindTarget = ShowDeleted }, Sort = { BindTarget = Sort }, @@ -81,25 +81,25 @@ namespace osu.Game.Overlays.Comments { var request = new GetCommentsRequest(CommentableId.Value, Type.Value, Sort.Value, page, drawableComment.Comment.Id); - request.Success += response => Schedule(() => appendComments(response)); + request.Success += response => Schedule(() => AppendComments(response)); api.PerformAsync(request); } - private readonly Dictionary commentDictionary = new Dictionary(); + protected readonly Dictionary CommentDictionary = new Dictionary(); /// /// Appends retrieved comments to the subtree rooted of comments in this page. /// /// The bundle of comments to add. - private void appendComments([NotNull] CommentBundle bundle) + protected void AppendComments([NotNull] CommentBundle bundle) { var orphaned = new List(); foreach (var comment in bundle.Comments.Concat(bundle.IncludedComments)) { // Exclude possible duplicated comments. - if (commentDictionary.ContainsKey(comment.Id)) + if (CommentDictionary.ContainsKey(comment.Id)) continue; addNewComment(comment); @@ -118,7 +118,7 @@ namespace osu.Game.Overlays.Comments // Comments that have no parent are added as top-level comments to the flow. flow.Add(drawableComment); } - else if (commentDictionary.TryGetValue(comment.ParentId.Value, out var parentDrawable)) + else if (CommentDictionary.TryGetValue(comment.ParentId.Value, out var parentDrawable)) { // The comment's parent has already been seen, so the parent<-> child links can be added. comment.ParentComment = parentDrawable.Comment; From d6046abe700722023b2325ea1c6ba19664a2e8ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Feb 2020 18:59:16 +0900 Subject: [PATCH 042/192] Only expose save for now --- osu.Game/Screens/Edit/Editor.cs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d84151f5af..8d3b8849bc 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -20,6 +20,8 @@ using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Design; using osuTK.Input; +using System.Collections.Generic; +using osu.Framework; using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; using osu.Game.Graphics.Cursor; @@ -91,6 +93,16 @@ namespace osu.Game.Screens.Edit EditorMenuBar menuBar; + var fileMenuItems = new List(); + + fileMenuItems.Add(new EditorMenuItem("Save", MenuItemType.Standard, saveBeatmap)); + + if (RuntimeInfo.IsDesktop) + fileMenuItems.Add(new EditorMenuItem("Export package", MenuItemType.Standard, exportBeatmap)); + + fileMenuItems.Add(new EditorMenuItemSpacer()); + fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit)); + AddInternal(new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -121,13 +133,7 @@ namespace osu.Game.Screens.Edit { new MenuItem("File") { - Items = new[] - { - new EditorMenuItem("Save", MenuItemType.Standard, saveBeatmap), - new EditorMenuItem("Export package", MenuItemType.Standard, exportBeatmap), - new EditorMenuItemSpacer(), - new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit), - } + Items = fileMenuItems } } } From a0474563545ed534f1fd6e15172be38662993705 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Feb 2020 19:07:15 +0900 Subject: [PATCH 043/192] Revert changes to make HitObject abstract --- .../TestSceneDrawableJudgement.cs | 2 +- .../TestSceneDrawableJudgement.cs | 2 +- .../TestSceneTaikoPlayfield.cs | 8 ++++---- .../Gameplay/TestSceneHitObjectAccentColour.cs | 2 +- .../Gameplay/TestSceneHitObjectContainer.cs | 12 ++++++------ .../Gameplay/TestSceneDrawableScrollingRuleset.cs | 3 ++- .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 4 ++-- .../Visual/Gameplay/TestSceneScrollingHitObjects.cs | 4 ++-- .../Visual/Gameplay/TestSceneSongProgress.cs | 4 ++-- .../Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 1 + osu.Game/Rulesets/Objects/HitObject.cs | 8 ++++---- .../Objects/{ => Legacy}/ConvertHitObject.cs | 4 ++-- 12 files changed, 28 insertions(+), 26 deletions(-) rename osu.Game/Rulesets/Objects/{ => Legacy}/ConvertHitObject.cs (83%) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs index 7b71f2feda..692d079c16 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Tests foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1)) { AddStep("Show " + result.GetDescription(), () => SetContents(() => - new DrawableManiaJudgement(new JudgementResult(new ConvertHitObject(), new Judgement()) { Type = result }, null) + new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 8f72e2f60d..02d4406809 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Tests foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1)) { AddStep("Show " + result.GetDescription(), () => SetContents(() => - new DrawableOsuJudgement(new JudgementResult(new ConvertHitObject(), new Judgement()) { Type = result }, null) + new DrawableOsuJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs index c8c4d004d6..c01eef5252 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new ConvertHitObject(), new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); } private void addStrongHitJudgement(bool kiai) @@ -163,13 +163,13 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new ConvertHitObject(), new TaikoJudgement()) { Type = hitResult }); - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new ConvertHitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great }); } private void addMissJudgement() { - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new ConvertHitObject(), new TaikoJudgement()) { Type = HitResult.Miss }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss }); } private void addBarLine(bool major, double delay = scroll_time) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs index e99a10524e..7a89642e11 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -11,8 +11,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Testing; using osu.Game.Audio; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; using osu.Game.Tests.Visual; diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs index 6452857bad..f2bfccb6de 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs @@ -27,9 +27,9 @@ namespace osu.Game.Tests.Gameplay { DrawableHitObject hitObject = null; - AddStep("setup", () => container.Add(new TestDrawableHitObject(new ConvertHitObject { StartTime = 500 }))); + AddStep("setup", () => container.Add(new TestDrawableHitObject(new HitObject { StartTime = 500 }))); - AddStep("add late hitobject", () => container.Add(hitObject = new TestDrawableHitObject(new ConvertHitObject { StartTime = 1000 }))); + AddStep("add late hitobject", () => container.Add(hitObject = new TestDrawableHitObject(new HitObject { StartTime = 1000 }))); AddAssert("hitobject index is 0", () => container.IndexOf(hitObject) == 0); } @@ -39,9 +39,9 @@ namespace osu.Game.Tests.Gameplay { DrawableHitObject hitObject = null; - AddStep("setup", () => container.Add(new TestDrawableHitObject(new ConvertHitObject { StartTime = 500 }))); + AddStep("setup", () => container.Add(new TestDrawableHitObject(new HitObject { StartTime = 500 }))); - AddStep("add early hitobject", () => container.Add(hitObject = new TestDrawableHitObject(new ConvertHitObject()))); + AddStep("add early hitobject", () => container.Add(hitObject = new TestDrawableHitObject(new HitObject()))); AddAssert("hitobject index is 0", () => container.IndexOf(hitObject) == 1); } @@ -54,8 +54,8 @@ namespace osu.Game.Tests.Gameplay AddStep("setup", () => { - container.Add(firstObject = new TestDrawableHitObject(new ConvertHitObject())); - container.Add(secondObject = new TestDrawableHitObject(new ConvertHitObject { StartTime = 1000 })); + container.Add(firstObject = new TestDrawableHitObject(new HitObject())); + container.Add(secondObject = new TestDrawableHitObject(new HitObject { StartTime = 1000 })); }); AddStep("move first object after second", () => firstObject.HitObject.StartTime = 2000); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index d350c3d58d..b25b81c9af 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -20,6 +20,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.UI; @@ -163,7 +164,7 @@ namespace osu.Game.Tests.Visual.Gameplay var beatmap = new Beatmap { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } }; for (int i = 0; i < 10; i++) - beatmap.HitObjects.Add(new ConvertHitObject { StartTime = i * time_range }); + beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range }); return beatmap; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 494611d414..ddad337c66 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Game.Rulesets.Objects; using System; using System.Collections.Generic; using osu.Game.Rulesets.Judgements; @@ -12,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Scoring; @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void newJudgement(double offset = 0) { - var judgement = new JudgementResult(new ConvertHitObject(), new Judgement()) + var judgement = new JudgementResult(new HitObject(), new Judgement()) { TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset, Type = HitResult.Perfect, diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs index c2a71eadae..d03716db2e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs @@ -224,7 +224,7 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestDrawableControlPoint : DrawableHitObject { public TestDrawableControlPoint(ScrollingDirection direction, double time) - : base(new ConvertHitObject { StartTime = time }) + : base(new HitObject { StartTime = time }) { Origin = Anchor.Centre; @@ -255,7 +255,7 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestDrawableHitObject : DrawableHitObject { public TestDrawableHitObject(double time) - : base(new ConvertHitObject { StartTime = time }) + : base(new HitObject { StartTime = time }) { Origin = Anchor.Custom; OriginPosition = new Vector2(75 / 4.0f); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index 6a60ae110b..b9b13d7bd8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var objects = new List(); for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000) - objects.Add(new ConvertHitObject { StartTime = i }); + objects.Add(new HitObject { StartTime = i }); replaceObjects(objects); } @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var objects = new List(); for (double i = 0; i < 5000; i++) - objects.Add(new ConvertHitObject { StartTime = i }); + objects.Add(new HitObject { StartTime = i }); replaceObjects(objects); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 5cac897200..e02ebf3be1 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Taiko; diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index bef48f0ced..477d025253 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Objects /// HitObjects may contain more properties for which you should be checking through the IHas* types. /// /// - public abstract class HitObject + public class HitObject { /// /// A small adjustment to the start time of control points to account for rounding/precision errors. @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Objects [JsonIgnore] public IReadOnlyList NestedHitObjects => nestedHitObjects; - protected HitObject() + public HitObject() { StartTimeBindable.ValueChanged += time => { @@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Objects /// Used to decide on drawable object lifetimes. /// [NotNull] - public abstract Judgement CreateJudgement(); + public virtual Judgement CreateJudgement() => new Judgement(); /// /// Creates the for this . @@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.Objects /// /// [NotNull] - protected abstract HitWindows CreateHitWindows(); + protected virtual HitWindows CreateHitWindows() => new HitWindows(); } public static class HitObjectExtensions diff --git a/osu.Game/Rulesets/Objects/ConvertHitObject.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs similarity index 83% rename from osu.Game/Rulesets/Objects/ConvertHitObject.cs rename to osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs index 81a88615a3..e3b0d8a498 100644 --- a/osu.Game/Rulesets/Objects/ConvertHitObject.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs @@ -4,12 +4,12 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; -namespace osu.Game.Rulesets.Objects +namespace osu.Game.Rulesets.Objects.Legacy { /// /// A hit object only used for conversion, not actual gameplay. /// - public class ConvertHitObject : HitObject + internal abstract class ConvertHitObject : HitObject { public override Judgement CreateJudgement() => new IgnoreJudgement(); From 36079236e6d96ea1c3a688e662cee9de4e6e0fe4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Feb 2020 19:22:51 +0900 Subject: [PATCH 044/192] Remove pointless comment Co-Authored-By: Dan Balasescu --- osu.Game/Rulesets/Objects/HitObject.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 477d025253..9a8efdde84 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -144,7 +144,6 @@ namespace osu.Game.Rulesets.Objects /// /// Creates the that represents the scoring information for this . - /// Used to decide on drawable object lifetimes. /// [NotNull] public virtual Judgement CreateJudgement() => new Judgement(); From b872f782e7eae6fe381c2c9f32666626326e4d49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Feb 2020 20:04:39 +0900 Subject: [PATCH 045/192] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 939d179b1d..24afbb86fb 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 24ee25c4de..c5ebf0f712 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 3743138c1a..9574b8cba9 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From 9557a2cd9659fdc565fa95510d7f6b49c71337d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Feb 2020 20:52:33 +0900 Subject: [PATCH 046/192] Use object initialiser --- osu.Game/Screens/Edit/Editor.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 8d3b8849bc..3a6f02f811 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -93,9 +93,10 @@ namespace osu.Game.Screens.Edit EditorMenuBar menuBar; - var fileMenuItems = new List(); - - fileMenuItems.Add(new EditorMenuItem("Save", MenuItemType.Standard, saveBeatmap)); + var fileMenuItems = new List + { + new EditorMenuItem("Save", MenuItemType.Standard, saveBeatmap) + }; if (RuntimeInfo.IsDesktop) fileMenuItems.Add(new EditorMenuItem("Export package", MenuItemType.Standard, exportBeatmap)); From 535a7989d613538564df1ed650968bf56d49d965 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 25 Feb 2020 20:57:15 +0100 Subject: [PATCH 047/192] Open rankings overlay on spotlights page. --- osu.Game/Overlays/RankingsOverlay.cs | 6 ++++++ osu.Game/Screens/Menu/MainMenu.cs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 3304c6ebec..afb23883ac 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -138,6 +138,12 @@ namespace osu.Game.Overlays Country.Value = requested; } + public void ShowSpotlights() + { + Scope.Value = RankingsScope.Spotlights; + Show(); + } + private void loadNewContent() { loading.Show(); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index af5db4ce8c..127270f521 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -134,7 +134,7 @@ namespace osu.Game.Screens.Menu buttons.OnSettings = () => settings?.ToggleVisibility(); buttons.OnDirect = () => direct?.ToggleVisibility(); - buttons.OnChart = () => rankings?.ToggleVisibility(); + buttons.OnChart = () => rankings?.ShowSpotlights(); LoadComponentAsync(background = new BackgroundScreenDefault()); preloadSongSelect(); From 88ec31c262e7d8fefe84d90babb7358a9ebd13c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 25 Feb 2020 22:39:20 +0100 Subject: [PATCH 048/192] Add tests demonstrating crash --- .../TestSceneBananaShower.cs | 8 ++- .../TestSceneJuiceStream.cs | 56 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs index 56e378d19e..20911b8d06 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs @@ -29,6 +29,12 @@ namespace osu.Game.Rulesets.Catch.Tests { } + [Test] + public void TestBananaShower() + { + AddUntilStep("player is done", () => !Player.ValidForResume); + } + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { var beatmap = new Beatmap @@ -40,7 +46,7 @@ namespace osu.Game.Rulesets.Catch.Tests } }; - beatmap.HitObjects.Add(new BananaShower { StartTime = 200, Duration = 5000, NewCombo = true }); + beatmap.HitObjects.Add(new BananaShower { StartTime = 200, Duration = 3000, NewCombo = true }); return beatmap; } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs new file mode 100644 index 0000000000..cbc87459e1 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public class TestSceneJuiceStream : PlayerTestScene + { + public TestSceneJuiceStream() + : base(new CatchRuleset()) + { + } + + [Test] + public void TestJuiceStreamEndingCombo() + { + AddUntilStep("player is done", () => !Player.ValidForResume); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty { CircleSize = 5, SliderMultiplier = 2 }, + Ruleset = ruleset + }, + HitObjects = new List + { + new JuiceStream + { + X = 0.5f, + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(0, 100) + }), + StartTime = 200 + }, + new Banana + { + X = 0.5f, + StartTime = 1000, + NewCombo = true + } + } + }; + } +} From bf36dc10a561b02bad1399a6c3a0991572173a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 25 Feb 2020 23:13:32 +0100 Subject: [PATCH 049/192] Fix invalid cast Since introduction of IgnoreJudgement and its usage in JuiceStream and BananaShower the hard cast in CatcherArea that was used to check if the drawable hit object should cause the fruits on the plate explode at the end of combo caused a hard crash instead, since IgnoreJudgement was no longer deriving from CatchJudgement. Replace the hard cast with a soft pattern-matched cast. --- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index ff2471f8bf..0b3809150a 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Catch.UI if (fruit.HitObject.LastInCombo) { - if (((CatchJudgement)result.Judgement).ShouldExplodeFor(result)) + if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result)) runAfterLoaded(() => MovableCatcher.Explode()); else MovableCatcher.Drop(); From fab49fb1ba593f6a1c7da84afae5605871e77f53 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 26 Feb 2020 12:18:46 +0900 Subject: [PATCH 050/192] Slightly increase HP awarded for 100s --- osu.Game/Rulesets/Judgements/Judgement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 599135ba54..9105b920ca 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Judgements return -DEFAULT_MAX_HEALTH_INCREASE * 0.01; case HitResult.Good: - return DEFAULT_MAX_HEALTH_INCREASE * 0.3; + return DEFAULT_MAX_HEALTH_INCREASE * 0.5; case HitResult.Great: return DEFAULT_MAX_HEALTH_INCREASE; From 334ec7bbd48ea58c524ba24f0ad4108db9d33dce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 26 Feb 2020 15:06:30 +0900 Subject: [PATCH 051/192] Apply further framework changes --- osu.Game/Graphics/Containers/SectionsContainer.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index 9d886c457f..07a50c39e1 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Layout; namespace osu.Game.Graphics.Containers { @@ -142,15 +143,17 @@ namespace osu.Game.Graphics.Containers public void ScrollToTop() => scrollContainer.ScrollTo(0); - public override void InvalidateFromChild(Invalidation invalidation, Drawable source = null) + protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) { - base.InvalidateFromChild(invalidation, source); + var result = base.OnInvalidate(invalidation, source); - if ((invalidation & Invalidation.DrawSize) != 0) + if (source == InvalidationSource.Child && (invalidation & Invalidation.DrawSize) != 0) { - if (source == ExpandableHeader) //We need to recalculate the positions if the ExpandableHeader changed its size - lastKnownScroll = -1; + lastKnownScroll = -1; + result = true; } + + return result; } private float lastKnownScroll; From 015a39abc76eaebe0b69ff3b1bf6eef6b4c0a761 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Feb 2020 18:45:06 +0900 Subject: [PATCH 052/192] Fix hyperdash fruit not visible on default skin --- .../Objects/Drawables/FruitPiece.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs index 0f5044eda7..5797588ded 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs @@ -7,9 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osu.Game.Rulesets.Objects.Drawables; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawables @@ -64,15 +62,24 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables if (hitObject.HyperDash) { - AddInternal(new Pulp + AddInternal(new Circle { - RelativePositionAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - AccentColour = { Value = Color4.Red }, - Blending = BlendingParameters.Additive, - Alpha = 0.5f, - Scale = new Vector2(1.333f) + BorderColour = Color4.Red, + BorderThickness = 12f * RADIUS_ADJUST, + Children = new Drawable[] + { + new Box + { + AlwaysPresent = true, + Alpha = 0.3f, + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + Colour = Color4.Red, + } + } }); } } From 83495eb64804e1089cb8712a992f20e64996d1de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Feb 2020 19:22:09 +0900 Subject: [PATCH 053/192] Add testing support for hyperdash fruits --- .../TestSceneFruitObjects.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index 7bade41e6c..82d5aa936f 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -42,6 +42,9 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("show droplet", () => SetContents(createDrawableDroplet)); AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet)); + + foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation))) + AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawable(rep, true))); } private Drawable createDrawableTinyDroplet() @@ -82,9 +85,13 @@ namespace osu.Game.Rulesets.Catch.Tests }; } - private Drawable createDrawable(FruitVisualRepresentation rep) + private Drawable createDrawable(FruitVisualRepresentation rep, bool hyperdash = false) { - Fruit fruit = new TestCatchFruit(rep) { Scale = 1.5f }; + Fruit fruit = new TestCatchFruit(rep) + { + Scale = 1.5f, + HyperDashTarget = hyperdash ? new Banana() : null + }; return new DrawableFruit(fruit) { From e5927447fc5664c00f0092a48b1ab24913610dee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Feb 2020 19:22:46 +0900 Subject: [PATCH 054/192] Fix hyperdash fruit not visible on custom skins --- .../Skinning/LegacyFruitPiece.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs index 2631fe5487..25ee0811d0 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; +using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Skinning @@ -49,6 +50,23 @@ namespace osu.Game.Rulesets.Catch.Skinning Origin = Anchor.Centre, }, }; + + if (drawableCatchObject.HitObject.HyperDash) + { + var hyperDash = new Sprite + { + Texture = skin.GetTexture(lookupName), + Colour = Color4.Red, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, + Depth = 1, + Alpha = 0.7f, + Scale = new Vector2(1.2f) + }; + + AddInternal(hyperDash); + } } protected override void LoadComplete() From 2aa649e073d75b0d7b9df8880096f510135d279e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Feb 2020 19:31:49 +0900 Subject: [PATCH 055/192] Fix catcher dropping juice streams due to it considering ignored judgements --- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 0b3809150a..b977d46611 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -50,6 +50,9 @@ namespace osu.Game.Rulesets.Catch.UI public void OnResult(DrawableCatchHitObject fruit, JudgementResult result) { + if (result.Judgement is IgnoreJudgement) + return; + void runAfterLoaded(Action action) { if (lastPlateableFruit == null) From ed2bdd7106da3abb8bf04bf9bbb47bbc20ef2b96 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Feb 2020 20:45:57 +0900 Subject: [PATCH 056/192] Fix stutter when showing game HUD after being hidden for a while --- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 7 ------- .../Screens/Play/HUD/StandardHealthDisplay.cs | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index acd8656fb2..37038ad58c 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -13,12 +13,5 @@ namespace osu.Game.Screens.Play.HUD MinValue = 0, MaxValue = 1 }; - - protected HealthDisplay() - { - Current.ValueChanged += health => SetHealth((float)health.NewValue); - } - - protected abstract void SetHealth(float value); } } diff --git a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs index 315bc27a79..7736541c92 100644 --- a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Judgements; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play.HUD @@ -108,11 +109,23 @@ namespace osu.Game.Screens.Play.HUD if (result.Type == HitResult.Miss) return; + Scheduler.AddOnce(flash); + } + + private void flash() + { fill.FadeEdgeEffectTo(Math.Min(1, fill.EdgeEffect.Colour.Linear.A + (1f - base_glow_opacity) / glow_max_hits), 50, Easing.OutQuint) .Delay(glow_fade_delay) .FadeEdgeEffectTo(base_glow_opacity, glow_fade_time, Easing.OutQuint); } - protected override void SetHealth(float value) => fill.ResizeTo(new Vector2(value, 1), 200, Easing.OutQuint); + protected override void Update() + { + base.Update(); + + fill.Width = Interpolation.ValueAt( + Math.Clamp(Clock.ElapsedFrameTime, 0, 200), + fill.Width, (float)Current.Value, 0, 200, Easing.OutQuint); + } } } From 54d83eff94dc357a4ffb1d2443f42f170023f022 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 26 Feb 2020 19:35:20 +0300 Subject: [PATCH 057/192] Use OverlayColourProfider for VotePill component --- osu.Game.Tests/Visual/Online/TestSceneVotePill.cs | 5 +++++ osu.Game/Overlays/Comments/VotePill.cs | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs index 8197cf72de..770cef8f1b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs @@ -7,6 +7,8 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Overlays.Comments; using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Allocation; +using osu.Game.Overlays; namespace osu.Game.Tests.Visual.Online { @@ -18,6 +20,9 @@ namespace osu.Game.Tests.Visual.Online typeof(VotePill) }; + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + private VotePill votePill; [Test] diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index 978846549e..aa9723ea85 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -33,6 +33,9 @@ namespace osu.Game.Overlays.Comments [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + private readonly Comment comment; private Box background; private Box hoverLayer; @@ -68,7 +71,7 @@ namespace osu.Game.Overlays.Comments base.LoadComplete(); isVoted.Value = comment.IsVoted; votesCount.Value = comment.VotesCount; - isVoted.BindValueChanged(voted => background.Colour = voted.NewValue ? AccentColour : OsuColour.Gray(0.05f), true); + isVoted.BindValueChanged(voted => background.Colour = voted.NewValue ? AccentColour : colourProvider.Background6, true); votesCount.BindValueChanged(count => votesCounter.Text = $"+{count.NewValue}", true); } From a4edd3b8a18a7a197f39d3db98ae1997f5e02a74 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Feb 2020 09:24:45 +0900 Subject: [PATCH 058/192] Fix iOS/Android lockups by disabling LLVM --- osu.Android.props | 2 -- osu.Android/osu.Android.csproj | 3 ++- osu.iOS.props | 3 --- osu.iOS/osu.iOS.csproj | 3 ++- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 24afbb86fb..28fbdb3367 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -25,7 +25,6 @@ portable False DEBUG;TRACE - false false true false @@ -34,7 +33,6 @@ false None True - true false False true diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index ac3905a372..0598a50530 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -13,6 +13,7 @@ osu.Android Properties\AndroidManifest.xml armeabi-v7a;x86;arm64-v8a + false cjk;mideast;other;rare;west @@ -52,4 +53,4 @@ - \ No newline at end of file + diff --git a/osu.iOS.props b/osu.iOS.props index 9574b8cba9..abd562dc81 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -49,9 +49,6 @@ true 28126 - - true - Static diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index d60a3475e7..1e9a21865d 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -7,6 +7,7 @@ {3F082D0B-A964-43D7-BDF7-C256D76A50D0} osu.iOS osu.iOS + false @@ -116,4 +117,4 @@ - \ No newline at end of file + From c06db5a54acbb72d90c1a208f35b3e8b289c1fd5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Feb 2020 11:28:29 +0900 Subject: [PATCH 059/192] Remove legacy DrawableHitObject state management --- .../Objects/Drawables/DrawableHitObject.cs | 69 +++++-------------- 1 file changed, 17 insertions(+), 52 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 9a5e25c8ac..aa29e42fac 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -221,18 +221,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public event Action ApplyCustomUpdateState; -#pragma warning disable 618 // (legacy state management) - can be removed 20200227 - - /// - /// Enables automatic transform management of this hitobject. Implementation of transforms should be done in and only. Rewinding and removing previous states is done automatically. - /// - /// - /// Going forward, this is the preferred way of implementing s. Previous functionality - /// is offered as a compatibility layer until all rulesets have been migrated across. - /// - [Obsolete("Use UpdateInitialTransforms()/UpdateStateTransforms() instead")] // can be removed 20200227 - protected virtual bool UseTransformStateManagement => true; - protected override void ClearInternal(bool disposeChildren = true) => throw new InvalidOperationException($"Should never clear a {nameof(DrawableHitObject)}"); private void updateState(ArmedState newState, bool force = false) @@ -240,35 +228,28 @@ namespace osu.Game.Rulesets.Objects.Drawables if (State.Value == newState && !force) return; - if (UseTransformStateManagement) + LifetimeEnd = double.MaxValue; + + double transformTime = HitObject.StartTime - InitialLifetimeOffset; + + base.ApplyTransformsAt(double.MinValue, true); + base.ClearTransformsAfter(double.MinValue, true); + + using (BeginAbsoluteSequence(transformTime, true)) { - LifetimeEnd = double.MaxValue; + UpdateInitialTransforms(); - double transformTime = HitObject.StartTime - InitialLifetimeOffset; + var judgementOffset = Result?.TimeOffset ?? 0; - base.ApplyTransformsAt(double.MinValue, true); - base.ClearTransformsAfter(double.MinValue, true); - - using (BeginAbsoluteSequence(transformTime, true)) + using (BeginDelayedSequence(InitialLifetimeOffset + judgementOffset, true)) { - UpdateInitialTransforms(); - - var judgementOffset = Result?.TimeOffset ?? 0; - - using (BeginDelayedSequence(InitialLifetimeOffset + judgementOffset, true)) - { - UpdateStateTransforms(newState); - state.Value = newState; - } + UpdateStateTransforms(newState); + state.Value = newState; } - - if (state.Value != ArmedState.Idle && LifetimeEnd == double.MaxValue || HitObject.HitWindows == null) - Expire(); } - else - state.Value = newState; - UpdateState(newState); + if (state.Value != ArmedState.Idle && LifetimeEnd == double.MaxValue || HitObject.HitWindows == null) + Expire(); // apply any custom state overrides ApplyCustomUpdateState?.Invoke(this, newState); @@ -303,30 +284,14 @@ namespace osu.Game.Rulesets.Objects.Drawables public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null) { - // When we are using automatic state management, parent calls to this should be blocked for safety. - if (!UseTransformStateManagement) - base.ClearTransformsAfter(time, propagateChildren, targetMember); + // Parent calls to this should be blocked for safety, as we are manually handling this in updateState. } public override void ApplyTransformsAt(double time, bool propagateChildren = false) { - // When we are using automatic state management, parent calls to this should be blocked for safety. - if (!UseTransformStateManagement) - base.ApplyTransformsAt(time, propagateChildren); + // Parent calls to this should be blocked for safety, as we are manually handling this in updateState. } - /// - /// Legacy method to handle state changes. - /// Should generally not be used when is true; use instead. - /// - /// The new armed state. - [Obsolete("Use UpdateInitialTransforms()/UpdateStateTransforms() instead")] // can be removed 20200227 - protected virtual void UpdateState(ArmedState state) - { - } - -#pragma warning restore 618 - #endregion protected sealed override void SkinChanged(ISkinSource skin, bool allowFallback) From 7ad6ad0bb01fbc023a02080fd6b373571455a4e9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Feb 2020 13:32:23 +0900 Subject: [PATCH 060/192] Remove hacks that bypassed layout shortcomings --- osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs | 10 ++-------- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 11 ++--------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index a7ed1f5846..6cd1aa912f 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -246,10 +246,11 @@ namespace osu.Game.Screens.Multi FillMode = FillMode.Fill, Beatmap = { BindTarget = Beatmap } }, - new Container + new FillFlowContainer { Depth = -1, RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, // This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle Shear = new Vector2(0.8f, 0), Alpha = 0.5f, @@ -259,7 +260,6 @@ namespace osu.Game.Screens.Multi new Box { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Colour = Color4.Black, Width = 0.4f, }, @@ -267,26 +267,20 @@ namespace osu.Game.Screens.Multi new Box { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)), Width = 0.05f, - X = 0.4f, }, new Box { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)), Width = 0.2f, - X = 0.45f, }, new Box { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)), Width = 0.05f, - X = 0.65f, }, } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 1672131949..6cd145cfef 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -159,11 +159,11 @@ namespace osu.Game.Screens.Select.Carousel Origin = Anchor.Centre, FillMode = FillMode.Fill, }, - // Todo: This should be a fill flow, but has invalidation issues (see https://github.com/ppy/osu-framework/issues/223) - new Container + new FillFlowContainer { Depth = -1, RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, // This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle Shear = new Vector2(0.8f, 0), Alpha = 0.5f, @@ -173,7 +173,6 @@ namespace osu.Game.Screens.Select.Carousel new Box { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Colour = Color4.Black, Width = 0.4f, }, @@ -181,26 +180,20 @@ namespace osu.Game.Screens.Select.Carousel new Box { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)), Width = 0.05f, - X = 0.4f, }, new Box { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)), Width = 0.2f, - X = 0.45f, }, new Box { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)), Width = 0.05f, - X = 0.65f, }, } }, From 101a5876419b52fe97028b8f86620c8d04b78afe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Feb 2020 15:51:55 +0900 Subject: [PATCH 061/192] Disable triangles in triangles intro --- osu.Game/Screens/Menu/IntroTriangles.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 29f32406e8..4e51ff939a 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -54,8 +54,6 @@ namespace osu.Game.Screens.Menu { base.LogoArriving(logo, resuming); - logo.Triangles = true; - if (!resuming) { PrepareMenuLoad(); From 3f5c4633bc235a6ed0add44b72295be71833c7a9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Feb 2020 16:40:56 +0900 Subject: [PATCH 062/192] Remove workarounds for CreateRoomRequest shortcomings --- osu.Game/Online/Multiplayer/PlaylistItem.cs | 4 +--- osu.Game/Online/Multiplayer/Room.cs | 9 --------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/osu.Game/Online/Multiplayer/PlaylistItem.cs b/osu.Game/Online/Multiplayer/PlaylistItem.cs index 11e4854174..9d6e8eb8e3 100644 --- a/osu.Game/Online/Multiplayer/PlaylistItem.cs +++ b/osu.Game/Online/Multiplayer/PlaylistItem.cs @@ -65,9 +65,7 @@ namespace osu.Game.Online.Multiplayer public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets) { - // If we don't have an api beatmap, the request occurred as a result of room creation, so we can query the local beatmap instead - // Todo: Is this a bug? Room creation only returns the beatmap ID - Beatmap.Value = apiBeatmap == null ? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == BeatmapID) : apiBeatmap.ToBeatmap(rulesets); + Beatmap.Value = apiBeatmap.ToBeatmap(rulesets); Ruleset.Value = rulesets.GetRuleset(RulesetID); Ruleset rulesetInstance = Ruleset.Value.CreateInstance(); diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index 2bfcc019fa..c55822c407 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -118,15 +118,6 @@ namespace osu.Game.Online.Multiplayer if (DateTimeOffset.Now >= EndDate.Value) Status.Value = new RoomStatusEnded(); - // transfer local beatmaps across to ensure we have Metadata available (CreateRoomRequest does not give us metadata as expected) - foreach (var item in other.Playlist) - { - var localItem = Playlist.FirstOrDefault(i => i.BeatmapID == item.BeatmapID); - - if (localItem != null) - item.Beatmap.Value.Metadata = localItem.Beatmap.Value.Metadata; - } - if (!Playlist.SequenceEqual(other.Playlist)) { Playlist.Clear(); From ffa8a50c6bcddfd643c6a5abd3c213b4fb750e2e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Feb 2020 19:21:59 +0900 Subject: [PATCH 063/192] Make User IEquatable --- osu.Game/Users/User.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index c573fdd089..d25c552160 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -9,7 +9,7 @@ using osu.Framework.Bindables; namespace osu.Game.Users { - public class User + public class User : IEquatable { [JsonProperty(@"id")] public long Id = 1; @@ -244,5 +244,13 @@ namespace osu.Game.Users [Description("Touch Screen")] Touch, } + + public bool Equals(User other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return Id == other.Id; + } } } From 99442ec9c322f66c450b059698d41e696a64e102 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Feb 2020 19:23:21 +0900 Subject: [PATCH 064/192] Implement single-room multiplayer room polling --- .../Online/API/Requests/GetRoomRequest.cs | 19 ++ .../Multi/Lounge/Components/RoomsContainer.cs | 4 +- .../Screens/Multi/Lounge/LoungeSubScreen.cs | 8 +- osu.Game/Screens/Multi/Multiplayer.cs | 37 ++- osu.Game/Screens/Multi/RoomManager.cs | 211 ++++++++++++++---- 5 files changed, 223 insertions(+), 56 deletions(-) create mode 100644 osu.Game/Online/API/Requests/GetRoomRequest.cs diff --git a/osu.Game/Online/API/Requests/GetRoomRequest.cs b/osu.Game/Online/API/Requests/GetRoomRequest.cs new file mode 100644 index 0000000000..531e1857de --- /dev/null +++ b/osu.Game/Online/API/Requests/GetRoomRequest.cs @@ -0,0 +1,19 @@ +// 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.Online.Multiplayer; + +namespace osu.Game.Online.API.Requests +{ + public class GetRoomRequest : APIRequest + { + private readonly int roomId; + + public GetRoomRequest(int roomId) + { + this.roomId = roomId; + } + + protected override string Target => $"rooms/{roomId}"; + } +} diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 64618a1d85..f14aa5fd8c 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components private Bindable filter { get; set; } [Resolved] - private Bindable currentRoom { get; set; } + private Bindable selectedRoom { get; set; } [Resolved] private IRoomManager roomManager { get; set; } @@ -122,7 +122,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components else roomFlow.Children.ForEach(r => r.State = r.Room == room ? SelectionState.Selected : SelectionState.NotSelected); - currentRoom.Value = room; + selectedRoom.Value = room; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index e2e5b1b549..7c10f0f975 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Multi.Lounge private readonly LoadingLayer loadingLayer; [Resolved] - private Bindable currentRoom { get; set; } + private Bindable selectedRoom { get; set; } public LoungeSubScreen() { @@ -101,8 +101,8 @@ namespace osu.Game.Screens.Multi.Lounge { base.OnResuming(last); - if (currentRoom.Value?.RoomID.Value == null) - currentRoom.Value = new Room(); + if (selectedRoom.Value?.RoomID.Value == null) + selectedRoom.Value = new Room(); onReturning(); } @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Multi.Lounge if (!this.IsCurrentScreen()) return; - currentRoom.Value = room; + selectedRoom.Value = room; this.Push(new MatchSubScreen(room)); } diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 1219919425..20b1f860a9 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Multi private readonly IBindable isIdle = new BindableBool(); [Cached] - private readonly Bindable currentRoom = new Bindable(); + private readonly Bindable selectedRoom = new Bindable(); [Cached] private readonly Bindable currentFilter = new Bindable(new FilterCriteria()); @@ -163,14 +163,39 @@ namespace osu.Game.Screens.Multi protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new CachedModelDependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Model.BindTo(currentRoom); + dependencies.Model.BindTo(selectedRoom); return dependencies; } private void updatePollingRate(bool idle) { - roomManager.TimeBetweenPolls = !this.IsCurrentScreen() || !(screenStack.CurrentScreen is LoungeSubScreen) ? 0 : (idle ? 120000 : 15000); - Logger.Log($"Polling adjusted to {roomManager.TimeBetweenPolls}"); + if (!this.IsCurrentScreen()) + { + roomManager.TimeBetweenListingPolls = 0; + roomManager.TimeBetweenSelectionPolls = 0; + } + else + { + switch (screenStack.CurrentScreen) + { + case LoungeSubScreen _: + roomManager.TimeBetweenListingPolls = idle ? 120000 : 15000; + roomManager.TimeBetweenSelectionPolls = idle ? 30000 : 5000; + break; + + case MatchSubScreen _: + roomManager.TimeBetweenListingPolls = 0; + roomManager.TimeBetweenSelectionPolls = idle ? 30000 : 5000; + break; + + default: + roomManager.TimeBetweenListingPolls = 0; + roomManager.TimeBetweenSelectionPolls = 0; + break; + } + } + + Logger.Log($"Polling adjusted (listing: {roomManager.TimeBetweenListingPolls}, selection: {roomManager.TimeBetweenSelectionPolls})"); } /// @@ -222,6 +247,8 @@ namespace osu.Game.Screens.Multi base.OnResuming(last); beginHandlingTrack(); + + updatePollingRate(isIdle.Value); } public override void OnSuspending(IScreen next) @@ -231,7 +258,7 @@ namespace osu.Game.Screens.Multi endHandlingTrack(); - roomManager.TimeBetweenPolls = 0; + updatePollingRate(isIdle.Value); } public override bool OnExiting(IScreen next) diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index cdaba85b9e..ef1e50d2ac 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -2,10 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Online; @@ -17,20 +20,24 @@ using osu.Game.Screens.Multi.Lounge.Components; namespace osu.Game.Screens.Multi { - public class RoomManager : PollingComponent, IRoomManager + public class RoomManager : CompositeDrawable, IRoomManager { public event Action RoomsUpdated; private readonly BindableList rooms = new BindableList(); public IBindableList Rooms => rooms; - private Room joinedRoom; + public double TimeBetweenListingPolls + { + get => listingPollingComponent.TimeBetweenPolls; + set => listingPollingComponent.TimeBetweenPolls = value; + } - [Resolved] - private Bindable currentFilter { get; set; } - - [Resolved] - private IAPIProvider api { get; set; } + public double TimeBetweenSelectionPolls + { + get => selectionPollingComponent.TimeBetweenPolls; + set => selectionPollingComponent.TimeBetweenPolls = value; + } [Resolved] private RulesetStore rulesets { get; set; } @@ -38,14 +45,26 @@ namespace osu.Game.Screens.Multi [Resolved] private BeatmapManager beatmaps { get; set; } - [BackgroundDependencyLoader] - private void load() + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private Bindable selectedRoom { get; set; } + + private readonly ListingPollingComponent listingPollingComponent; + private readonly SelectionPollingComponent selectionPollingComponent; + + private Room joinedRoom; + + public RoomManager() { - currentFilter.BindValueChanged(_ => + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] { - if (IsLoaded) - PollImmediately(); - }); + listingPollingComponent = new ListingPollingComponent { RoomsReceived = onRoomsReceived }, + selectionPollingComponent = new SelectionPollingComponent { RoomReceived = onRoomReceived } + }; } protected override void Dispose(bool isDisposing) @@ -116,45 +135,52 @@ namespace osu.Game.Screens.Multi joinedRoom = null; } - private GetRoomsRequest pollReq; - - protected override Task Poll() + /// + /// Invoked when the listing of all s is received from the server. + /// + /// The listing. + private void onRoomsReceived(List listing) { - if (!api.IsLoggedIn) - return base.Poll(); - - var tcs = new TaskCompletionSource(); - - pollReq?.Cancel(); - pollReq = new GetRoomsRequest(currentFilter.Value.PrimaryFilter); - - pollReq.Success += result => + // Remove past matches + foreach (var r in rooms.ToList()) { - // Remove past matches - foreach (var r in rooms.ToList()) + if (listing.All(e => e.RoomID.Value != r.RoomID.Value)) + rooms.Remove(r); + } + + for (int i = 0; i < listing.Count; i++) + { + if (selectedRoom.Value?.RoomID?.Value == listing[i].RoomID.Value) { - if (result.All(e => e.RoomID.Value != r.RoomID.Value)) - rooms.Remove(r); + // The listing request contains less data than the selection request, so data from the selection request is always preferred while the room is selected. + continue; } - for (int i = 0; i < result.Count; i++) + var r = listing[i]; + r.Position.Value = i; + + update(r, r); + addRoom(r); + } + + RoomsUpdated?.Invoke(); + } + + /// + /// Invoked when a is received from the server. + /// + /// The received . + private void onRoomReceived(Room toUpdate) + { + foreach (var room in rooms) + { + if (room.RoomID.Value == toUpdate.RoomID.Value) { - var r = result[i]; - r.Position.Value = i; - - update(r, r); - addRoom(r); + toUpdate.Position.Value = room.Position.Value; + update(room, toUpdate); + break; } - - RoomsUpdated?.Invoke(); - tcs.SetResult(true); - }; - - pollReq.Failure += _ => tcs.SetResult(false); - - api.Queue(pollReq); - - return tcs.Task; + } } /// @@ -182,5 +208,100 @@ namespace osu.Game.Screens.Multi else existing.CopyFrom(room); } + + private class SelectionPollingComponent : PollingComponent + { + public Action RoomReceived; + + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private Bindable selectedRoom { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + selectedRoom.BindValueChanged(_ => + { + if (IsLoaded) + PollImmediately(); + }); + } + + private GetRoomRequest pollReq; + + protected override Task Poll() + { + if (!api.IsLoggedIn) + return base.Poll(); + + if (selectedRoom.Value?.RoomID.Value == null) + return base.Poll(); + + var tcs = new TaskCompletionSource(); + + pollReq?.Cancel(); + pollReq = new GetRoomRequest(selectedRoom.Value.RoomID.Value.Value); + + pollReq.Success += result => + { + RoomReceived?.Invoke(result); + tcs.SetResult(true); + }; + + pollReq.Failure += _ => tcs.SetResult(false); + + api.Queue(pollReq); + + return tcs.Task; + } + } + + private class ListingPollingComponent : PollingComponent + { + public Action> RoomsReceived; + + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private Bindable currentFilter { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + currentFilter.BindValueChanged(_ => + { + if (IsLoaded) + PollImmediately(); + }); + } + + private GetRoomsRequest pollReq; + + protected override Task Poll() + { + if (!api.IsLoggedIn) + return base.Poll(); + + var tcs = new TaskCompletionSource(); + + pollReq?.Cancel(); + pollReq = new GetRoomsRequest(currentFilter.Value.PrimaryFilter); + + pollReq.Success += result => + { + RoomsReceived?.Invoke(result); + tcs.SetResult(true); + }; + + pollReq.Failure += _ => tcs.SetResult(false); + + api.Queue(pollReq); + + return tcs.Task; + } + } } } From 97c07281d8a1f1b1ba647340425b7ed3bfda6b35 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Feb 2020 19:24:13 +0900 Subject: [PATCH 065/192] Make ParticipantsList use the new participants property --- osu.Game/Online/Multiplayer/Room.cs | 2 +- .../Multi/Components/ParticipantsList.cs | 91 +++++++++---------- 2 files changed, 42 insertions(+), 51 deletions(-) diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index 2bfcc019fa..327f2c75da 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -59,7 +59,7 @@ namespace osu.Game.Online.Multiplayer public Bindable MaxParticipants { get; private set; } = new Bindable(); [Cached] - [JsonIgnore] + [JsonProperty("recent_participants")] public BindableList Participants { get; private set; } = new BindableList(); [Cached] diff --git a/osu.Game/Screens/Multi/Components/ParticipantsList.cs b/osu.Game/Screens/Multi/Components/ParticipantsList.cs index e383e0414b..16ab905322 100644 --- a/osu.Game/Screens/Multi/Components/ParticipantsList.cs +++ b/osu.Game/Screens/Multi/Components/ParticipantsList.cs @@ -7,9 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; using osu.Game.Graphics; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Users; using osu.Game.Users.Drawables; using osuTK; @@ -46,73 +45,69 @@ namespace osu.Game.Screens.Multi.Components set => fill.Direction = value; } - private readonly FillFlowContainer fill; + private readonly FillFlowContainer fill; public ParticipantsList() { - InternalChild = fill = new FillFlowContainer { Spacing = new Vector2(10) }; + InternalChild = fill = new FillFlowContainer { Spacing = new Vector2(10) }; } [BackgroundDependencyLoader] private void load() { - RoomID.BindValueChanged(_ => updateParticipants(), true); + Participants.CollectionChanged += (_, __) => updateParticipants(); + updateParticipants(); } - [Resolved] - private IAPIProvider api { get; set; } - - private GetRoomScoresRequest request; + private ScheduledDelegate scheduledUpdate; private void updateParticipants() { - var roomId = RoomID.Value ?? 0; - - request?.Cancel(); - - // nice little progressive fade - int time = 500; - - foreach (var c in fill.Children) + scheduledUpdate?.Cancel(); + scheduledUpdate = Schedule(() => { - c.Delay(500 - time).FadeOut(time, Easing.Out); - time = Math.Max(20, time - 20); - c.Expire(); - } + // Remove all extra tiles with a nice, progressive fade + int time = 500; - if (roomId == 0) return; + for (int i = Participants.Count; i < fill.Count; i++) + { + var tile = fill[i]; - request = new GetRoomScoresRequest(roomId); - request.Success += scores => Schedule(() => - { - if (roomId != RoomID.Value) - return; + tile.Delay(500 - time).FadeOut(time, Easing.Out); + time = Math.Max(20, time - 20); + tile.Expire(); + } - fill.Clear(); - foreach (var s in scores) - fill.Add(new UserTile(s.User)); + // Add new tiles for all new players + for (int i = fill.Count; i < Participants.Count; i++) + { + var tile = new UserTile(); + fill.Add(tile); - fill.FadeInFromZero(1000, Easing.OutQuint); + tile.ClearTransforms(); + tile.LifetimeEnd = double.MaxValue; + tile.FadeInFromZero(250, Easing.OutQuint); + } + + for (int i = 0; i < Participants.Count; i++) + fill[i].User = Participants[i]; }); - - api.Queue(request); - } - - protected override void Dispose(bool isDisposing) - { - request?.Cancel(); - base.Dispose(isDisposing); } private class UserTile : CompositeDrawable, IHasTooltip { - private readonly User user; - - public string TooltipText => user.Username; - - public UserTile(User user) + public User User + { + get => avatar.User; + set => avatar.User = value; + } + + public string TooltipText => User?.Username ?? string.Empty; + + private readonly UpdateableAvatar avatar; + + public UserTile() { - this.user = user; Size = new Vector2(TILE_SIZE); CornerRadius = 5f; Masking = true; @@ -124,11 +119,7 @@ namespace osu.Game.Screens.Multi.Components RelativeSizeAxes = Axes.Both, Colour = OsuColour.FromHex(@"27252d"), }, - new UpdateableAvatar - { - RelativeSizeAxes = Axes.Both, - User = user, - }, + avatar = new UpdateableAvatar { RelativeSizeAxes = Axes.Both }, }; } } From fe10a64137ec868a5e97fa087b79f2787d5553de Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Feb 2020 19:37:04 +0900 Subject: [PATCH 066/192] Improve participants list transforms --- .../Multi/Components/ParticipantsList.cs | 61 +++++++++---------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/osu.Game/Screens/Multi/Components/ParticipantsList.cs b/osu.Game/Screens/Multi/Components/ParticipantsList.cs index 16ab905322..13de0202e1 100644 --- a/osu.Game/Screens/Multi/Components/ParticipantsList.cs +++ b/osu.Game/Screens/Multi/Components/ParticipantsList.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -25,7 +24,9 @@ namespace osu.Game.Screens.Multi.Components set { base.RelativeSizeAxes = value; - fill.RelativeSizeAxes = value; + + if (tiles != null) + tiles.RelativeSizeAxes = value; } } @@ -35,21 +36,24 @@ namespace osu.Game.Screens.Multi.Components set { base.AutoSizeAxes = value; - fill.AutoSizeAxes = value; + + if (tiles != null) + tiles.AutoSizeAxes = value; } } + private FillDirection direction = FillDirection.Full; + public FillDirection Direction { - get => fill.Direction; - set => fill.Direction = value; - } + get => direction; + set + { + direction = value; - private readonly FillFlowContainer fill; - - public ParticipantsList() - { - InternalChild = fill = new FillFlowContainer { Spacing = new Vector2(10) }; + if (tiles != null) + tiles.Direction = value; + } } [BackgroundDependencyLoader] @@ -60,37 +64,30 @@ namespace osu.Game.Screens.Multi.Components } private ScheduledDelegate scheduledUpdate; + private FillFlowContainer tiles; private void updateParticipants() { scheduledUpdate?.Cancel(); scheduledUpdate = Schedule(() => { - // Remove all extra tiles with a nice, progressive fade - int time = 500; + tiles?.FadeOut(250, Easing.Out).Expire(); - for (int i = Participants.Count; i < fill.Count; i++) + tiles = new FillFlowContainer { - var tile = fill[i]; - - tile.Delay(500 - time).FadeOut(time, Easing.Out); - time = Math.Max(20, time - 20); - tile.Expire(); - } - - // Add new tiles for all new players - for (int i = fill.Count; i < Participants.Count; i++) - { - var tile = new UserTile(); - fill.Add(tile); - - tile.ClearTransforms(); - tile.LifetimeEnd = double.MaxValue; - tile.FadeInFromZero(250, Easing.OutQuint); - } + Alpha = 0, + Direction = Direction, + AutoSizeAxes = AutoSizeAxes, + RelativeSizeAxes = RelativeSizeAxes, + Spacing = new Vector2(10) + }; for (int i = 0; i < Participants.Count; i++) - fill[i].User = Participants[i]; + tiles.Add(new UserTile { User = Participants[i] }); + + AddInternal(tiles); + + tiles.Delay(250).FadeIn(250, Easing.OutQuint); }); } From 22862256c1fc519bc5ae5150501b5edcdeca1446 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Feb 2020 19:42:19 +0900 Subject: [PATCH 067/192] Increase time between polls --- osu.Game/Screens/Multi/Multiplayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 20b1f860a9..b0d773869a 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -180,7 +180,7 @@ namespace osu.Game.Screens.Multi { case LoungeSubScreen _: roomManager.TimeBetweenListingPolls = idle ? 120000 : 15000; - roomManager.TimeBetweenSelectionPolls = idle ? 30000 : 5000; + roomManager.TimeBetweenSelectionPolls = idle ? 120000 : 15000; break; case MatchSubScreen _: From 085968dd7fe8b96d71d0c55b6dbebc932d94e63b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Feb 2020 19:42:28 +0900 Subject: [PATCH 068/192] Rename recent participants --- osu.Game/Online/Multiplayer/Room.cs | 8 ++++---- .../Screens/Multi/Components/OverlinedParticipants.cs | 2 +- osu.Game/Screens/Multi/Components/ParticipantsList.cs | 6 +++--- osu.Game/Screens/Multi/MultiplayerComposite.cs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index 327f2c75da..fd700bff0b 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -60,7 +60,7 @@ namespace osu.Game.Online.Multiplayer [Cached] [JsonProperty("recent_participants")] - public BindableList Participants { get; private set; } = new BindableList(); + public BindableList RecentParticipants { get; private set; } = new BindableList(); [Cached] public Bindable ParticipantCount { get; private set; } = new Bindable(); @@ -133,10 +133,10 @@ namespace osu.Game.Online.Multiplayer Playlist.AddRange(other.Playlist); } - if (!Participants.SequenceEqual(other.Participants)) + if (!RecentParticipants.SequenceEqual(other.RecentParticipants)) { - Participants.Clear(); - Participants.AddRange(other.Participants); + RecentParticipants.Clear(); + RecentParticipants.AddRange(other.RecentParticipants); } Position = other.Position; diff --git a/osu.Game/Screens/Multi/Components/OverlinedParticipants.cs b/osu.Game/Screens/Multi/Components/OverlinedParticipants.cs index a709c6a57a..eb1782d147 100644 --- a/osu.Game/Screens/Multi/Components/OverlinedParticipants.cs +++ b/osu.Game/Screens/Multi/Components/OverlinedParticipants.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Multi.Components } public OverlinedParticipants(Direction direction) - : base("Participants") + : base("Recent participants") { OsuScrollContainer scroll; ParticipantsList list; diff --git a/osu.Game/Screens/Multi/Components/ParticipantsList.cs b/osu.Game/Screens/Multi/Components/ParticipantsList.cs index 13de0202e1..5a2dc19b66 100644 --- a/osu.Game/Screens/Multi/Components/ParticipantsList.cs +++ b/osu.Game/Screens/Multi/Components/ParticipantsList.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Multi.Components [BackgroundDependencyLoader] private void load() { - Participants.CollectionChanged += (_, __) => updateParticipants(); + RecentParticipants.CollectionChanged += (_, __) => updateParticipants(); updateParticipants(); } @@ -82,8 +82,8 @@ namespace osu.Game.Screens.Multi.Components Spacing = new Vector2(10) }; - for (int i = 0; i < Participants.Count; i++) - tiles.Add(new UserTile { User = Participants[i] }); + for (int i = 0; i < RecentParticipants.Count; i++) + tiles.Add(new UserTile { User = RecentParticipants[i] }); AddInternal(tiles); diff --git a/osu.Game/Screens/Multi/MultiplayerComposite.cs b/osu.Game/Screens/Multi/MultiplayerComposite.cs index 3f048eceab..e612e77748 100644 --- a/osu.Game/Screens/Multi/MultiplayerComposite.cs +++ b/osu.Game/Screens/Multi/MultiplayerComposite.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Multi protected BindableList Playlist { get; private set; } [Resolved(typeof(Room))] - protected BindableList Participants { get; private set; } + protected BindableList RecentParticipants { get; private set; } [Resolved(typeof(Room))] protected Bindable ParticipantCount { get; private set; } From dd2bd5c19dd5ab57c01953cfdc029ce713e28678 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Feb 2020 20:01:23 +0900 Subject: [PATCH 069/192] Add delay for loading multiplayer beatmap covers --- .../Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index 30346a8a96..eb05cbaf85 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -16,6 +16,8 @@ namespace osu.Game.Beatmaps.Drawables { public readonly Bindable Beatmap = new Bindable(); + protected override double LoadDelay => 500; + [Resolved] private BeatmapManager beatmaps { get; set; } From b3220476d73db0798f1d583048c18481c7f1d8df Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Feb 2020 20:05:12 +0900 Subject: [PATCH 070/192] Rename methods --- osu.Game/Screens/Multi/RoomManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index ef1e50d2ac..ad461af57f 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -62,8 +62,8 @@ namespace osu.Game.Screens.Multi InternalChildren = new Drawable[] { - listingPollingComponent = new ListingPollingComponent { RoomsReceived = onRoomsReceived }, - selectionPollingComponent = new SelectionPollingComponent { RoomReceived = onRoomReceived } + listingPollingComponent = new ListingPollingComponent { RoomsReceived = onListingReceived }, + selectionPollingComponent = new SelectionPollingComponent { RoomReceived = onSelectedRoomReceived } }; } @@ -139,7 +139,7 @@ namespace osu.Game.Screens.Multi /// Invoked when the listing of all s is received from the server. /// /// The listing. - private void onRoomsReceived(List listing) + private void onListingReceived(List listing) { // Remove past matches foreach (var r in rooms.ToList()) @@ -170,7 +170,7 @@ namespace osu.Game.Screens.Multi /// Invoked when a is received from the server. /// /// The received . - private void onRoomReceived(Room toUpdate) + private void onSelectedRoomReceived(Room toUpdate) { foreach (var room in rooms) { From 32dc4501c189bf10e3f22bd86aa31ab2fbaa751e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 27 Feb 2020 14:16:35 +0300 Subject: [PATCH 071/192] Fix incorrect RepliesButton presentation --- osu.Game/Overlays/Comments/DrawableComment.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index cb9e32f1ad..46f600615a 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -384,7 +384,7 @@ namespace osu.Game.Overlays.Comments protected override void OnExpandedChanged(ValueChangedEvent expanded) { - text.Text = $@"{(expanded.NewValue ? "[+]" : "[-]")} replies ({count})"; + text.Text = $@"{(expanded.NewValue ? "[-]" : "[+]")} replies ({count})"; } } From d92e93ed3168502ef5e449343e40a4d4f5510054 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 27 Feb 2020 14:39:10 +0300 Subject: [PATCH 072/192] Move background creation out from UpdateStreamBadgeArea --- osu.Game/Overlays/Changelog/ChangelogHeader.cs | 14 ++++++++++++++ .../Overlays/Changelog/UpdateStreamBadgeArea.cs | 11 +---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index 8663ec586b..09bcd62021 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -4,9 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; +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.Requests.Responses; @@ -22,6 +24,8 @@ namespace osu.Game.Overlays.Changelog private const string listing_string = "listing"; + private Box streamsBackground; + public ChangelogHeader() { TabControl.AddItem(listing_string); @@ -40,6 +44,12 @@ namespace osu.Game.Overlays.Changelog }; } + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + streamsBackground.Colour = colourProvider.Background5; + } + private ChangelogHeaderTitle title; private void showBuild(ValueChangedEvent e) @@ -72,6 +82,10 @@ namespace osu.Game.Overlays.Changelog AutoSizeAxes = Axes.Y, Children = new Drawable[] { + streamsBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, Streams = new UpdateStreamBadgeArea(), } }; diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs index 639c0d9780..faff7381c9 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs @@ -6,25 +6,16 @@ using osu.Framework.Input.Events; using osu.Game.Online.API.Requests.Responses; using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Allocation; namespace osu.Game.Overlays.Changelog { public class UpdateStreamBadgeArea : TabControl { - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + public UpdateStreamBadgeArea() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - - AddInternal(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background5, - }); } public void Populate(List streams) From a8c31c31add957afa4dd5f5c916115baeed094c2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 27 Feb 2020 14:47:31 +0300 Subject: [PATCH 073/192] Move padding outside of the UpdateStreamBadgeArea --- .../Overlays/Changelog/ChangelogHeader.cs | 12 +++++++++++- .../Changelog/UpdateStreamBadgeArea.cs | 19 +++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index 09bcd62021..f5b10ef0f2 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -86,7 +86,17 @@ namespace osu.Game.Overlays.Changelog { RelativeSizeAxes = Axes.Both }, - Streams = new UpdateStreamBadgeArea(), + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Horizontal = 85, + Vertical = 20 + }, + Child = Streams = new UpdateStreamBadgeArea() + } } }; diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs index faff7381c9..d937bf775a 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs @@ -40,21 +40,12 @@ namespace osu.Game.Overlays.Changelog base.OnHoverLost(e); } - protected override TabFillFlowContainer CreateTabFlow() + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer { - var flow = base.CreateTabFlow(); - - flow.RelativeSizeAxes = Axes.X; - flow.AutoSizeAxes = Axes.Y; - flow.AllowMultiline = true; - flow.Padding = new MarginPadding - { - Vertical = 20, - Horizontal = 85, - }; - - return flow; - } + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + AllowMultiline = true, + }; protected override Dropdown CreateDropdown() => null; From f9aa6b9c07a99d5272302d8835b9a797c2277d0a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 27 Feb 2020 15:33:01 +0300 Subject: [PATCH 074/192] Remove fadeContainer and adjust fade condition --- .../Overlays/Changelog/UpdateStreamBadge.cs | 69 +++++++++---------- 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs index 10aca31441..dc9d3ccaed 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs @@ -26,7 +26,6 @@ namespace osu.Game.Overlays.Changelog private readonly APIUpdateStream stream; - private Container fadeContainer; private FillFlowContainer text; private ExpandingBar expandingBar; @@ -44,47 +43,39 @@ namespace osu.Game.Overlays.Changelog AddRange(new Drawable[] { - fadeContainer = new Container + text = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Margin = new MarginPadding { Top = 6 }, + Children = new[] { - text = new FillFlowContainer + new OsuSpriteText { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Margin = new MarginPadding { Top = 6 }, - Children = new[] - { - new OsuSpriteText - { - Text = stream.DisplayName, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black), - }, - new OsuSpriteText - { - Text = stream.LatestBuild.DisplayVersion, - Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), - }, - new OsuSpriteText - { - Text = stream.LatestBuild.Users > 0 ? $"{"user".ToQuantity(stream.LatestBuild.Users, "N0")} online" : null, - Font = OsuFont.GetFont(size: 10), - Colour = colourProvider.Foreground1 - }, - } + Text = stream.DisplayName, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black), }, - expandingBar = new ExpandingBar + new OsuSpriteText { - Anchor = Anchor.TopCentre, - Colour = stream.Colour, - ExpandedSize = 4, - CollapsedSize = 2, - IsCollapsed = true + Text = stream.LatestBuild.DisplayVersion, + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), + }, + new OsuSpriteText + { + Text = stream.LatestBuild.Users > 0 ? $"{"user".ToQuantity(stream.LatestBuild.Users, "N0")} online" : null, + Font = OsuFont.GetFont(size: 10), + Colour = colourProvider.Foreground1 }, } }, + expandingBar = new ExpandingBar + { + Anchor = Anchor.TopCentre, + Colour = stream.Colour, + ExpandedSize = 4, + CollapsedSize = 2, + IsCollapsed = true + }, new HoverClickSounds() }); @@ -112,21 +103,23 @@ namespace osu.Game.Overlays.Changelog // Expand based on the local state bool shouldExpand = Active.Value || IsHovered; + bool allHighlighted = SelectedTab.Value == null && !externalDimRequested; + // Expand based on whether no build is selected and the badge area is hovered - shouldExpand |= SelectedTab.Value == null && !externalDimRequested; + shouldExpand |= allHighlighted; if (shouldExpand) { expandingBar.Expand(); - fadeContainer.FadeTo(1, transition_duration); + expandingBar.FadeTo(1, transition_duration, Easing.OutQuint); } else { expandingBar.Collapse(); - fadeContainer.FadeTo(0.5f, transition_duration); + expandingBar.FadeTo(0.5f, transition_duration, Easing.OutQuint); } - text.FadeTo(externalDimRequested && !IsHovered ? 0.5f : 1, transition_duration); + text.FadeTo(IsHovered || (Active.Value && !externalDimRequested) || allHighlighted ? 1 : 0.5f, transition_duration, Easing.OutQuint); } private bool externalDimRequested; From f486db48de22a4638c1eb1d00c1cb3d5ae27caaf Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Thu, 27 Feb 2020 17:30:06 -0800 Subject: [PATCH 075/192] Add labels and fix dropdown bindable values --- osu.Game/Configuration/SettingSourceAttribute.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 4bdbb5fc24..fe487cb1d0 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -8,7 +8,6 @@ using System.Reflection; using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.UserInterface; using osu.Game.Overlays.Settings; namespace osu.Game.Configuration @@ -105,7 +104,8 @@ namespace osu.Game.Configuration var dropdownType = typeof(SettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]); var dropdown = (Drawable)Activator.CreateInstance(dropdownType); - dropdown.GetType().GetProperty(nameof(IHasCurrentValue.Current))?.SetValue(dropdown, obj); + dropdownType.GetProperty(nameof(SettingsDropdown.LabelText))?.SetValue(dropdown, attr.Label); + dropdownType.GetProperty(nameof(SettingsDropdown.Bindable))?.SetValue(dropdown, bindable); yield return dropdown; From 377ae3e685466fa1dd4c2519f102a29a8f7336fc Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 28 Feb 2020 12:48:06 +0300 Subject: [PATCH 076/192] Make a separate if section for all highlighted case --- .../Overlays/Changelog/UpdateStreamBadge.cs | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs index dc9d3ccaed..5e10410f37 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs @@ -100,26 +100,20 @@ namespace osu.Game.Overlays.Changelog private void updateState() { - // Expand based on the local state - bool shouldExpand = Active.Value || IsHovered; - - bool allHighlighted = SelectedTab.Value == null && !externalDimRequested; - - // Expand based on whether no build is selected and the badge area is hovered - shouldExpand |= allHighlighted; - - if (shouldExpand) + if (SelectedTab.Value == null && !externalDimRequested) { expandingBar.Expand(); expandingBar.FadeTo(1, transition_duration, Easing.OutQuint); - } - else - { - expandingBar.Collapse(); - expandingBar.FadeTo(0.5f, transition_duration, Easing.OutQuint); + text.FadeTo(1, transition_duration, Easing.OutQuint); + return; } - text.FadeTo(IsHovered || (Active.Value && !externalDimRequested) || allHighlighted ? 1 : 0.5f, transition_duration, Easing.OutQuint); + var shouldExpand = Active.Value || IsHovered; + + expandingBar.IsCollapsed = !shouldExpand; + expandingBar.FadeTo(shouldExpand ? 1 : 0.5f, transition_duration, Easing.OutQuint); + + text.FadeTo(IsHovered || (Active.Value && !externalDimRequested) ? 1 : 0.5f, transition_duration, Easing.OutQuint); } private bool externalDimRequested; From 7b9937b851bebad05ba6f9b06cee7759f24f48f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Feb 2020 22:15:39 +0900 Subject: [PATCH 077/192] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 28fbdb3367..2a6bfa0f88 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c5ebf0f712..ca62a959d9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index abd562dc81..11e7991dfa 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + @@ -79,7 +79,7 @@ - + From 4ad2d0cfb606835a9fe82ddb3b22181588e04e33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Feb 2020 16:07:08 +0900 Subject: [PATCH 078/192] Remove deprecated debug setting --- osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs index 457f064f89..9edb18e065 100644 --- a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs @@ -22,11 +22,6 @@ namespace osu.Game.Overlays.Settings.Sections.Debug Bindable = frameworkConfig.GetBindable(FrameworkSetting.ShowLogOverlay) }, new SettingsCheckbox - { - LabelText = "Performance logging", - Bindable = config.GetBindable(DebugSetting.PerformanceLogging) - }, - new SettingsCheckbox { LabelText = "Bypass front-to-back render pass", Bindable = config.GetBindable(DebugSetting.BypassFrontToBackPass) From 394b88aa65d704cf602fb6f7bae4e2f6d0f95640 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Feb 2020 19:11:18 +0900 Subject: [PATCH 079/192] Add thread mode dropdown --- .../Overlays/Settings/Sections/Graphics/RendererSettings.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index 7317076c54..69ff9b43e5 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Platform; using osu.Game.Configuration; namespace osu.Game.Overlays.Settings.Sections.Graphics @@ -24,6 +25,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics LabelText = "Frame limiter", Bindable = config.GetBindable(FrameworkSetting.FrameSync) }, + new SettingsEnumDropdown + { + LabelText = "Threading mode", + Bindable = config.GetBindable(FrameworkSetting.ExecutionMode) + }, new SettingsCheckbox { LabelText = "Show FPS", From 036f155afe755d961c99fa891cc3b9095b733cf6 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Fri, 28 Feb 2020 21:09:31 +0100 Subject: [PATCH 080/192] Adjust colours in DrawableMostPlayedBeatmap --- .../Historical/DrawableMostPlayedBeatmap.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index e75ad2f161..dc8492562a 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -37,8 +37,10 @@ namespace osu.Game.Overlays.Profile.Sections.Historical } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, OverlayColourProvider colourProvider) { + ProfileItemContainer container; + AddRangeInternal(new Drawable[] { new UpdateableBeatmapSetCover @@ -61,7 +63,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical CornerRadius = corner_radius, Children = new Drawable[] { - new ProfileItemContainer + container = new ProfileItemContainer { Child = new Container { @@ -78,11 +80,14 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Children = new Drawable[] { new MostPlayedBeatmapMetadataContainer(beatmap), - new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular)) + new LinkFlowContainer(t => + { + t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular); + t.Colour = colourProvider.Foreground1; + }) { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Colour = colours.GreySeafoamLighter }.With(d => { d.AddText("mapped by "); @@ -103,6 +108,9 @@ namespace osu.Game.Overlays.Profile.Sections.Historical } } }); + + container.IdleColour = colourProvider.Background4; + container.HoverColour = colourProvider.Background3; } private class MostPlayedBeatmapMetadataContainer : BeatmapMetadataContainer From 5838af39c1a20cc8bc1dc5fc6aab93c4dc82403f Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Fri, 28 Feb 2020 21:09:49 +0100 Subject: [PATCH 081/192] Add background colour customization to ProfileItemContainer --- .../Profile/Sections/ProfileItemContainer.cs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs b/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs index f65c909155..a48f21f52b 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs @@ -16,12 +16,19 @@ namespace osu.Game.Overlays.Profile.Sections protected override Container Content => content; - private Color4 idleColour; - private Color4 hoverColour; - private readonly Box background; private readonly Container content; + private Color4 idleColour; + + public Color4 IdleColour + { + get => idleColour; + set => idleColour = background.Colour = value; + } + + public Color4 HoverColour { get; set; } + public ProfileItemContainer() { RelativeSizeAxes = Axes.Both; @@ -44,20 +51,20 @@ namespace osu.Game.Overlays.Profile.Sections [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - background.Colour = idleColour = colourProvider.Background3; - hoverColour = colourProvider.Background2; + IdleColour = colourProvider.Background3; + HoverColour = colourProvider.Background2; } protected override bool OnHover(HoverEvent e) { - background.FadeColour(hoverColour, hover_duration, Easing.OutQuint); + background.FadeColour(HoverColour, hover_duration, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - background.FadeColour(idleColour, hover_duration, Easing.OutQuint); + background.FadeColour(IdleColour, hover_duration, Easing.OutQuint); } } } From 5dff7f0955a9108290277a971e0b5c7b2b73266a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 29 Feb 2020 02:21:52 +0300 Subject: [PATCH 082/192] Adjust horizontal padding --- osu.Game/Overlays/Changelog/ChangelogHeader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index f5b10ef0f2..dcadbf4cf5 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Changelog AutoSizeAxes = Axes.Y, Padding = new MarginPadding { - Horizontal = 85, + Horizontal = 65, Vertical = 20 }, Child = Streams = new UpdateStreamBadgeArea() From f18a1cde53e840b35646a5e946a8218f0d9b5507 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Feb 2020 08:39:27 +0900 Subject: [PATCH 083/192] Fix crash when reaching results screen on single threaded execution mode --- osu.Game/Screens/Play/Player.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index bd43b23a9f..11ca36e25f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -609,9 +609,9 @@ namespace osu.Game.Screens.Play { var score = CreateScore(); if (DrawableRuleset.ReplayScore == null) - scoreManager.Import(score).Wait(); - - this.Push(CreateResults(score)); + scoreManager.Import(score).ContinueWith(_ => Schedule(() => this.Push(CreateResults(score)))); + else + this.Push(CreateResults(score)); }); } From a332b6980bc1dc250a6effcb4fa5e18b15f40ac0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Feb 2020 14:28:26 +0900 Subject: [PATCH 084/192] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 2a6bfa0f88..651d1beda1 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ca62a959d9..168a358e47 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 11e7991dfa..3c2a67e908 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + @@ -79,7 +79,7 @@ - + From bca58ddb42f73cc79116b1450c4db446bd015577 Mon Sep 17 00:00:00 2001 From: naoey Date: Sat, 29 Feb 2020 21:07:42 +0530 Subject: [PATCH 085/192] Make KeyCounter stop counting during breaks --- .../Visual/Gameplay/TestSceneAutoplay.cs | 16 ++++++++++++---- osu.Game/Screens/Play/Player.cs | 10 ++++++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 4daab8d137..ebd89039a5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -24,10 +24,17 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void AddCheckSteps() { - AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0); - AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2)); - AddStep("rewind", () => track.Seek(-10000)); - AddUntilStep("key counter reset", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); + ScoreAccessiblePlayer scoreAccessiblePlayer = null; + + AddUntilStep("player loaded", () => (scoreAccessiblePlayer = (ScoreAccessiblePlayer)Player) != null); + AddUntilStep("score above zero", () => scoreAccessiblePlayer.ScoreProcessor.TotalScore.Value > 0); + AddUntilStep("key counter counted keys", () => scoreAccessiblePlayer.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2)); + AddStep("seek to break time", () => scoreAccessiblePlayer.GameplayClockContainer.Seek(scoreAccessiblePlayer.BreakOverlay.Breaks.First().StartTime)); + AddUntilStep("wait for seek to complete", () => + scoreAccessiblePlayer.HUDOverlay.Progress.ReferenceClock.CurrentTime >= scoreAccessiblePlayer.BreakOverlay.Breaks.First().StartTime); + AddAssert("test keys not counting", () => !scoreAccessiblePlayer.HUDOverlay.KeyCounter.IsCounting); + AddStep("rewind", () => scoreAccessiblePlayer.GameplayClockContainer.Seek(-80000)); + AddUntilStep("key counter reset", () => scoreAccessiblePlayer.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); } protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) @@ -43,6 +50,7 @@ namespace osu.Game.Tests.Visual.Gameplay { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new HUDOverlay HUDOverlay => base.HUDOverlay; + public new BreakOverlay BreakOverlay => base.BreakOverlay; public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 11ca36e25f..237e364e69 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -184,7 +184,7 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToHealthProcessor(HealthProcessor); - BreakOverlay.IsBreakTime.ValueChanged += _ => updatePauseOnFocusLostState(); + BreakOverlay.IsBreakTime.BindValueChanged(_ => onBreakTimeChanged(), true); } private void addUnderlayComponents(Container target) @@ -229,7 +229,7 @@ namespace osu.Game.Screens.Play IsPaused = { BindTarget = GameplayClockContainer.IsPaused } }, PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, - KeyCounter = { AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded } }, + KeyCounter = { AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded }, IsCounting = false }, RequestSeek = GameplayClockContainer.Seek, Anchor = Anchor.Centre, Origin = Anchor.Centre @@ -286,6 +286,12 @@ namespace osu.Game.Screens.Play HealthProcessor.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); } + private void onBreakTimeChanged() + { + updatePauseOnFocusLostState(); + HUDOverlay.KeyCounter.IsCounting = !BreakOverlay.IsBreakTime.Value; + } + private void updatePauseOnFocusLostState() => HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost && !DrawableRuleset.HasReplayLoaded.Value From 1ce972dd5b4a06d7ca6774a7daaf79cf3902dc69 Mon Sep 17 00:00:00 2001 From: naoey Date: Sat, 29 Feb 2020 21:53:49 +0530 Subject: [PATCH 086/192] Remove unused variable --- osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index ebd89039a5..8e16d74dca 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -14,8 +14,6 @@ namespace osu.Game.Tests.Visual.Gameplay [Description("Player instantiated with an autoplay mod.")] public class TestSceneAutoplay : TestSceneAllRulesetPlayers { - private ClockBackedTestWorkingBeatmap.TrackVirtualManual track; - protected override Player CreatePlayer(Ruleset ruleset) { SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); @@ -41,8 +39,6 @@ namespace osu.Game.Tests.Visual.Gameplay { var working = base.CreateWorkingBeatmap(beatmap, storyboard); - track = (ClockBackedTestWorkingBeatmap.TrackVirtualManual)working.Track; - return working; } From b9fef4f715717851998ec403582e46e6bf62207e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 1 Mar 2020 09:26:16 +0900 Subject: [PATCH 087/192] Update link location for appimage --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae57b1d954..77c7eb9d2d 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ If you are looking to install or test osu! without setting up a development envi **Latest build:** -| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.x86_64.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) +| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | ------------- | ------------- | ------------- | ------------- | ------------- | - When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/dependencies?tabs=netcore31&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs. From 089ec4c7922a18fe4f4da718bbc4ede63ddf6289 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sat, 29 Feb 2020 21:16:28 -0800 Subject: [PATCH 088/192] Test scene for mod development --- .../TestSceneCatchModSandbox.cs | 28 +++++ .../TestSceneManiaModSandbox.cs | 28 +++++ .../Mods/TestSceneOsuModDifficultyAdjust.cs | 20 ++++ .../TestSceneOsuModSandbox.cs | 28 +++++ .../TestSceneTaikoModSandbox.cs | 28 +++++ osu.Game/Tests/Visual/PlayerTestScene.cs | 4 +- osu.Game/Tests/Visual/TestSceneModSandbox.cs | 108 ++++++++++++++++++ 7 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneCatchModSandbox.cs create mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneManiaModSandbox.cs create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneOsuModSandbox.cs create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoModSandbox.cs create mode 100644 osu.Game/Tests/Visual/TestSceneModSandbox.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModSandbox.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModSandbox.cs new file mode 100644 index 0000000000..3abf8163bd --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModSandbox.cs @@ -0,0 +1,28 @@ +// 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.Game.Rulesets.Mods; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestSceneCatchModSandbox : TestSceneModSandbox + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(TestSceneCatchModSandbox)).ToList(); + + public TestSceneCatchModSandbox() + : this(null) + { + } + + public TestSceneCatchModSandbox(Mod mod = null) + : base(new CatchRuleset(), mod) + { + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaModSandbox.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaModSandbox.cs new file mode 100644 index 0000000000..2693cebb43 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaModSandbox.cs @@ -0,0 +1,28 @@ +// 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.Game.Rulesets.Mods; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [TestFixture] + public class TestSceneManiaModSandbox : TestSceneModSandbox + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(TestSceneManiaModSandbox)).ToList(); + + public TestSceneManiaModSandbox() + : this(null) + { + } + + public TestSceneManiaModSandbox(Mod mod = null) + : base(new ManiaRuleset(), mod) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs new file mode 100644 index 0000000000..7f09731a11 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -0,0 +1,20 @@ +// 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.Rulesets.Osu.Mods; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModDifficultyAdjust : TestSceneOsuModSandbox + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(OsuModDifficultyAdjust)).ToList(); + + public TestSceneOsuModDifficultyAdjust() + : base(new OsuModDifficultyAdjust()) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuModSandbox.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuModSandbox.cs new file mode 100644 index 0000000000..d2a9d1ea6e --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuModSandbox.cs @@ -0,0 +1,28 @@ +// 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.Game.Rulesets.Mods; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestSceneOsuModSandbox : TestSceneModSandbox + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(TestSceneOsuModSandbox)).ToList(); + + public TestSceneOsuModSandbox() + : this(null) + { + } + + public TestSceneOsuModSandbox(Mod mod = null) + : base(new OsuRuleset(), mod) + { + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoModSandbox.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoModSandbox.cs new file mode 100644 index 0000000000..f5481713f5 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoModSandbox.cs @@ -0,0 +1,28 @@ +// 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.Game.Rulesets.Mods; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class TestSceneTaikoModSandbox : TestSceneModSandbox + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(TestSceneTaikoModSandbox)).ToList(); + + public TestSceneTaikoModSandbox() + : this(null) + { + } + + public TestSceneTaikoModSandbox(Mod mod = null) + : base(new TaikoRuleset(), mod) + { + } + } +} diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 7c5ba7d30f..1ca5256353 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual { base.SetUpSteps(); - AddStep(ruleset.RulesetInfo.Name, loadPlayer); + AddStep(ruleset.RulesetInfo.Name, LoadPlayer); AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); } @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual protected virtual bool Autoplay => false; - private void loadPlayer() + protected void LoadPlayer() { var beatmap = CreateBeatmap(ruleset.RulesetInfo); diff --git a/osu.Game/Tests/Visual/TestSceneModSandbox.cs b/osu.Game/Tests/Visual/TestSceneModSandbox.cs new file mode 100644 index 0000000000..5c32ebadce --- /dev/null +++ b/osu.Game/Tests/Visual/TestSceneModSandbox.cs @@ -0,0 +1,108 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + public abstract class TestSceneModSandbox : PlayerTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(TestSceneModSandbox) + }; + + protected Mod Mod; + private readonly TriangleButton button; + + protected TestSceneModSandbox(Ruleset ruleset, Mod mod = null) + : base(ruleset) + { + Mod = mod ?? new SandboxMod(); + + var props = Mod.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance); + var hasSettings = props.Any(prop => prop.GetCustomAttribute(true) != null); + + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(50), + Margin = new MarginPadding { Bottom = 20 }, + Width = 0.4f, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Children = new Drawable[] + { + new ModControlSection(Mod, Mod.CreateSettingsControls()), + button = new TriangleButton + { + RelativeSizeAxes = Axes.X, + Width = 0.5f, + Text = "Start", + Action = () => + { + button.Text = hasSettings ? "Apply Settings" : "Restart"; + LoadPlayer(); + } + } + } + }; + } + + [SetUpSteps] + public override void SetUpSteps() + { + } + + [BackgroundDependencyLoader] + private void load() + { + LocalConfig.GetBindable(OsuSetting.KeyOverlay).Value = true; + } + + protected override Player CreatePlayer(Ruleset ruleset) + { + SelectedMods.Value = SelectedMods.Value.Append(Mod).ToArray(); + + return base.CreatePlayer(ruleset); + } + + protected class SandboxMod : Mod + { + public override string Name => "Sandbox Test"; + public override string Acronym => "ST"; + public override double ScoreMultiplier => 1.0; + + [SettingSource("Test Setting")] + public Bindable TestSetting1 { get; } = new BindableBool + { + Default = true, + Value = true + }; + + [SettingSource("Test Setting 2")] + public Bindable TestSetting2 { get; } = new BindableFloat + { + Precision = 0.1f, + MinValue = 0, + MaxValue = 20 + }; + } + } +} From a02c5710ac07a2486be020a7b0e2d1d4feb035b1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Mar 2020 10:06:49 +0900 Subject: [PATCH 089/192] Rename base class --- osu.Game.Rulesets.Catch.Tests/TestSceneCatchModSandbox.cs | 2 +- osu.Game.Rulesets.Mania.Tests/TestSceneManiaModSandbox.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneOsuModSandbox.cs | 2 +- osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoModSandbox.cs | 2 +- .../{TestSceneModSandbox.cs => ModSandboxTestScene.cs} | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) rename osu.Game/Tests/Visual/{TestSceneModSandbox.cs => ModSandboxTestScene.cs} (95%) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModSandbox.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModSandbox.cs index 3abf8163bd..3e94121fd7 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModSandbox.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModSandbox.cs @@ -11,7 +11,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestSceneCatchModSandbox : TestSceneModSandbox + public class TestSceneCatchModSandbox : ModSandboxTestScene { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(TestSceneCatchModSandbox)).ToList(); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaModSandbox.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaModSandbox.cs index 2693cebb43..897e7df1e9 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaModSandbox.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaModSandbox.cs @@ -11,7 +11,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] - public class TestSceneManiaModSandbox : TestSceneModSandbox + public class TestSceneManiaModSandbox : ModSandboxTestScene { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(TestSceneManiaModSandbox)).ToList(); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuModSandbox.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuModSandbox.cs index d2a9d1ea6e..9f816ef2ed 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuModSandbox.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuModSandbox.cs @@ -11,7 +11,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestSceneOsuModSandbox : TestSceneModSandbox + public class TestSceneOsuModSandbox : ModSandboxTestScene { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(TestSceneOsuModSandbox)).ToList(); diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoModSandbox.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoModSandbox.cs index f5481713f5..ef62c7ed56 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoModSandbox.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoModSandbox.cs @@ -11,7 +11,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - public class TestSceneTaikoModSandbox : TestSceneModSandbox + public class TestSceneTaikoModSandbox : ModSandboxTestScene { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(TestSceneTaikoModSandbox)).ToList(); diff --git a/osu.Game/Tests/Visual/TestSceneModSandbox.cs b/osu.Game/Tests/Visual/ModSandboxTestScene.cs similarity index 95% rename from osu.Game/Tests/Visual/TestSceneModSandbox.cs rename to osu.Game/Tests/Visual/ModSandboxTestScene.cs index 5c32ebadce..bb872123c5 100644 --- a/osu.Game/Tests/Visual/TestSceneModSandbox.cs +++ b/osu.Game/Tests/Visual/ModSandboxTestScene.cs @@ -19,17 +19,17 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { - public abstract class TestSceneModSandbox : PlayerTestScene + public abstract class ModSandboxTestScene : PlayerTestScene { public override IReadOnlyList RequiredTypes => new[] { - typeof(TestSceneModSandbox) + typeof(ModSandboxTestScene) }; protected Mod Mod; private readonly TriangleButton button; - protected TestSceneModSandbox(Ruleset ruleset, Mod mod = null) + protected ModSandboxTestScene(Ruleset ruleset, Mod mod = null) : base(ruleset) { Mod = mod ?? new SandboxMod(); From 5c15704c819ab25868c48f511a26b417a2bb96d0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Mar 2020 10:28:39 +0900 Subject: [PATCH 090/192] Improve abstract structure for testability --- osu.Game/Tests/Visual/ModSandboxTestScene.cs | 96 ++++++-------------- osu.Game/Tests/Visual/TestReplayPlayer.cs | 24 +++++ 2 files changed, 50 insertions(+), 70 deletions(-) create mode 100644 osu.Game/Tests/Visual/TestReplayPlayer.cs diff --git a/osu.Game/Tests/Visual/ModSandboxTestScene.cs b/osu.Game/Tests/Visual/ModSandboxTestScene.cs index bb872123c5..84bab6a9b9 100644 --- a/osu.Game/Tests/Visual/ModSandboxTestScene.cs +++ b/osu.Game/Tests/Visual/ModSandboxTestScene.cs @@ -4,15 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Testing; -using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Mods; +using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; @@ -26,83 +18,47 @@ namespace osu.Game.Tests.Visual typeof(ModSandboxTestScene) }; - protected Mod Mod; - private readonly TriangleButton button; - - protected ModSandboxTestScene(Ruleset ruleset, Mod mod = null) + protected ModSandboxTestScene(Ruleset ruleset) : base(ruleset) { - Mod = mod ?? new SandboxMod(); - - var props = Mod.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance); - var hasSettings = props.Any(prop => prop.GetCustomAttribute(true) != null); - - Child = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding(50), - Margin = new MarginPadding { Bottom = 20 }, - Width = 0.4f, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Children = new Drawable[] - { - new ModControlSection(Mod, Mod.CreateSettingsControls()), - button = new TriangleButton - { - RelativeSizeAxes = Axes.X, - Width = 0.5f, - Text = "Start", - Action = () => - { - button.Text = hasSettings ? "Apply Settings" : "Restart"; - LoadPlayer(); - } - } - } - }; } - [SetUpSteps] + private ModTestCaseData currentTest; + public override void SetUpSteps() { + foreach (var testCase in CreateTestCases()) + { + AddStep("set test case", () => currentTest = testCase); + base.SetUpSteps(); + } } - [BackgroundDependencyLoader] - private void load() - { - LocalConfig.GetBindable(OsuSetting.KeyOverlay).Value = true; - } + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTest.Beatmap; protected override Player CreatePlayer(Ruleset ruleset) { - SelectedMods.Value = SelectedMods.Value.Append(Mod).ToArray(); + SelectedMods.Value = SelectedMods.Value.Append(currentTest.Mod).ToArray(); + + if (currentTest.Autoplay) + { + // We're simulating an auto-play via a replay so that the auto-play mod does not interfere + var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, SelectedMods.Value); + var score = ruleset.GetAutoplayMod().CreateReplayScore(beatmap); + + return new TestReplayPlayer(score, false, false); + } return base.CreatePlayer(ruleset); } - protected class SandboxMod : Mod + protected abstract ModTestCaseData[] CreateTestCases(); + + protected class ModTestCaseData { - public override string Name => "Sandbox Test"; - public override string Acronym => "ST"; - public override double ScoreMultiplier => 1.0; - - [SettingSource("Test Setting")] - public Bindable TestSetting1 { get; } = new BindableBool - { - Default = true, - Value = true - }; - - [SettingSource("Test Setting 2")] - public Bindable TestSetting2 { get; } = new BindableFloat - { - Precision = 0.1f, - MinValue = 0, - MaxValue = 20 - }; + public Mod Mod; + public bool Autoplay; + public IBeatmap Beatmap; } } } diff --git a/osu.Game/Tests/Visual/TestReplayPlayer.cs b/osu.Game/Tests/Visual/TestReplayPlayer.cs new file mode 100644 index 0000000000..e99fcc1e37 --- /dev/null +++ b/osu.Game/Tests/Visual/TestReplayPlayer.cs @@ -0,0 +1,24 @@ +// 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.UI; +using osu.Game.Scoring; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + public class TestReplayPlayer : ReplayPlayer + { + protected override bool PauseOnFocusLost { get; } + + public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; + + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + + public TestReplayPlayer(Score score, bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) + : base(score, allowPause, showResults) + { + PauseOnFocusLost = pauseOnFocusLost; + } + } +} From 239cfddcbb92503b662d89750953950b6a11a12d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Mar 2020 10:50:41 +0900 Subject: [PATCH 091/192] Improve test scenes/cases --- .../TestSceneCatchModSandbox.cs | 28 ------- .../TestSceneManiaModSandbox.cs | 28 ------- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 38 +++++++++- .../TestSceneOsuModSandbox.cs | 28 ------- .../TestSceneTaikoModSandbox.cs | 28 ------- osu.Game/Tests/Visual/ModSandboxTestScene.cs | 73 +++++++++++++++---- 6 files changed, 95 insertions(+), 128 deletions(-) delete mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneCatchModSandbox.cs delete mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneManiaModSandbox.cs delete mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneOsuModSandbox.cs delete mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoModSandbox.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModSandbox.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModSandbox.cs deleted file mode 100644 index 3e94121fd7..0000000000 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModSandbox.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Game.Rulesets.Mods; -using osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Catch.Tests -{ - [TestFixture] - public class TestSceneCatchModSandbox : ModSandboxTestScene - { - public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(TestSceneCatchModSandbox)).ToList(); - - public TestSceneCatchModSandbox() - : this(null) - { - } - - public TestSceneCatchModSandbox(Mod mod = null) - : base(new CatchRuleset(), mod) - { - } - } -} diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaModSandbox.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaModSandbox.cs deleted file mode 100644 index 897e7df1e9..0000000000 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaModSandbox.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Game.Rulesets.Mods; -using osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Mania.Tests -{ - [TestFixture] - public class TestSceneManiaModSandbox : ModSandboxTestScene - { - public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(TestSceneManiaModSandbox)).ToList(); - - public TestSceneManiaModSandbox() - : this(null) - { - } - - public TestSceneManiaModSandbox(Mod mod = null) - : base(new ManiaRuleset(), mod) - { - } - } -} diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 7f09731a11..46a3c1dff3 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -5,16 +5,50 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests.Mods { - public class TestSceneOsuModDifficultyAdjust : TestSceneOsuModSandbox + public class TestSceneOsuModDifficultyAdjust : ModSandboxTestScene { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(OsuModDifficultyAdjust)).ToList(); public TestSceneOsuModDifficultyAdjust() - : base(new OsuModDifficultyAdjust()) + : base(new OsuRuleset()) { } + + protected override ModTestCaseData[] CreateTestCases() => new[] + { + new ModTestCaseData("no adjustment", new OsuModDifficultyAdjust()) + { + Autoplay = true, + PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 + }, + new ModTestCaseData("cs = 10", new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }) + { + Autoplay = true, + PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 + }, + new ModTestCaseData("ar = 10", new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }) + { + Autoplay = true, + PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 + }, + }; + + protected override TestPlayer CreateReplayPlayer(Score score) => new ScoreAccessibleTestPlayer(score); + + private class ScoreAccessibleTestPlayer : TestPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + public ScoreAccessibleTestPlayer(Score score) + : base(score) + { + } + } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuModSandbox.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuModSandbox.cs deleted file mode 100644 index 9f816ef2ed..0000000000 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuModSandbox.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Game.Rulesets.Mods; -using osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Osu.Tests -{ - [TestFixture] - public class TestSceneOsuModSandbox : ModSandboxTestScene - { - public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(TestSceneOsuModSandbox)).ToList(); - - public TestSceneOsuModSandbox() - : this(null) - { - } - - public TestSceneOsuModSandbox(Mod mod = null) - : base(new OsuRuleset(), mod) - { - } - } -} diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoModSandbox.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoModSandbox.cs deleted file mode 100644 index ef62c7ed56..0000000000 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoModSandbox.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Game.Rulesets.Mods; -using osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Taiko.Tests -{ - [TestFixture] - public class TestSceneTaikoModSandbox : ModSandboxTestScene - { - public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(TestSceneTaikoModSandbox)).ToList(); - - public TestSceneTaikoModSandbox() - : this(null) - { - } - - public TestSceneTaikoModSandbox(Mod mod = null) - : base(new TaikoRuleset(), mod) - { - } - } -} diff --git a/osu.Game/Tests/Visual/ModSandboxTestScene.cs b/osu.Game/Tests/Visual/ModSandboxTestScene.cs index 84bab6a9b9..0610a145ae 100644 --- a/osu.Game/Tests/Visual/ModSandboxTestScene.cs +++ b/osu.Game/Tests/Visual/ModSandboxTestScene.cs @@ -4,9 +4,12 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Game.Beatmaps; +using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual @@ -29,36 +32,78 @@ namespace osu.Game.Tests.Visual { foreach (var testCase in CreateTestCases()) { - AddStep("set test case", () => currentTest = testCase); + AddStep(testCase.Name, () => currentTest = testCase); base.SetUpSteps(); + AddUntilStep("test passed", () => testCase.PassCondition?.Invoke() ?? true); } } - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTest.Beatmap; + protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTest?.Beatmap ?? base.CreateBeatmap(ruleset); - protected override Player CreatePlayer(Ruleset ruleset) + protected sealed override Player CreatePlayer(Ruleset ruleset) { SelectedMods.Value = SelectedMods.Value.Append(currentTest.Mod).ToArray(); - if (currentTest.Autoplay) - { - // We're simulating an auto-play via a replay so that the auto-play mod does not interfere - var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, SelectedMods.Value); - var score = ruleset.GetAutoplayMod().CreateReplayScore(beatmap); + var score = currentTest.Autoplay + ? ruleset.GetAutoplayMod().CreateReplayScore(Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, SelectedMods.Value)) + : new Score { Replay = new Replay() }; - return new TestReplayPlayer(score, false, false); - } - - return base.CreatePlayer(ruleset); + return CreateReplayPlayer(score); } + /// + /// Creates the test cases for this test scene. + /// protected abstract ModTestCaseData[] CreateTestCases(); + /// + /// Creates the for a test case. + /// + /// The . + protected virtual TestPlayer CreateReplayPlayer(Score score) => new TestPlayer(score); + + protected class TestPlayer : TestReplayPlayer + { + public TestPlayer(Score score) + : base(score, false, false) + { + } + } + protected class ModTestCaseData { - public Mod Mod; - public bool Autoplay; + /// + /// Whether to use a replay to simulate an auto-play. True by default. + /// + public bool Autoplay = true; + + /// + /// The beatmap for this test case. + /// + [CanBeNull] public IBeatmap Beatmap; + + /// + /// The conditions that cause this test case to pass. + /// + [CanBeNull] + public Func PassCondition; + + /// + /// The name of this test case, displayed in the test browser. + /// + public readonly string Name; + + /// + /// The this test case tests. + /// + public readonly Mod Mod; + + public ModTestCaseData(string name, Mod mod) + { + Name = name; + Mod = mod; + } } } } From ce7cbf29ca1546f4fc8f1d81ca8cbb401537dfdf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Mar 2020 12:20:25 +0900 Subject: [PATCH 092/192] Move to using test methods for better separation --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 37 ++++++++++--------- osu.Game/Tests/Visual/ModSandboxTestScene.cs | 22 +++++------ osu.Game/Tests/Visual/PlayerTestScene.cs | 17 +++++++++ osu.Game/Tests/Visual/ScreenTestScene.cs | 2 +- 4 files changed, 48 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 46a3c1dff3..1fc9ccccd1 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NUnit.Framework; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -20,24 +21,26 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { } - protected override ModTestCaseData[] CreateTestCases() => new[] + [Test] + public void TestNoAdjustment() => CreateModTest(new ModTestCaseData("no adjustment", new OsuModDifficultyAdjust()) { - new ModTestCaseData("no adjustment", new OsuModDifficultyAdjust()) - { - Autoplay = true, - PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 - }, - new ModTestCaseData("cs = 10", new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }) - { - Autoplay = true, - PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 - }, - new ModTestCaseData("ar = 10", new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }) - { - Autoplay = true, - PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 - }, - }; + Autoplay = true, + PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 + }); + + [Test] + public void TestCircleSize10() => CreateModTest(new ModTestCaseData("cs = 10", new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }) + { + Autoplay = true, + PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 + }); + + [Test] + public void TestApproachRate10() => CreateModTest(new ModTestCaseData("ar = 10", new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }) + { + Autoplay = true, + PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 + }); protected override TestPlayer CreateReplayPlayer(Score score) => new ScoreAccessibleTestPlayer(score); diff --git a/osu.Game/Tests/Visual/ModSandboxTestScene.cs b/osu.Game/Tests/Visual/ModSandboxTestScene.cs index 0610a145ae..a1fa757452 100644 --- a/osu.Game/Tests/Visual/ModSandboxTestScene.cs +++ b/osu.Game/Tests/Visual/ModSandboxTestScene.cs @@ -16,6 +16,8 @@ namespace osu.Game.Tests.Visual { public abstract class ModSandboxTestScene : PlayerTestScene { + protected sealed override bool HasCustomSteps => true; + public override IReadOnlyList RequiredTypes => new[] { typeof(ModSandboxTestScene) @@ -28,14 +30,15 @@ namespace osu.Game.Tests.Visual private ModTestCaseData currentTest; - public override void SetUpSteps() + protected void CreateModTest(ModTestCaseData testCaseData) => CreateTest(() => { - foreach (var testCase in CreateTestCases()) - { - AddStep(testCase.Name, () => currentTest = testCase); - base.SetUpSteps(); - AddUntilStep("test passed", () => testCase.PassCondition?.Invoke() ?? true); - } + AddStep("set test data", () => currentTest = testCaseData); + }); + + public override void TearDownSteps() + { + AddUntilStep("test passed", () => currentTest?.PassCondition?.Invoke() ?? true); + base.TearDownSteps(); } protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTest?.Beatmap ?? base.CreateBeatmap(ruleset); @@ -51,11 +54,6 @@ namespace osu.Game.Tests.Visual return CreateReplayPlayer(score); } - /// - /// Creates the test cases for this test scene. - /// - protected abstract ModTestCaseData[] CreateTestCases(); - /// /// Creates the for a test case. /// diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 1ca5256353..0d5aac8cfd 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.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.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; @@ -14,6 +15,11 @@ namespace osu.Game.Tests.Visual { public abstract class PlayerTestScene : RateAdjustedBeatmapTestScene { + /// + /// Whether custom test steps are provided. Custom tests should invoke to create the test steps. + /// + protected virtual bool HasCustomSteps { get; } = false; + private readonly Ruleset ruleset; protected Player Player; @@ -37,6 +43,17 @@ namespace osu.Game.Tests.Visual { base.SetUpSteps(); + if (!HasCustomSteps) + CreateTest(null); + } + + protected void CreateTest(Action action) + { + if (action != null && !HasCustomSteps) + throw new InvalidOperationException($"Cannot add custom test steps without {nameof(HasCustomSteps)} being set."); + + action?.Invoke(); + AddStep(ruleset.RulesetInfo.Name, LoadPlayer); AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); } diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs index feca592049..d26aacf2bc 100644 --- a/osu.Game/Tests/Visual/ScreenTestScene.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual public virtual void SetUpSteps() => addExitAllScreensStep(); [TearDownSteps] - public void TearDownSteps() => addExitAllScreensStep(); + public virtual void TearDownSteps() => addExitAllScreensStep(); private void addExitAllScreensStep() { From 89399b177c56e7e129a3f642c787ea384e9f0dca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 16:41:00 +0900 Subject: [PATCH 093/192] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 651d1beda1..983c622f77 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 168a358e47..c6c2708e64 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 3c2a67e908..3552047cf8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + @@ -79,7 +79,7 @@ - + From 5432371c74bf9c4787ae206ff154513abd8da60b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2020 08:42:12 +0000 Subject: [PATCH 094/192] Bump Sentry from 2.0.3 to 2.1.0 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 2.0.3 to 2.1.0. - [Release notes](https://github.com/getsentry/sentry-dotnet/releases) - [Commits](https://github.com/getsentry/sentry-dotnet/compare/2.0.3...2.1.0) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 168a358e47..2b6ac585fa 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + From ee73f3e2b2c2fba744cd67fd499b6fd1ef5fb46c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 18:54:00 +0900 Subject: [PATCH 095/192] Change matching mode for global actions to better discern similar binds --- osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs | 5 +++-- osu.Game/Input/Bindings/GlobalActionContainer.cs | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index ea274284ac..e83d899469 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -31,8 +31,9 @@ namespace osu.Game.Input.Bindings /// A reference to identify the current . Used to lookup mappings. Null for global mappings. /// An optional variant for the specified . Used when a ruleset has more than one possible keyboard layouts. /// Specify how to deal with multiple matches of s and s. - public DatabasedKeyBindingContainer(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None) - : base(simultaneousMode) + /// Specify how to deal with exact matches. + public DatabasedKeyBindingContainer(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None, KeyCombinationMatchingMode matchingMode = KeyCombinationMatchingMode.Any) + : base(simultaneousMode, matchingMode) { this.ruleset = ruleset; this.variant = variant; diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 7763577a14..bb0c586d73 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -15,6 +15,7 @@ namespace osu.Game.Input.Bindings private readonly Drawable handler; public GlobalActionContainer(OsuGameBase game) + : base(matchingMode: KeyCombinationMatchingMode.Modifiers) { if (game is IKeyBindingHandler) handler = game; From 489bf16beae7bbdb91e390db14b30f9c0ea33575 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 18:55:28 +0900 Subject: [PATCH 096/192] Add SelectNext and SelectPrevious global actions --- .../Input/Bindings/GlobalActionContainer.cs | 9 +++ osu.Game/Screens/Play/GameplayMenuOverlay.cs | 40 +++++--------- osu.Game/Screens/Select/BeatmapCarousel.cs | 55 +++++++++---------- 3 files changed, 50 insertions(+), 54 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index bb0c586d73..8f481d94c7 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -39,6 +39,9 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.Escape, GlobalAction.Back), new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back), + new KeyBinding(InputKey.Up, GlobalAction.SelectPrevious), + new KeyBinding(InputKey.Down, GlobalAction.SelectNext), + new KeyBinding(InputKey.Space, GlobalAction.Select), new KeyBinding(InputKey.Enter, GlobalAction.Select), new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select), @@ -142,5 +145,11 @@ namespace osu.Game.Input.Bindings [Description("Toggle now playing overlay")] ToggleNowPlaying, + + [Description("Previous Selection")] + SelectPrevious, + + [Description("Next Selection")] + SelectNext, } } diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 98e2bc5a03..6b37135c86 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -12,7 +12,6 @@ using osu.Game.Graphics; using osu.Framework.Allocation; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Shapes; -using osuTK.Input; using System.Collections.Generic; using System.Linq; using osu.Framework.Input.Bindings; @@ -204,35 +203,24 @@ namespace osu.Game.Screens.Play InternalButtons[selectionIndex].Selected.Value = true; } - protected override bool OnKeyDown(KeyDownEvent e) - { - if (!e.Repeat) - { - switch (e.Key) - { - case Key.Up: - if (selectionIndex == -1 || selectionIndex == 0) - setSelected(InternalButtons.Count - 1); - else - setSelected(selectionIndex - 1); - return true; - - case Key.Down: - if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) - setSelected(0); - else - setSelected(selectionIndex + 1); - return true; - } - } - - return base.OnKeyDown(e); - } - public bool OnPressed(GlobalAction action) { switch (action) { + case GlobalAction.SelectPrevious: + if (selectionIndex == -1 || selectionIndex == 0) + setSelected(InternalButtons.Count - 1); + else + setSelected(selectionIndex - 1); + return true; + + case GlobalAction.SelectNext: + if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) + setSelected(0); + else + setSelected(selectionIndex + 1); + return true; + case GlobalAction.Back: BackAction.Invoke(); return true; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 7f36a23a86..1db97af2f0 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -16,15 +16,17 @@ using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Threading; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; +using osu.Game.Input.Bindings; using osu.Game.Screens.Select.Carousel; namespace osu.Game.Screens.Select { - public class BeatmapCarousel : CompositeDrawable + public class BeatmapCarousel : CompositeDrawable, IKeyBindingHandler { private const float bleed_top = FilterControl.HEIGHT; private const float bleed_bottom = Footer.HEIGHT; @@ -435,41 +437,38 @@ namespace osu.Game.Screens.Select protected override bool OnKeyDown(KeyDownEvent e) { - // allow for controlling volume when alt is held. - // this is required as the VolumeControlReceptor uses OnPressed, which is - // executed after all OnKeyDown events. - if (e.AltPressed) - return base.OnKeyDown(e); - - int direction = 0; - bool skipDifficulties = false; - switch (e.Key) { - case Key.Up: - direction = -1; - break; - - case Key.Down: - direction = 1; - break; - case Key.Left: - direction = -1; - skipDifficulties = true; - break; + SelectNext(-1, true); + return true; case Key.Right: - direction = 1; - skipDifficulties = true; - break; + SelectNext(1, true); + return true; } - if (direction == 0) - return base.OnKeyDown(e); + return false; + } - SelectNext(direction, skipDifficulties); - return true; + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.SelectNext: + SelectNext(1, false); + return true; + + case GlobalAction.SelectPrevious: + SelectNext(-1, false); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { } protected override void Update() From 3404b09010608459508bf0f7829adde00bae011d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 18:55:52 +0900 Subject: [PATCH 097/192] Bring database snapshot in line with specs --- osu.Game/Migrations/OsuDbContextModelSnapshot.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index a6d9d1f3cb..bc4fc3342d 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -43,7 +43,7 @@ namespace osu.Game.Migrations b.Property("ID") .ValueGeneratedOnAdd(); - b.Property("AudioLeadIn"); + b.Property("AudioLeadIn"); b.Property("BPM"); From ed516f05c2fb6d054e20cb6ffd0e343f5603f74f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 18:56:09 +0900 Subject: [PATCH 098/192] Forward any unhandled scroll events to volume overlay --- osu.Game/OsuGame.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a73d5b57c4..5781a7fbc4 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -25,6 +25,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Beatmaps; @@ -805,6 +806,13 @@ namespace osu.Game return d; } + protected override bool OnScroll(ScrollEvent e) + { + // forward any unhandled mouse scroll events to the volume control. + volume.Adjust(GlobalAction.IncreaseVolume, e.ScrollDelta.Y, e.IsPrecise); + return true; + } + public bool OnPressed(GlobalAction action) { if (introScreen == null) return false; From 88583b5c79c5c5becd55268ffc19ce6b1b76be5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 18:56:41 +0900 Subject: [PATCH 099/192] Remove any existing bindings to increase/decrease volume --- ...02094919_RefreshVolumeBindings.Designer.cs | 506 ++++++++++++++++++ .../20200302094919_RefreshVolumeBindings.cs | 16 + 2 files changed, 522 insertions(+) create mode 100644 osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs create mode 100644 osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs new file mode 100644 index 0000000000..22316b0380 --- /dev/null +++ b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs @@ -0,0 +1,506 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20200302094919_RefreshVolumeBindings")] + partial class RefreshVolumeBindings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs new file mode 100644 index 0000000000..ec4475971c --- /dev/null +++ b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class RefreshVolumeBindings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM KeyBinding WHERE action in (6,7)"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} From 6989738710cc6e3da689b26087ab9f6f3dc09295 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 18:59:05 +0900 Subject: [PATCH 100/192] Change default global bindings for volume changing to include the alt key prefix --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 8f481d94c7..71771abede 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -58,10 +58,11 @@ namespace osu.Game.Input.Bindings public IEnumerable AudioControlKeyBindings => new[] { - new KeyBinding(InputKey.Up, GlobalAction.IncreaseVolume), - new KeyBinding(InputKey.MouseWheelUp, GlobalAction.IncreaseVolume), - new KeyBinding(InputKey.Down, GlobalAction.DecreaseVolume), - new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume), + new KeyBinding(new[] { InputKey.Alt, InputKey.Up }, GlobalAction.IncreaseVolume), + new KeyBinding(new[] { InputKey.Alt, InputKey.MouseWheelUp }, GlobalAction.IncreaseVolume), + new KeyBinding(new[] { InputKey.Alt, InputKey.Down }, GlobalAction.DecreaseVolume), + new KeyBinding(new[] { InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.DecreaseVolume), + new KeyBinding(InputKey.F4, GlobalAction.ToggleMute), new KeyBinding(InputKey.TrackPrevious, GlobalAction.MusicPrev), From e608d807f4c8db1c90741cf80fff56dc769f292a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 18:59:38 +0900 Subject: [PATCH 101/192] Handle SelectPrevious / SelectNext as volume change operations if nothing else handled game-wide --- .../Overlays/Volume/VolumeControlReceptor.cs | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs index 76fad945cc..4bff8146b4 100644 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs @@ -14,8 +14,25 @@ namespace osu.Game.Overlays.Volume public Func ActionRequested; public Func ScrollActionRequested; - public bool OnPressed(GlobalAction action) => ActionRequested?.Invoke(action) ?? false; - public bool OnScroll(GlobalAction action, float amount, bool isPrecise) => ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false; + public bool OnPressed(GlobalAction action) + { + // if nothing else handles selection actions in the game, it's safe to let volume be adjusted. + switch (action) + { + case GlobalAction.SelectPrevious: + action = GlobalAction.IncreaseVolume; + break; + + case GlobalAction.SelectNext: + action = GlobalAction.DecreaseVolume; + break; + } + + return ActionRequested?.Invoke(action) ?? false; + } + + public bool OnScroll(GlobalAction action, float amount, bool isPrecise) => + ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false; public void OnReleased(GlobalAction action) { From 81191a3d20d6f7150bc201bf40149d160dd3b259 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 18:59:55 +0900 Subject: [PATCH 102/192] Handle SelectPrevious/SelectNext locally to volume meter when hovered --- osu.Game/Overlays/Volume/VolumeMeter.cs | 27 ++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 7effd290e6..07accf8820 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -11,16 +11,18 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Input.Bindings; using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.Volume { - public class VolumeMeter : Container + public class VolumeMeter : Container, IKeyBindingHandler { private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; @@ -260,5 +262,28 @@ namespace osu.Game.Overlays.Volume { this.ScaleTo(1f, transition_length, Easing.OutExpo); } + + public bool OnPressed(GlobalAction action) + { + if (!IsHovered) + return false; + + switch (action) + { + case GlobalAction.SelectPrevious: + adjust(1, false); + return true; + + case GlobalAction.SelectNext: + adjust(-1, false); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + } } } From e890e454200dbccb263df546d37ba209cc5fb7b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 19:40:32 +0900 Subject: [PATCH 103/192] Fix Ctrl+Enter behaviour regression --- osu.Game/Screens/Select/PlaySongSelect.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index e744fd6a7b..af113781e5 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -4,6 +4,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Screens.Play; @@ -49,6 +50,20 @@ namespace osu.Game.Screens.Select } } + protected override bool OnKeyDown(KeyDownEvent e) + { + switch (e.Key) + { + case Key.Enter: + // this is a special hard-coded case; we can't rely on OnPressed (of SongSelect) as GlobalActionContainer is + // matching with exact modifier consideration (so Ctrl+Enter would be ignored). + FinaliseSelection(); + return true; + } + + return base.OnKeyDown(e); + } + protected override bool OnStart() { if (player != null) return false; From 23068034b103e6e5ac5c320cbfa37d43c19bb17b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 19:51:06 +0900 Subject: [PATCH 104/192] Rename bool and make property for legibility --- .../Overlays/Changelog/UpdateStreamBadge.cs | 23 ++++++++++--------- .../Changelog/UpdateStreamBadgeArea.cs | 4 ++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs index 5e10410f37..97c58614b8 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs @@ -100,7 +100,7 @@ namespace osu.Game.Overlays.Changelog private void updateState() { - if (SelectedTab.Value == null && !externalDimRequested) + if (SelectedTab.Value == null && !allStreamsDimmed) { expandingBar.Expand(); expandingBar.FadeTo(1, transition_duration, Easing.OutQuint); @@ -113,21 +113,22 @@ namespace osu.Game.Overlays.Changelog expandingBar.IsCollapsed = !shouldExpand; expandingBar.FadeTo(shouldExpand ? 1 : 0.5f, transition_duration, Easing.OutQuint); - text.FadeTo(IsHovered || (Active.Value && !externalDimRequested) ? 1 : 0.5f, transition_duration, Easing.OutQuint); + text.FadeTo(IsHovered || (Active.Value && !allStreamsDimmed) ? 1 : 0.5f, transition_duration, Easing.OutQuint); } - private bool externalDimRequested; + private bool allStreamsDimmed; - public void EnableDim() + public bool AllStreamsDimmed { - externalDimRequested = true; - updateState(); - } + get => allStreamsDimmed; + set + { + if (value == allStreamsDimmed) + return; - public void DisableDim() - { - externalDimRequested = false; - updateState(); + allStreamsDimmed = value; + updateState(); + } } } } diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs index d937bf775a..314f94101a 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Changelog protected override bool OnHover(HoverEvent e) { foreach (var streamBadge in TabContainer.Children.OfType()) - streamBadge.EnableDim(); + streamBadge.AllStreamsDimmed = true; return base.OnHover(e); } @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Changelog protected override void OnHoverLost(HoverLostEvent e) { foreach (var streamBadge in TabContainer.Children.OfType()) - streamBadge.DisableDim(); + streamBadge.AllStreamsDimmed = false; base.OnHoverLost(e); } From 9f73b2960d39d6039054df3590fd32a5da0d781b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Mar 2020 20:08:04 +0900 Subject: [PATCH 105/192] Prevent unnecessary auto-size computations in mania --- osu.Game.Rulesets.Mania/UI/ManiaStage.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index a28de7ea58..bfe9f1085b 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -115,9 +115,8 @@ namespace osu.Game.Rulesets.Mania.UI { Anchor = Anchor.TopCentre, Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, Y = HIT_TARGET_POSITION + 150, - BypassAutoSizeAxes = Axes.Both }, topLevelContainer = new Container { RelativeSizeAxes = Axes.Both } } From 69b47137311e121be5c1fdd9c7d865f570b548c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 20:16:58 +0900 Subject: [PATCH 106/192] Refactor everything so I can read the code --- .../Graphics/UserInterface/ExpandingBar.cs | 20 +++++----- .../Overlays/Changelog/UpdateStreamBadge.cs | 40 +++++++++++-------- .../Changelog/UpdateStreamBadgeArea.cs | 4 +- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExpandingBar.cs b/osu.Game/Graphics/UserInterface/ExpandingBar.cs index 439a6002d8..60cb35b4c4 100644 --- a/osu.Game/Graphics/UserInterface/ExpandingBar.cs +++ b/osu.Game/Graphics/UserInterface/ExpandingBar.cs @@ -13,17 +13,17 @@ namespace osu.Game.Graphics.UserInterface /// public class ExpandingBar : Circle { - private bool isCollapsed; + private bool expanded = true; - public bool IsCollapsed + public bool Expanded { - get => isCollapsed; + get => expanded; set { - if (value == isCollapsed) + if (value == expanded) return; - isCollapsed = value; + expanded = value; updateState(); } } @@ -83,19 +83,21 @@ namespace osu.Game.Graphics.UserInterface updateState(); } - public void Collapse() => IsCollapsed = true; + public void Collapse() => Expanded = false; - public void Expand() => IsCollapsed = false; + public void Expand() => Expanded = true; private void updateState() { - float newSize = IsCollapsed ? CollapsedSize : ExpandedSize; - Easing easingType = IsCollapsed ? Easing.Out : Easing.OutElastic; + float newSize = expanded ? ExpandedSize : CollapsedSize; + Easing easingType = expanded ? Easing.OutElastic : Easing.Out; if (RelativeSizeAxes == Axes.X) this.ResizeHeightTo(newSize, 400, easingType); else this.ResizeWidthTo(newSize, 400, easingType); + + this.FadeTo(expanded ? 1 : 0.5f, 100, Easing.OutQuint); } } } diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs index 97c58614b8..6786bbc49f 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs @@ -74,7 +74,7 @@ namespace osu.Game.Overlays.Changelog Colour = stream.Colour, ExpandedSize = 4, CollapsedSize = 2, - IsCollapsed = true + Expanded = true }, new HoverClickSounds() }); @@ -100,33 +100,39 @@ namespace osu.Game.Overlays.Changelog private void updateState() { - if (SelectedTab.Value == null && !allStreamsDimmed) + // highlighted regardless if we are hovered + bool textHighlighted = IsHovered; + bool barExpanded = IsHovered; + + if (SelectedTab.Value == null) { - expandingBar.Expand(); - expandingBar.FadeTo(1, transition_duration, Easing.OutQuint); - text.FadeTo(1, transition_duration, Easing.OutQuint); - return; + // at listing, all badges are highlighted when user is not hovering any badge. + textHighlighted |= !userHoveringArea; + barExpanded |= !userHoveringArea; + } + else + { + // bar is always expanded when active + barExpanded |= Active.Value; + + // text is highlighted only when hovered or active (but not if in selection mode) + textHighlighted |= Active.Value && !userHoveringArea; } - var shouldExpand = Active.Value || IsHovered; - - expandingBar.IsCollapsed = !shouldExpand; - expandingBar.FadeTo(shouldExpand ? 1 : 0.5f, transition_duration, Easing.OutQuint); - - text.FadeTo(IsHovered || (Active.Value && !allStreamsDimmed) ? 1 : 0.5f, transition_duration, Easing.OutQuint); + expandingBar.Expanded = barExpanded; + text.FadeTo(textHighlighted ? 1 : 0.5f, transition_duration, Easing.OutQuint); } - private bool allStreamsDimmed; + private bool userHoveringArea; - public bool AllStreamsDimmed + public bool UserHoveringArea { - get => allStreamsDimmed; set { - if (value == allStreamsDimmed) + if (value == userHoveringArea) return; - allStreamsDimmed = value; + userHoveringArea = value; updateState(); } } diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs index 314f94101a..ffb622dd37 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Changelog protected override bool OnHover(HoverEvent e) { foreach (var streamBadge in TabContainer.Children.OfType()) - streamBadge.AllStreamsDimmed = true; + streamBadge.UserHoveringArea = true; return base.OnHover(e); } @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Changelog protected override void OnHoverLost(HoverLostEvent e) { foreach (var streamBadge in TabContainer.Children.OfType()) - streamBadge.AllStreamsDimmed = false; + streamBadge.UserHoveringArea = false; base.OnHoverLost(e); } From 3d344a076de0a934a5adfb92f2d1d01a3e1da31d Mon Sep 17 00:00:00 2001 From: naoey Date: Tue, 3 Mar 2020 06:17:25 +0530 Subject: [PATCH 107/192] Add test for disabled keycounter, don't discard change event values --- .../Visual/Gameplay/TestSceneKeyCounter.cs | 23 ++++++++++--------- osu.Game/Screens/Play/Player.cs | 19 ++++++++------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index e7b3e007fc..10827bc0b9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -47,21 +47,22 @@ namespace osu.Game.Tests.Visual.Gameplay Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key; - AddStep($"Press {testKey} key", () => + void addPressKeyStep() { - InputManager.PressKey(testKey); - InputManager.ReleaseKey(testKey); - }); + AddStep($"Press {testKey} key", () => + { + InputManager.PressKey(testKey); + InputManager.ReleaseKey(testKey); + }); + } + addPressKeyStep(); AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 1); - - AddStep($"Press {testKey} key", () => - { - InputManager.PressKey(testKey); - InputManager.ReleaseKey(testKey); - }); - + addPressKeyStep(); AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 2); + AddStep($"Disable counting", () => testCounter.IsCounting = false); + addPressKeyStep(); + AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses == 2); Add(kc); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 237e364e69..2d49c707ec 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -157,7 +157,10 @@ namespace osu.Game.Screens.Play addGameplayComponents(GameplayClockContainer, Beatmap.Value); addOverlayComponents(GameplayClockContainer, Beatmap.Value); - DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); + DrawableRuleset.HasReplayLoaded.BindValueChanged(e => + { + updatePauseOnFocusLostState(e.NewValue, BreakOverlay.IsBreakTime.Value); + }, true); // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); @@ -184,7 +187,7 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToHealthProcessor(HealthProcessor); - BreakOverlay.IsBreakTime.BindValueChanged(_ => onBreakTimeChanged(), true); + BreakOverlay.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); } private void addUnderlayComponents(Container target) @@ -286,16 +289,16 @@ namespace osu.Game.Screens.Play HealthProcessor.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); } - private void onBreakTimeChanged() + private void onBreakTimeChanged(ValueChangedEvent changeEvent) { - updatePauseOnFocusLostState(); - HUDOverlay.KeyCounter.IsCounting = !BreakOverlay.IsBreakTime.Value; + updatePauseOnFocusLostState(DrawableRuleset.HasReplayLoaded.Value, changeEvent.NewValue); + HUDOverlay.KeyCounter.IsCounting = !changeEvent.NewValue; } - private void updatePauseOnFocusLostState() => + private void updatePauseOnFocusLostState(bool replayLoaded, bool isBreakTime) => HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost - && !DrawableRuleset.HasReplayLoaded.Value - && !BreakOverlay.IsBreakTime.Value; + && !replayLoaded + && !isBreakTime; private IBeatmap loadPlayableBeatmap() { From 90c2f7bd89620bc05cd0529f3a74780199e2fa0a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 3 Mar 2020 12:13:36 +0900 Subject: [PATCH 108/192] Fail tests by default --- osu.Game/Tests/Visual/ModSandboxTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/ModSandboxTestScene.cs b/osu.Game/Tests/Visual/ModSandboxTestScene.cs index a1fa757452..8bfa373e46 100644 --- a/osu.Game/Tests/Visual/ModSandboxTestScene.cs +++ b/osu.Game/Tests/Visual/ModSandboxTestScene.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual public override void TearDownSteps() { - AddUntilStep("test passed", () => currentTest?.PassCondition?.Invoke() ?? true); + AddUntilStep("test passed", () => currentTest?.PassCondition?.Invoke() ?? false); base.TearDownSteps(); } From 1e26df64b6cc022123222e4e104fbb382670037d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 3 Mar 2020 12:58:07 +0900 Subject: [PATCH 109/192] Fix constructor test failures --- osu.Game/Tests/Visual/ModSandboxTestScene.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/ModSandboxTestScene.cs b/osu.Game/Tests/Visual/ModSandboxTestScene.cs index 8bfa373e46..11612d0eca 100644 --- a/osu.Game/Tests/Visual/ModSandboxTestScene.cs +++ b/osu.Game/Tests/Visual/ModSandboxTestScene.cs @@ -37,7 +37,14 @@ namespace osu.Game.Tests.Visual public override void TearDownSteps() { - AddUntilStep("test passed", () => currentTest?.PassCondition?.Invoke() ?? false); + AddUntilStep("test passed", () => + { + if (currentTest == null) + return true; + + return currentTest.PassCondition?.Invoke() ?? false; + }); + base.TearDownSteps(); } From a1aecd4c3905bbd9cd9d9e1925d257ff40e6419a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 3 Mar 2020 12:59:41 +0900 Subject: [PATCH 110/192] Fix TrackVirtualManual not respecting rate adjustments --- osu.Game/Tests/Visual/OsuTestScene.cs | 57 ++++++--------------------- 1 file changed, 11 insertions(+), 46 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index b203557fab..3faecc87d2 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -229,17 +229,10 @@ namespace osu.Game.Tests.Visual /// public class TrackVirtualManual : Track { + private readonly StopwatchClock stopwatchClock = new StopwatchClock(); + private readonly IFrameBasedClock referenceClock; - private readonly ManualClock clock = new ManualClock(); - - private bool running; - - /// - /// Local offset added to the reference clock to resolve correct time. - /// - private double offset; - public TrackVirtualManual(IFrameBasedClock referenceClock) { this.referenceClock = referenceClock; @@ -248,60 +241,32 @@ namespace osu.Game.Tests.Visual public override bool Seek(double seek) { - offset = Math.Clamp(seek, 0, Length); - lastReferenceTime = null; + var offset = Math.Clamp(seek, 0, Length); + + stopwatchClock.Seek(offset); return offset == seek; } - public override void Start() - { - running = true; - } + public override void Start() => stopwatchClock.Start(); public override void Reset() { - Seek(0); + stopwatchClock.Seek(0); base.Reset(); } - public override void Stop() - { - if (running) - { - running = false; - // on stopping, the current value should be transferred out of the clock, as we can no longer rely on - // the referenceClock (which will still be counting time). - offset = clock.CurrentTime; - lastReferenceTime = null; - } - } + public override void Stop() => stopwatchClock.Stop(); - public override bool IsRunning => running; + public override bool IsRunning => stopwatchClock.IsRunning; - private double? lastReferenceTime; - - public override double CurrentTime => clock.CurrentTime; + public override double CurrentTime => stopwatchClock.CurrentTime; protected override void UpdateState() { base.UpdateState(); - if (running) - { - double refTime = referenceClock.CurrentTime; - - if (!lastReferenceTime.HasValue) - { - // if the clock just started running, the current value should be transferred to the offset - // (to zero the progression of time). - offset -= refTime; - } - - lastReferenceTime = refTime; - } - - clock.CurrentTime = Math.Min((lastReferenceTime ?? 0) + offset, Length); + stopwatchClock.Rate = Rate * referenceClock.Rate; if (CurrentTime >= Length) { From cc5b44e46681eb6b46c6c0fc1109cd5b64dd108b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 3 Mar 2020 13:36:55 +0900 Subject: [PATCH 111/192] Add test scene --- .../Mods/TestSceneOsuModDoubleTime.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs new file mode 100644 index 0000000000..deb733c581 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModDoubleTime : ModSandboxTestScene + { + public TestSceneOsuModDoubleTime() + : base(new OsuRuleset()) + { + } + + [TestCase(0.5)] + [TestCase(1.01)] + [TestCase(1.5)] + [TestCase(2)] + [TestCase(5)] + public void TestDefaultRate(double rate) => CreateModTest(new ModTestCaseData("1.5x", new OsuModDoubleTime { SpeedChange = { Value = rate } }) + { + PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 + }); + + protected override TestPlayer CreateReplayPlayer(Score score) => new ScoreAccessibleTestPlayer(score); + + private class ScoreAccessibleTestPlayer : TestPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + public ScoreAccessibleTestPlayer(Score score) + : base(score) + { + } + } + } +} From 2e4adc056f3e02d1e165945bc0b7f251cae50d65 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 3 Mar 2020 13:38:10 +0900 Subject: [PATCH 112/192] Fix potential deadlock during gameplay tests --- osu.Game/Screens/Play/GameplayClockContainer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 1c061c215b..591e969ad8 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -123,6 +123,10 @@ namespace osu.Game.Screens.Play public void Restart() { + // The Reset() call below causes speed adjustments to be reset in an async context, leading to deadlocks. + // The deadlock can be prevented by resetting the track synchronously before entering the async context. + track.ResetSpeedAdjustments(); + Task.Run(() => { track.Reset(); From cb1129218135bd2b1e2d3a287d381c13e08725d6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 3 Mar 2020 16:06:31 +0900 Subject: [PATCH 113/192] Reduce social overlay/direct overlay paddings --- osu.Game/Overlays/SearchableList/SearchableListOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index 0783c64c20..72796df6d5 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.SearchableList { public abstract class SearchableListOverlay : FullscreenOverlay { - public const float WIDTH_PADDING = 80; + public const float WIDTH_PADDING = 10; protected SearchableListOverlay(OverlayColourScheme colourScheme) : base(colourScheme) From f1f4f1ffbd29bfe1126159c217084e552058f3a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 18:04:12 +0900 Subject: [PATCH 114/192] Add torus font --- .../Resources/Fonts/Aquatico-Light.bin | Bin 4998 -> 0 bytes .../Resources/Fonts/Aquatico-Light_0.png | Bin 68407 -> 0 bytes .../Resources/Fonts/Aquatico-Regular.bin | Bin 4994 -> 0 bytes .../Resources/Fonts/Aquatico-Regular_0.png | Bin 69000 -> 0 bytes .../Gameplay/Components/MatchHeader.cs | 4 +- .../Gameplay/Components/MatchScoreDisplay.cs | 4 +- .../Screens/TeamIntro/TeamIntroScreen.cs | 4 +- .../Screens/TeamWin/TeamWinScreen.cs | 8 +- osu.Game.Tournament/TournamentFont.cs | 75 ------------------ osu.Game.Tournament/TournamentGameBase.cs | 3 - osu.Game/Graphics/OsuFont.cs | 17 ++-- osu.Game/OsuGameBase.cs | 5 ++ 12 files changed, 22 insertions(+), 98 deletions(-) delete mode 100644 osu.Game.Tournament/Resources/Fonts/Aquatico-Light.bin delete mode 100644 osu.Game.Tournament/Resources/Fonts/Aquatico-Light_0.png delete mode 100644 osu.Game.Tournament/Resources/Fonts/Aquatico-Regular.bin delete mode 100644 osu.Game.Tournament/Resources/Fonts/Aquatico-Regular_0.png delete mode 100644 osu.Game.Tournament/TournamentFont.cs diff --git a/osu.Game.Tournament/Resources/Fonts/Aquatico-Light.bin b/osu.Game.Tournament/Resources/Fonts/Aquatico-Light.bin deleted file mode 100644 index 42cfdf08de4977e66acd9063ef31741f88715f0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4998 zcmZ9O$&XZ36vof1YC$6UOoK#>Od^8_O?Lw-Gzv7>Ey|#X4HzR~kimgL8AMGq{sB0| zg_`Kb7$YVoy3mDjr7K z#f*s9A3y8r5Jg8vtUG>k&xynPj?Uh6_~4-vQ7Q9^$8;|`ioB$wI$eMNzivrh*R}WX zk=@;0$BrC~j*Al_W^2WY7>YvwN9>C^5v8uERR>~H^hY(Ogmx}(llEmXznB^~WlcMm zbETb+598j(x_)W5OZ!pR(%7bTH)!uD=jr$2%Es6j>+`c)rOlUipx72GV}rELxKG*w zY3CJVVoa4bHLjPoP};*vZANU#&njklhqUjrwmGjuTO{orJ$p;sEbXS4E$vQebBaYp zPb`r(GwzbMSlSQG7#nkpCDLxJ%qwn<30k)-RBPEKty-C1Op1xpwqy^MO5?wt*ePvt z-mhD~zo|s5j{Bu`#yUOQBki|p#Po(QGo|%PJ0lNzvIm`Ux3p!_K9{z%;R{|Zm-cq$ zP5EN)*GQ8d9~F;i9bQezo~o8Orx?o`aaU@c_KfeU=cBvvEc@LftxxNgx2)6Jc%kjt zRnms!)udRU7TTX{khY_Ef80#oFYTYoh3IWOi}wT4&dRHv7Hv@4CwdpwG-gtJm5Lp&SKk;HccZjp zs^i+mvo~nngVHXjM+@{UIcv_!hoqI2U*(>2mdE8LX}x;3tD&jip7%7g16sFJ z+6Bd3(a`W8fcn7dVbawao1Hst{k!(Wa(&-qd|@aIU3VIz4Zi9 zuW6`BthKhINyM?{taz3fqj?wNn)obS$r>;GmBhr6-&iJcHc^L(+Due#!VMESo2X+f zF}JQ4vImfTf$SB;e)aqW`wsJvWRJZzLtr8)Do+SR?ox=(@>Lm z-kN*P6Ye?Won$lY7ox_nh;Ab;Ai_-EhtYx|E5#Cc8mKM|F+lyLJuDPC|AU zBF;$S_jX1hLlSZlSq@ z5ViU4@g_NY?mByjtVR<5OyO?I#9b4aj%AHZ#}aGEY9td`nd}gE$;z6YM6rpLSZON_R^srgHccA%;GON{0(G2|~X@~J+?1$`T}Co-x1R75t)4a3nVd6ExDLp*L9Q3F2atV%>=pg- zevqR<)LTyw^_r;HM7<_w#X2e)%}-FJ$!8dMeTFduxG|O>GBA;qiA+soZ=xa-Rhp=L zEHO#!SI^{%&|D?t$|1V}v7et{KNI_zsAD+qM;*h7)o3XbT1reNaYr%{*F;e(XUNeYX8<`X$eHrSvTis*tQ*dmItE!eF63Mw=l369{|p2G diff --git a/osu.Game.Tournament/Resources/Fonts/Aquatico-Light_0.png b/osu.Game.Tournament/Resources/Fonts/Aquatico-Light_0.png deleted file mode 100644 index 332d9ca0564224402f3b28d295b9ec05d3736822..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68407 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 Rd(f$N8tR(DYOlLK{x5_sc-8;_ diff --git a/osu.Game.Tournament/Resources/Fonts/Aquatico-Regular.bin b/osu.Game.Tournament/Resources/Fonts/Aquatico-Regular.bin deleted file mode 100644 index 3047c2eb3ef7354d250cf4c88b919269a696a06c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4994 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 DisplayedCountSpriteText.Font = value - ? TournamentFont.GetFont(typeface: TournamentTypeface.Aquatico, weight: FontWeight.Regular, size: 60) - : TournamentFont.GetFont(typeface: TournamentTypeface.Aquatico, weight: FontWeight.Light, size: 40); + ? OsuFont.Torus.With(weight: FontWeight.Regular, size: 60) + : OsuFont.Torus.With(weight: FontWeight.Light, size: 40); } } } diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs index 47c923ff30..75d63cde13 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs @@ -201,7 +201,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro new OsuSpriteText { Text = team?.FullName.Value.ToUpper() ?? "???", - Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 40, FontWeight.Light), + Font = OsuFont.Torus.With(size: 40, weight: FontWeight.Light), Colour = Color4.Black, Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, @@ -209,7 +209,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro new OsuSpriteText { Text = teamName.ToUpper(), - Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 20, FontWeight.Regular), + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Regular), Colour = colour, Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index a0216c5db3..b72cacc66f 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tournament.Screens.TeamWin Origin = Anchor.TopCentre, Colour = col, Text = "WINNER", - Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 15, FontWeight.Regular), + Font = OsuFont.Torus.With(size: 15, weight: FontWeight.Regular), }, new OsuSpriteText { @@ -134,7 +134,7 @@ namespace osu.Game.Tournament.Screens.TeamWin Origin = Anchor.TopCentre, Colour = col, Text = match.Round.Value?.Name.Value ?? "Unknown Round", - Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 50, FontWeight.Light), + Font = OsuFont.Torus.With(size: 50, weight: FontWeight.Light), Spacing = new Vector2(10, 0), }, new OsuSpriteText @@ -143,7 +143,7 @@ namespace osu.Game.Tournament.Screens.TeamWin Origin = Anchor.TopCentre, Colour = col, Text = match.Date.Value.ToUniversalTime().ToString("dd MMMM HH:mm UTC"), - Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 20, FontWeight.Light), + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Light), }, } } @@ -203,7 +203,7 @@ namespace osu.Game.Tournament.Screens.TeamWin new OsuSpriteText { Text = team?.FullName.Value.ToUpper() ?? "???", - Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 40, FontWeight.Light), + Font = OsuFont.Torus.With(size: 40, weight: FontWeight.Light), Colour = Color4.Black, Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, diff --git a/osu.Game.Tournament/TournamentFont.cs b/osu.Game.Tournament/TournamentFont.cs deleted file mode 100644 index 32f0264562..0000000000 --- a/osu.Game.Tournament/TournamentFont.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; - -namespace osu.Game.Tournament -{ - public static class TournamentFont - { - /// - /// The default font size. - /// - public const float DEFAULT_FONT_SIZE = 16; - - /// - /// Retrieves a . - /// - /// The font typeface. - /// The size of the text in local space. For a value of 16, a single line will have a height of 16px. - /// The font weight. - /// Whether the font is italic. - /// Whether all characters should be spaced the same distance apart. - /// The . - public static FontUsage GetFont(TournamentTypeface typeface = TournamentTypeface.Aquatico, float size = DEFAULT_FONT_SIZE, FontWeight weight = FontWeight.Medium, bool italics = false, bool fixedWidth = false) - => new FontUsage(GetFamilyString(typeface), size, GetWeightString(typeface, weight), italics, fixedWidth); - - /// - /// Retrieves the string representation of a . - /// - /// The . - /// The string representation. - public static string GetFamilyString(TournamentTypeface typeface) - { - switch (typeface) - { - case TournamentTypeface.Aquatico: - return "Aquatico"; - } - - return null; - } - - /// - /// Retrieves the string representation of a . - /// - /// The . - /// The . - /// The string representation of in the specified . - public static string GetWeightString(TournamentTypeface typeface, FontWeight weight) - => GetWeightString(GetFamilyString(typeface), weight); - - /// - /// Retrieves the string representation of a . - /// - /// The family string. - /// The . - /// The string representation of in the specified . - public static string GetWeightString(string family, FontWeight weight) - { - string weightString = weight.ToString(); - - // Only exo has an explicit "regular" weight, other fonts do not - if (weight == FontWeight.Regular && family != GetFamilyString(TournamentTypeface.Aquatico)) - weightString = string.Empty; - - return weightString; - } - } - - public enum TournamentTypeface - { - Aquatico - } -} diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 1c94856a4e..9916b0e042 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -54,9 +54,6 @@ namespace osu.Game.Tournament { Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); - AddFont(Resources, @"Resources/Fonts/Aquatico-Regular"); - AddFont(Resources, @"Resources/Fonts/Aquatico-Light"); - Textures.AddStore(new TextureLoaderStore(new ResourceStore(new StorageBackedResourceStore(storage)))); this.storage = storage; diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 22250d4a56..54a25875bf 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -19,6 +19,8 @@ namespace osu.Game.Graphics public static FontUsage Numeric => GetFont(Typeface.Venera, weight: FontWeight.Regular); + public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular); + /// /// Retrieves a . /// @@ -45,6 +47,9 @@ namespace osu.Game.Graphics case Typeface.Venera: return "Venera"; + + case Typeface.Torus: + return "Torus"; } return null; @@ -65,16 +70,7 @@ namespace osu.Game.Graphics /// The family string. /// The . /// The string representation of in the specified . - public static string GetWeightString(string family, FontWeight weight) - { - string weightString = weight.ToString(); - - // Only exo has an explicit "regular" weight, other fonts do not - if (family != GetFamilyString(Typeface.Exo) && weight == FontWeight.Regular) - weightString = string.Empty; - - return weightString; - } + public static string GetWeightString(string family, FontWeight weight) => weight.ToString(); } public static class OsuFontExtensions @@ -102,6 +98,7 @@ namespace osu.Game.Graphics { Exo, Venera, + Torus } public enum FontWeight diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 07c9d37a86..676b4b6c7a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -157,6 +157,11 @@ namespace osu.Game AddFont(Resources, @"Fonts/Exo2.0-Black"); AddFont(Resources, @"Fonts/Exo2.0-BlackItalic"); + AddFont(Resources, @"Fonts/Torus-SemiBold"); + AddFont(Resources, @"Fonts/Torus-Bold"); + AddFont(Resources, @"Fonts/Torus-Regular"); + AddFont(Resources, @"Fonts/Torus-Light"); + AddFont(Resources, @"Fonts/Venera"); AddFont(Resources, @"Fonts/Venera-Light"); AddFont(Resources, @"Fonts/Venera-Medium"); From 08756186e983d533d739979cdcfaeca61c5a89f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 18:35:28 +0900 Subject: [PATCH 115/192] 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 983c622f77..d62011bf1d 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 3b00b807a2..e407fca8b7 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 3552047cf8..cc0b8074bb 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + From a2273234cb9009745fc7c6b2eb080d720a584717 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 18:09:25 +0900 Subject: [PATCH 116/192] Better handle startup when ladder cannot be read correctly --- osu.Game.Tournament/TournamentGameBase.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 9916b0e042..69f2bf57be 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -121,10 +121,9 @@ namespace osu.Game.Tournament using (var sr = new StreamReader(stream)) ladder = JsonConvert.DeserializeObject(sr.ReadToEnd()); } - else - { + + if (ladder == null) ladder = new LadderInfo(); - } if (ladder.Ruleset.Value == null) ladder.Ruleset.Value = RulesetStore.AvailableRulesets.First(); From 372060bc2b37dd5547defdfe1455e38f85e90310 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 18:11:21 +0900 Subject: [PATCH 117/192] Only trigger changes if new user information is actually populated --- osu.Game.Tournament/TournamentGameBase.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 69f2bf57be..dd93215fc1 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -201,9 +201,11 @@ namespace osu.Game.Tournament { foreach (var p in t.Players) { - if (p.Username == null || p.Statistics == null) + if (string.IsNullOrEmpty(p.Username) || p.Statistics == null) + { PopulateUser(p); - addedInfo = true; + addedInfo = true; + } } } From 093f2affdffcc95fb91dec285954e823bbc229d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 18:12:42 +0900 Subject: [PATCH 118/192] Add seeding screen --- .../Screens/TestSceneSeedingScreen.cs | 125 +++++++ osu.Game.Tournament/Models/SeedingBeatmap.cs | 23 ++ osu.Game.Tournament/Models/SeedingResult.cs | 21 ++ osu.Game.Tournament/Models/TournamentTeam.cs | 27 ++ .../Screens/Editors/SeedingEditor.cs | 288 ++++++++++++++++ .../Screens/Editors/TeamEditorScreen.cs | 27 +- .../Ladder/Components/LadderEditorSettings.cs | 43 --- .../Components/LadderSettingsDropdown.cs | 26 ++ .../Ladder/Components/SettingsTeamDropdown.cs | 55 +++ .../Screens/TeamIntro/SeedingScreen.cs | 316 ++++++++++++++++++ osu.Game.Tournament/TournamentGameBase.cs | 18 + osu.Game.Tournament/TournamentSceneManager.cs | 14 + osu.Game.Tournament/TournamentSpriteText.cs | 16 + 13 files changed, 955 insertions(+), 44 deletions(-) create mode 100644 osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs create mode 100644 osu.Game.Tournament/Models/SeedingBeatmap.cs create mode 100644 osu.Game.Tournament/Models/SeedingResult.cs create mode 100644 osu.Game.Tournament/Screens/Editors/SeedingEditor.cs create mode 100644 osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs create mode 100644 osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs create mode 100644 osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs create mode 100644 osu.Game.Tournament/TournamentSpriteText.cs diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs new file mode 100644 index 0000000000..eb46d75705 --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs @@ -0,0 +1,125 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.TeamIntro; +using osu.Game.Users; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneSeedingScreen : LadderTestScene + { + [Cached] + private readonly LadderInfo ladder = new LadderInfo(); + + [BackgroundDependencyLoader] + private void load() + { + ladder.CurrentMatch.Value = new TournamentMatch + { + Team1 = + { + Value = new TournamentTeam + { + FlagName = { Value = "JP" }, + FullName = { Value = "Japan" }, + LastYearPlacing = { Value = 10 }, + Seed = { Value = "Low" }, + SeedingResults = + { + new SeedingResult + { + Mod = { Value = "NM" }, + Seed = { Value = 10 }, + Beatmaps = + { + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 12345672, + Seed = { Value = 24 }, + }, + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 1234567, + Seed = { Value = 12 }, + }, + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 1234567, + Seed = { Value = 16 }, + } + } + }, + new SeedingResult + { + Mod = { Value = "DT" }, + Seed = { Value = 5 }, + Beatmaps = + { + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 234567, + Seed = { Value = 3 }, + }, + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 234567, + Seed = { Value = 6 }, + }, + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 234567, + Seed = { Value = 12 }, + } + } + } + }, + Players = + { + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } }, + } + } + }, + Team2 = + { + Value = new TournamentTeam + { + FlagName = { Value = "US" }, + FullName = { Value = "United States" }, + Players = + { + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + } + } + }, + Round = + { + Value = new TournamentRound { Name = { Value = "Quarterfinals" } } + } + }; + + Add(new SeedingScreen + { + FillMode = FillMode.Fit, + FillAspectRatio = 16 / 9f + }); + } + } +} diff --git a/osu.Game.Tournament/Models/SeedingBeatmap.cs b/osu.Game.Tournament/Models/SeedingBeatmap.cs new file mode 100644 index 0000000000..2cd6fa7188 --- /dev/null +++ b/osu.Game.Tournament/Models/SeedingBeatmap.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Beatmaps; + +namespace osu.Game.Tournament.Models +{ + public class SeedingBeatmap + { + public int ID; + + public BeatmapInfo BeatmapInfo; + + public long Score; + + public Bindable Seed = new BindableInt + { + MinValue = 1, + MaxValue = 64 + }; + } +} diff --git a/osu.Game.Tournament/Models/SeedingResult.cs b/osu.Game.Tournament/Models/SeedingResult.cs new file mode 100644 index 0000000000..87aaf8bf36 --- /dev/null +++ b/osu.Game.Tournament/Models/SeedingResult.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Bindables; + +namespace osu.Game.Tournament.Models +{ + public class SeedingResult + { + public List Beatmaps = new List(); + + public Bindable Mod = new Bindable(); + + public Bindable Seed = new BindableInt + { + MinValue = 1, + MaxValue = 64 + }; + } +} diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs index 54b8a35180..7fca75cea4 100644 --- a/osu.Game.Tournament/Models/TournamentTeam.cs +++ b/osu.Game.Tournament/Models/TournamentTeam.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Game.Users; @@ -29,6 +30,32 @@ namespace osu.Game.Tournament.Models /// public Bindable Acronym = new Bindable(string.Empty); + public BindableList SeedingResults = new BindableList(); + + public double AverageRank + { + get + { + var ranks = Players.Select(p => p.Statistics?.Ranks.Global) + .Where(i => i.HasValue) + .Select(i => i.Value) + .ToArray(); + + if (ranks.Length == 0) + return 0; + + return ranks.Average(); + } + } + + public Bindable Seed = new Bindable(string.Empty); + + public Bindable LastYearPlacing = new BindableInt + { + MinValue = 1, + MaxValue = 64 + }; + [JsonProperty] public BindableList Players { get; set; } = new BindableList(); diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditor.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditor.cs new file mode 100644 index 0000000000..68003a1c43 --- /dev/null +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditor.cs @@ -0,0 +1,288 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; +using osuTK; + +namespace osu.Game.Tournament.Screens.Editors +{ + public class SeedingEditorScreen : TournamentEditorScreen + { + private readonly TournamentTeam team; + + protected override BindableList Storage => team.SeedingResults; + + public SeedingEditorScreen(TournamentTeam team) + { + this.team = team; + } + + public class SeeingResultRow : CompositeDrawable, IModelBacked + { + public SeedingResult Model { get; } + + [Resolved] + private LadderInfo ladderInfo { get; set; } + + public SeeingResultRow(TournamentTeam team, SeedingResult round) + { + Model = round; + + Masking = true; + CornerRadius = 10; + + SeedingBeatmapEditor beatmapEditor = new SeedingBeatmapEditor(round) + { + Width = 0.95f + }; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = OsuColour.Gray(0.1f), + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Margin = new MarginPadding(5), + Padding = new MarginPadding { Right = 160 }, + Spacing = new Vector2(5), + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new SettingsTextBox + { + LabelText = "Mod", + Width = 0.33f, + Bindable = Model.Mod + }, + new SettingsSlider + { + LabelText = "Seed", + Width = 0.33f, + Bindable = Model.Seed + }, + new SettingsButton + { + Width = 0.2f, + Margin = new MarginPadding(10), + Text = "Add beatmap", + Action = () => beatmapEditor.CreateNew() + }, + beatmapEditor + } + }, + new DangerousSettingsButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.None, + Width = 150, + Text = "Delete SeeingResult", + Action = () => + { + Expire(); + team.SeedingResults.Remove(Model); + }, + } + }; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + } + + public class SeedingBeatmapEditor : CompositeDrawable + { + private readonly SeedingResult round; + private readonly FillFlowContainer flow; + + public SeedingBeatmapEditor(SeedingResult round) + { + this.round = round; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + LayoutDuration = 200, + LayoutEasing = Easing.OutQuint, + ChildrenEnumerable = round.Beatmaps.Select(p => new SeedingBeatmapRow(round, p)) + }; + } + + public void CreateNew() + { + var user = new SeedingBeatmap(); + round.Beatmaps.Add(user); + flow.Add(new SeedingBeatmapRow(round, user)); + } + + public class SeedingBeatmapRow : CompositeDrawable + { + private readonly SeedingResult result; + public SeedingBeatmap Model { get; } + + [Resolved] + protected IAPIProvider API { get; private set; } + + private readonly Bindable beatmapId = new Bindable(); + + private readonly Bindable score = new Bindable(); + + private readonly Container drawableContainer; + + public SeedingBeatmapRow(SeedingResult result, SeedingBeatmap beatmap) + { + this.result = result; + Model = beatmap; + + Margin = new MarginPadding(10); + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = OsuColour.Gray(0.2f), + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Margin = new MarginPadding(5), + Padding = new MarginPadding { Right = 160 }, + Spacing = new Vector2(5), + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new SettingsNumberBox + { + LabelText = "Beatmap ID", + RelativeSizeAxes = Axes.None, + Width = 200, + Bindable = beatmapId, + }, + new SettingsSlider + { + LabelText = "Seed", + RelativeSizeAxes = Axes.None, + Width = 200, + Bindable = beatmap.Seed + }, + new SettingsTextBox + { + LabelText = "Score", + RelativeSizeAxes = Axes.None, + Width = 200, + Bindable = score, + }, + drawableContainer = new Container + { + Size = new Vector2(100, 70), + }, + } + }, + new DangerousSettingsButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.None, + Width = 150, + Text = "Delete Beatmap", + Action = () => + { + Expire(); + result.Beatmaps.Remove(beatmap); + }, + } + }; + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + beatmapId.Value = Model.ID.ToString(); + beatmapId.BindValueChanged(idString => + { + int parsed; + + int.TryParse(idString.NewValue, out parsed); + + Model.ID = parsed; + + if (idString.NewValue != idString.OldValue) + Model.BeatmapInfo = null; + + if (Model.BeatmapInfo != null) + { + updatePanel(); + return; + } + + var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = Model.ID }); + + req.Success += res => + { + Model.BeatmapInfo = res.ToBeatmap(rulesets); + updatePanel(); + }; + + req.Failure += _ => + { + Model.BeatmapInfo = null; + updatePanel(); + }; + + API.Queue(req); + }, true); + + score.Value = Model.Score.ToString(); + score.BindValueChanged(str => long.TryParse(str.NewValue, out Model.Score)); + } + + private void updatePanel() + { + drawableContainer.Clear(); + + if (Model.BeatmapInfo != null) + { + drawableContainer.Child = new TournamentBeatmapPanel(Model.BeatmapInfo, result.Mod.Value) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 300 + }; + } + } + } + } + } + + protected override SeeingResultRow CreateDrawable(SeedingResult model) => new SeeingResultRow(team, model); + } +} diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 494dd73edd..d6fa1a29a8 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -57,6 +57,9 @@ namespace osu.Game.Tournament.Screens.Editors private readonly Container drawableContainer; + [Resolved(canBeNull: true)] + private TournamentSceneManager sceneManager { get; set; } + [Resolved] private LadderInfo ladderInfo { get; set; } @@ -113,6 +116,18 @@ namespace osu.Game.Tournament.Screens.Editors Width = 0.2f, Bindable = Model.FlagName }, + new SettingsTextBox + { + LabelText = "Seed", + Width = 0.2f, + Bindable = Model.Seed + }, + new SettingsSlider + { + LabelText = "Last Year Placement", + Width = 0.33f, + Bindable = Model.LastYearPlacing + }, new SettingsButton { Width = 0.11f, @@ -131,7 +146,17 @@ namespace osu.Game.Tournament.Screens.Editors ladderInfo.Teams.Remove(Model); }, }, - playerEditor + playerEditor, + new SettingsButton + { + Width = 0.2f, + Margin = new MarginPadding(10), + Text = "Edit seeding results", + Action = () => + { + sceneManager.SetScreen(new SeedingEditorScreen(team)); + } + }, } }, }; diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index 8ab083ddaf..672b7ecc02 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -127,49 +127,6 @@ namespace osu.Game.Tournament.Screens.Ladder.Components } } - private class SettingsTeamDropdown : LadderSettingsDropdown - { - public SettingsTeamDropdown(BindableList teams) - { - foreach (var t in teams.Prepend(new TournamentTeam())) - add(t); - - teams.CollectionChanged += (_, args) => - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - args.NewItems.Cast().ForEach(add); - break; - - case NotifyCollectionChangedAction.Remove: - args.OldItems.Cast().ForEach(i => Control.RemoveDropdownItem(i)); - break; - } - }; - } - - private readonly List refBindables = new List(); - - private T boundReference(T obj) - where T : IBindable - { - obj = (T)obj.GetBoundCopy(); - refBindables.Add(obj); - return obj; - } - - private void add(TournamentTeam team) - { - Control.AddDropdownItem(team); - boundReference(team.FullName).BindValueChanged(_ => - { - Control.RemoveDropdownItem(team); - Control.AddDropdownItem(team); - }); - } - } - private class LadderSettingsDropdown : SettingsDropdown { protected override OsuDropdown CreateDropdown() => new DropdownControl(); diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs new file mode 100644 index 0000000000..347e4d91e0 --- /dev/null +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Tournament.Screens.Ladder.Components +{ + public class LadderSettingsDropdown : SettingsDropdown + { + protected override OsuDropdown CreateDropdown() => new DropdownControl(); + + private new class DropdownControl : SettingsDropdown.DropdownControl + { + protected override DropdownMenu CreateMenu() => new Menu(); + + private new class Menu : OsuDropdownMenu + { + public Menu() + { + MaxHeight = 200; + } + } + } + } +} diff --git a/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs b/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs new file mode 100644 index 0000000000..a630e51e44 --- /dev/null +++ b/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.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.Collections.Specialized; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Tournament.Models; + +namespace osu.Game.Tournament.Screens.Ladder.Components +{ + public class SettingsTeamDropdown : LadderSettingsDropdown + { + public SettingsTeamDropdown(BindableList teams) + { + foreach (var t in teams.Prepend(new TournamentTeam())) + add(t); + + teams.CollectionChanged += (_, args) => + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + args.NewItems.Cast().ForEach(add); + break; + + case NotifyCollectionChangedAction.Remove: + args.OldItems.Cast().ForEach(i => Control.RemoveDropdownItem(i)); + break; + } + }; + } + + private readonly List refBindables = new List(); + + private T boundReference(T obj) + where T : IBindable + { + obj = (T)obj.GetBoundCopy(); + refBindables.Add(obj); + return obj; + } + + private void add(TournamentTeam team) + { + Control.AddDropdownItem(team); + boundReference(team.FullName).BindValueChanged(_ => + { + Control.RemoveDropdownItem(team); + Control.AddDropdownItem(team); + }); + } + } +} diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs new file mode 100644 index 0000000000..db5363c155 --- /dev/null +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -0,0 +1,316 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Platform; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.Ladder.Components; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tournament.Screens.TeamIntro +{ + public class SeedingScreen : TournamentScreen, IProvideVideo + { + private Container mainContainer; + + private readonly Bindable currentMatch = new Bindable(); + + private readonly Bindable currentTeam = new Bindable(); + + [BackgroundDependencyLoader] + private void load(Storage storage) + { + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new TourneyVideo(storage.GetStream(@"videos/seeding.m4v")) + { + RelativeSizeAxes = Axes.Both, + Loop = true, + }, + mainContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + new ControlPanel + { + Children = new Drawable[] + { + new TourneyButton + { + RelativeSizeAxes = Axes.X, + Text = "Show first team", + Action = () => currentTeam.Value = currentMatch.Value.Team1.Value, + }, + new TourneyButton + { + RelativeSizeAxes = Axes.X, + Text = "Show second team", + Action = () => currentTeam.Value = currentMatch.Value.Team2.Value, + }, + new SettingsTeamDropdown(LadderInfo.Teams) + { + LabelText = "Show specific team", + Bindable = currentTeam, + } + } + } + }; + + currentMatch.BindValueChanged(matchChanged); + currentMatch.BindTo(LadderInfo.CurrentMatch); + + currentTeam.BindValueChanged(teamChanged, true); + } + + private void teamChanged(ValueChangedEvent team) + { + if (team.NewValue == null) + { + mainContainer.Clear(); + return; + } + + showTeam(team.NewValue); + } + + private void matchChanged(ValueChangedEvent match) => + currentTeam.Value = currentMatch.Value.Team1.Value; + + private void showTeam(TournamentTeam team) + { + mainContainer.Children = new Drawable[] + { + new LeftInfo(team) { Position = new Vector2(55, 150), }, + new RightInfo(team) { Position = new Vector2(500, 150), }, + }; + } + + private class RightInfo : CompositeDrawable + { + public RightInfo(TournamentTeam team) + { + FillFlowContainer fill; + + Width = 400; + + InternalChildren = new Drawable[] + { + fill = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + }; + + foreach (var seeding in team.SeedingResults) + { + fill.Add(new ModRow(seeding.Mod.Value, seeding.Seed.Value)); + foreach (var beatmap in seeding.Beatmaps) + fill.Add(new BeatmapScoreRow(beatmap)); + } + } + + private class BeatmapScoreRow : CompositeDrawable + { + public BeatmapScoreRow(SeedingBeatmap beatmap) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new TournamentSpriteText { Text = beatmap.BeatmapInfo.Metadata.Title, Colour = Color4.Black, }, + new TournamentSpriteText { Text = "by", Colour = Color4.Black, Font = OsuFont.Torus.With(weight: FontWeight.Regular) }, + new TournamentSpriteText { Text = beatmap.BeatmapInfo.Metadata.Artist, Colour = Color4.Black, Font = OsuFont.Torus.With(weight: FontWeight.Regular) }, + } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(40), + Children = new Drawable[] + { + new TournamentSpriteText { Text = beatmap.Score.ToString("#,0"), Colour = Color4.Black, Width = 80 }, + new TournamentSpriteText { Text = "#" + beatmap.Seed.Value.ToString("#,0"), Colour = Color4.Black, Font = OsuFont.Torus.With(weight: FontWeight.Regular) }, + } + }, + }; + } + } + + private class ModRow : CompositeDrawable + { + private readonly string mods; + private readonly int seeding; + + public ModRow(string mods, int seeding) + { + this.mods = mods; + this.seeding = seeding; + + Padding = new MarginPadding { Vertical = 10 }; + + AutoSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new Sprite + { + Texture = textures.Get($"mods/{mods.ToLower()}"), + Scale = new Vector2(0.5f) + }, + new Container + { + Size = new Vector2(50, 16), + CornerRadius = 10, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + new TournamentSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = seeding.ToString("#,0"), + }, + } + }, + } + }, + }; + } + } + } + + private class LeftInfo : CompositeDrawable + { + public LeftInfo(TournamentTeam team) + { + FillFlowContainer fill; + + Width = 200; + + if (team == null) return; + + InternalChildren = new Drawable[] + { + fill = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new TeamDisplay(team) { Margin = new MarginPadding { Bottom = 30 } }, + new RowDisplay("Average Rank:", $"#{team.AverageRank:#,0}"), + new RowDisplay("Seed:", team.Seed.Value), + new RowDisplay("Last year's placing:", team.LastYearPlacing.Value > 0 ? $"#{team.LastYearPlacing:#,0}" : "0"), + new Container { Margin = new MarginPadding { Bottom = 30 } }, + } + }, + }; + + foreach (var p in team.Players) + fill.Add(new RowDisplay(p.Username, p.Statistics?.Ranks.Global?.ToString("\\##,0") ?? "-")); + } + + internal class RowDisplay : CompositeDrawable + { + public RowDisplay(string left, string right) + { + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + + var colour = OsuColour.Gray(0.3f); + + InternalChildren = new Drawable[] + { + new TournamentSpriteText + { + Text = left, + Colour = colour, + Font = OsuFont.Torus.With(size: 22), + }, + new TournamentSpriteText + { + Text = right, + Colour = colour, + Anchor = Anchor.TopRight, + Origin = Anchor.TopLeft, + Font = OsuFont.Torus.With(size: 22, weight: FontWeight.Regular), + }, + }; + } + } + + private class TeamDisplay : DrawableTournamentTeam + { + public TeamDisplay(TournamentTeam team) + : base(team) + { + AutoSizeAxes = Axes.Both; + + Flag.RelativeSizeAxes = Axes.None; + Flag.Size = new Vector2(300, 200); + Flag.Scale = new Vector2(0.3f); + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + Flag, + new OsuSpriteText + { + Text = team?.FullName.Value ?? "???", + Font = OsuFont.Torus.With(size: 32, weight: FontWeight.SemiBold), + Colour = Color4.Black, + }, + } + }; + } + } + } + } +} diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index dd93215fc1..64a5618d73 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -241,6 +241,24 @@ namespace osu.Game.Tournament } } + foreach (var t in ladder.Teams) + { + foreach (var s in t.SeedingResults) + { + foreach (var b in s.Beatmaps) + { + if (b.BeatmapInfo == null && b.ID > 0) + { + var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID }); + req.Perform(API); + b.BeatmapInfo = req.Result?.ToBeatmap(RulesetStore); + + addedInfo = true; + } + } + } + } + return addedInfo; } diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index de3d685c31..38df40772f 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -80,6 +80,7 @@ namespace osu.Game.Tournament new ShowcaseScreen(), new MapPoolScreen(), new TeamIntroScreen(), + new SeedingScreen(), new DrawingsScreen(), new GameplayScreen(), new TeamWinScreen() @@ -121,6 +122,7 @@ namespace osu.Game.Tournament new ScreenButton(typeof(LadderScreen)) { Text = "Bracket", RequestSelection = SetScreen }, new Separator(), new ScreenButton(typeof(TeamIntroScreen)) { Text = "TeamIntro", RequestSelection = SetScreen }, + new ScreenButton(typeof(SeedingScreen)) { Text = "Seeding", RequestSelection = SetScreen }, new Separator(), new ScreenButton(typeof(MapPoolScreen)) { Text = "MapPool", RequestSelection = SetScreen }, new ScreenButton(typeof(GameplayScreen)) { Text = "Gameplay", RequestSelection = SetScreen }, @@ -146,8 +148,20 @@ namespace osu.Game.Tournament private Drawable currentScreen; private ScheduledDelegate scheduledHide; + private Drawable temporaryScreen; + + public void SetScreen(Drawable screen) + { + currentScreen?.Hide(); + currentScreen = null; + + screens.Add(temporaryScreen = screen); + } + public void SetScreen(Type screenType) { + temporaryScreen?.Expire(); + var target = screens.FirstOrDefault(s => s.GetType() == screenType); if (target == null || currentScreen == target) return; diff --git a/osu.Game.Tournament/TournamentSpriteText.cs b/osu.Game.Tournament/TournamentSpriteText.cs new file mode 100644 index 0000000000..e550dfbfae --- /dev/null +++ b/osu.Game.Tournament/TournamentSpriteText.cs @@ -0,0 +1,16 @@ +// 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.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Tournament +{ + public class TournamentSpriteText : OsuSpriteText + { + public TournamentSpriteText() + { + Font = OsuFont.Torus; + } + } +} From b6edd17242a2f94da3fe03e9c2bf2833b0d6bdec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 18:59:23 +0900 Subject: [PATCH 119/192] Fix TeamWin test scene --- osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs index 5cb35a506f..1a2faa76c1 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs @@ -21,6 +21,7 @@ namespace osu.Game.Tournament.Tests.Screens match.Team1.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "USA"); match.Team2.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "JPN"); match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals"); + match.Completed.Value = true; ladder.CurrentMatch.Value = match; Add(new TeamWinScreen From 1b355d02d6910b596a547670a1c6ffaaffc0fbd0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 19:10:29 +0900 Subject: [PATCH 120/192] Update video resource paths --- osu.Game.Tournament/Screens/Ladder/LadderScreen.cs | 2 +- osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs | 2 +- osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs | 2 +- osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs | 4 ++-- osu.Game.Tournament/TournamentSceneManager.cs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index 8ea366e1b4..5528c9e9f5 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tournament.Screens.Ladder RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new TourneyVideo(storage.GetStream(@"BG Side Logo - OWC.m4v")) + new TourneyVideo(storage.GetStream(@"videos/ladder.m4v")) { RelativeSizeAxes = Axes.Both, Loop = true, diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index 4b46264055..140ae54bfb 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tournament.Screens.Schedule InternalChildren = new Drawable[] { - new TourneyVideo(storage.GetStream(@"BG Side Logo - OWC.m4v")) + new TourneyVideo(storage.GetStream(@"videos/schedule.m4v")) { RelativeSizeAxes = Axes.Both, Loop = true, diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs index 75d63cde13..bff5c6aac3 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro InternalChildren = new Drawable[] { - new TourneyVideo(storage.GetStream(@"BG Team - Both OWC.m4v")) + new TourneyVideo(storage.GetStream(@"videos/teamintro.m4v")) { RelativeSizeAxes = Axes.Both, Loop = true, diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index b72cacc66f..cc1f2a73ae 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -33,13 +33,13 @@ namespace osu.Game.Tournament.Screens.TeamWin InternalChildren = new Drawable[] { - blueWinVideo = new TourneyVideo(storage.GetStream(@"BG Team - Win Blue.m4v")) + blueWinVideo = new TourneyVideo(storage.GetStream(@"videos/teamwin-blue.m4v")) { Alpha = 1, RelativeSizeAxes = Axes.Both, Loop = true, }, - redWinVideo = new TourneyVideo(storage.GetStream(@"BG Team - Win Red.m4v")) + redWinVideo = new TourneyVideo(storage.GetStream(@"videos/teamwin-red.m4v")) { Alpha = 0, RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 38df40772f..9f5f2b6827 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tournament //Masking = true, Children = new Drawable[] { - video = new TourneyVideo(storage.GetStream("BG Logoless - OWC.m4v")) + video = new TourneyVideo(storage.GetStream("videos/main.m4v")) { Loop = true, RelativeSizeAxes = Axes.Both, From e678a068b6ecca9df29c408c7e4858c75f39e477 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 19:14:44 +0900 Subject: [PATCH 121/192] Bring design up-to-date with 2019 standards --- .../Components/ControlPanel.cs | 3 +- .../Components/DrawableTournamentTeam.cs | 7 +- osu.Game.Tournament/Components/SongBar.cs | 7 +- .../Components/TournamentBeatmapPanel.cs | 21 +-- .../Screens/Drawings/Components/Group.cs | 7 +- .../Screens/Drawings/DrawingsScreen.cs | 9 +- .../Gameplay/Components/MatchHeader.cs | 48 ++--- .../Ladder/Components/DrawableMatchTeam.cs | 11 +- .../Components/DrawableTournamentRound.cs | 11 +- .../Screens/MapPool/MapPoolScreen.cs | 3 +- .../Screens/Schedule/ScheduleScreen.cs | 15 +- osu.Game.Tournament/Screens/SetupScreen.cs | 5 +- .../Screens/Showcase/ShowcaseScreen.cs | 2 +- .../Screens/Showcase/TournamentLogo.cs | 16 +- .../Screens/TeamIntro/TeamIntroScreen.cs | 76 +++----- .../Screens/TeamWin/TeamWinScreen.cs | 176 +++++++----------- osu.Game.Tournament/TournamentGame.cs | 4 + osu.Game.Tournament/TournamentGameBase.cs | 5 +- 18 files changed, 168 insertions(+), 258 deletions(-) diff --git a/osu.Game.Tournament/Components/ControlPanel.cs b/osu.Game.Tournament/Components/ControlPanel.cs index a9bb1bf42f..fa5c941f1a 100644 --- a/osu.Game.Tournament/Components/ControlPanel.cs +++ b/osu.Game.Tournament/Components/ControlPanel.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -35,7 +34,7 @@ namespace osu.Game.Tournament.Components RelativeSizeAxes = Axes.Both, Colour = new Color4(54, 54, 54, 255) }, - new OsuSpriteText + new TournamentSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs index 361bd92770..99116d4a17 100644 --- a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs +++ b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Components @@ -19,7 +18,7 @@ namespace osu.Game.Tournament.Components public readonly TournamentTeam Team; protected readonly Sprite Flag; - protected readonly OsuSpriteText AcronymText; + protected readonly TournamentSpriteText AcronymText; [UsedImplicitly] private Bindable acronym; @@ -37,9 +36,9 @@ namespace osu.Game.Tournament.Components FillMode = FillMode.Fit }; - AcronymText = new OsuSpriteText + AcronymText = new TournamentSpriteText { - Font = OsuFont.GetFont(weight: FontWeight.Regular), + Font = OsuFont.Torus.With(weight: FontWeight.Regular), }; } diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 8a46da9565..48ea36a8f3 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using osu.Game.Screens.Menu; using osuTK; @@ -262,7 +261,7 @@ namespace osu.Game.Tournament.Components static void cp(SpriteText s, Color4 colour) { s.Colour = colour; - s.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 15); + s.Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 15); } for (var i = 0; i < tuples.Length; i++) @@ -278,9 +277,9 @@ namespace osu.Game.Tournament.Components }); } - AddText(new OsuSpriteText { Text = heading }, s => cp(s, OsuColour.Gray(0.33f))); + AddText(new TournamentSpriteText { Text = heading }, s => cp(s, OsuColour.Gray(0.33f))); AddText(" ", s => cp(s, OsuColour.Gray(0.33f))); - AddText(new OsuSpriteText { Text = content }, s => cp(s, OsuColour.Gray(0.5f))); + AddText(new TournamentSpriteText { Text = content }, s => cp(s, OsuColour.Gray(0.5f))); } } } diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 51483a0964..394ffe304e 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -15,7 +15,6 @@ using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Tournament.Models; using osuTK; using osuTK.Graphics; @@ -77,14 +76,14 @@ namespace osu.Game.Tournament.Components Direction = FillDirection.Vertical, Children = new Drawable[] { - new OsuSpriteText + new TournamentSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = new LocalisedString(( $"{Beatmap.Metadata.ArtistUnicode ?? Beatmap.Metadata.Artist} - {Beatmap.Metadata.TitleUnicode ?? Beatmap.Metadata.Title}", $"{Beatmap.Metadata.Artist} - {Beatmap.Metadata.Title}")), - Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true), + Font = OsuFont.Torus.With(weight: FontWeight.Bold), }, new FillFlowContainer { @@ -95,28 +94,28 @@ namespace osu.Game.Tournament.Components Direction = FillDirection.Horizontal, Children = new Drawable[] { - new OsuSpriteText + new TournamentSpriteText { Text = "mapper", Padding = new MarginPadding { Right = 5 }, - Font = OsuFont.GetFont(italics: true, weight: FontWeight.Regular, size: 14) + Font = OsuFont.Torus.With(weight: FontWeight.Regular, size: 14) }, - new OsuSpriteText + new TournamentSpriteText { Text = Beatmap.Metadata.AuthorString, Padding = new MarginPadding { Right = 20 }, - Font = OsuFont.GetFont(italics: true, weight: FontWeight.Bold, size: 14) + Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 14) }, - new OsuSpriteText + new TournamentSpriteText { Text = "difficulty", Padding = new MarginPadding { Right = 5 }, - Font = OsuFont.GetFont(italics: true, weight: FontWeight.Regular, size: 14) + Font = OsuFont.Torus.With(weight: FontWeight.Regular, size: 14) }, - new OsuSpriteText + new TournamentSpriteText { Text = Beatmap.Version, - Font = OsuFont.GetFont(italics: true, weight: FontWeight.Bold, size: 14) + Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 14) }, } } diff --git a/osu.Game.Tournament/Screens/Drawings/Components/Group.cs b/osu.Game.Tournament/Screens/Drawings/Components/Group.cs index 549ff26018..4126f2db65 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/Group.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/Group.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osuTK; @@ -43,7 +42,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components Colour = new Color4(54, 54, 54, 255) }, // Group name - new OsuSpriteText + new TournamentSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -51,7 +50,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components Position = new Vector2(0, 7f), Text = $"GROUP {name.ToUpperInvariant()}", - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 8), + Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 8), Colour = new Color4(255, 204, 34, 255), }, teams = new FillFlowContainer @@ -134,7 +133,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components AcronymText.Anchor = Anchor.TopCentre; AcronymText.Origin = Anchor.TopCentre; AcronymText.Text = team.Acronym.Value.ToUpperInvariant(); - AcronymText.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 10); + AcronymText.Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 10); InternalChildren = new Drawable[] { diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 5efa0a1e69..8be66ff98c 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -14,7 +14,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.Drawings.Components; @@ -29,7 +28,7 @@ namespace osu.Game.Tournament.Screens.Drawings private ScrollingTeamContainer teamsContainer; private GroupContainer groupsContainer; - private OsuSpriteText fullTeamNameText; + private TournamentSpriteText fullTeamNameText; private readonly List allTeams = new List(); @@ -109,18 +108,18 @@ namespace osu.Game.Tournament.Screens.Drawings RelativeSizeAxes = Axes.X, }, // Scrolling team name - fullTeamNameText = new OsuSpriteText + fullTeamNameText = new TournamentSpriteText { Anchor = Anchor.Centre, Origin = Anchor.TopCentre, Position = new Vector2(0, 45f), - Colour = OsuColour.Gray(0.33f), + Colour = OsuColour.Gray(0.95f), Alpha = 0, - Font = OsuFont.GetFont(weight: FontWeight.Light, size: 42), + Font = OsuFont.Torus.With(weight: FontWeight.Light, size: 42), } } }, diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs index d8f2df2e93..ce17c392d0 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs @@ -5,9 +5,9 @@ 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.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; @@ -30,7 +30,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components new TournamentLogo(), new RoundDisplay { - Y = 10, + Y = 5, Anchor = Anchor.BottomCentre, Origin = Anchor.TopCentre, }, @@ -51,9 +51,6 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components { private readonly TeamColour teamColour; - private readonly Color4 red = new Color4(129, 68, 65, 255); - private readonly Color4 blue = new Color4(41, 91, 97, 255); - private readonly Bindable currentMatch = new Bindable(); private readonly Bindable currentTeam = new Bindable(); private readonly Bindable currentTeamScore = new Bindable(); @@ -106,7 +103,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components private void teamChanged(TournamentTeam team) { - var colour = teamColour == TeamColour.Red ? red : blue; + var colour = teamColour == TeamColour.Red ? TournamentGame.COLOUR_RED : TournamentGame.COLOUR_BLUE; var flip = teamColour != TeamColour.Red; InternalChildren = new Drawable[] @@ -169,7 +166,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components Children = new Drawable[] { Flag, - new OsuSpriteText + new TournamentSpriteText { Text = team?.FullName.Value.ToUpper() ?? "???", X = (flip ? -1 : 1) * 90, @@ -188,10 +185,31 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components { private readonly Bindable currentMatch = new Bindable(); + private readonly TournamentSpriteText text; + public RoundDisplay() { Width = 200; Height = 20; + + Masking = true; + CornerRadius = 10; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = OsuColour.Gray(0.18f), + RelativeSizeAxes = Axes.Both, + }, + text = new TournamentSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.White, + Font = OsuFont.Torus.With(weight: FontWeight.Regular, size: 16), + }, + }; } [BackgroundDependencyLoader] @@ -201,20 +219,8 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components currentMatch.BindTo(ladder.CurrentMatch); } - private void matchChanged(ValueChangedEvent match) - { - InternalChildren = new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = Color4.White, - Text = match.NewValue.Round.Value?.Name.Value ?? "Unknown Round", - Font = OsuFont.Torus.With(weight: FontWeight.Regular, size: 18), - }, - }; - } + private void matchChanged(ValueChangedEvent match) => + text.Text = match.NewValue.Round.Value?.Name.Value ?? "Unknown Round"; } } } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs index 031d6bf3d2..88d7b95b0c 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; @@ -26,7 +25,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { private readonly TournamentMatch match; private readonly bool losers; - private OsuSpriteText scoreText; + private TournamentSpriteText scoreText; private Box background; private readonly Bindable score = new Bindable(); @@ -69,7 +68,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components AcronymText.Anchor = AcronymText.Origin = Anchor.CentreLeft; AcronymText.Padding = new MarginPadding { Left = 50 }; - AcronymText.Font = OsuFont.GetFont(size: 24); + AcronymText.Font = OsuFont.Torus.With(size: 24); if (match != null) { @@ -119,11 +118,11 @@ namespace osu.Game.Tournament.Screens.Ladder.Components Alpha = 0.8f, RelativeSizeAxes = Axes.Both, }, - scoreText = new OsuSpriteText + scoreText = new TournamentSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 20), + Font = OsuFont.Torus.With(size: 20), } } } @@ -184,7 +183,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components background.FadeColour(winner ? colourWinner : colourNormal, winner ? 500 : 0, Easing.OutQuint); - scoreText.Font = AcronymText.Font = OsuFont.GetFont(weight: winner ? FontWeight.Bold : FontWeight.Regular); + scoreText.Font = AcronymText.Font = OsuFont.Torus.With(weight: winner ? FontWeight.Bold : FontWeight.Regular); } public MenuItem[] ContextMenuItems diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs index dacd98d3b8..d14ebb4d03 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Tournament.Models; using osuTK.Graphics; @@ -22,8 +21,8 @@ namespace osu.Game.Tournament.Screens.Ladder.Components public DrawableTournamentRound(TournamentRound round, bool losers = false) { - OsuSpriteText textName; - OsuSpriteText textDescription; + TournamentSpriteText textName; + TournamentSpriteText textDescription; AutoSizeAxes = Axes.Both; InternalChild = new FillFlowContainer @@ -32,15 +31,15 @@ namespace osu.Game.Tournament.Screens.Ladder.Components AutoSizeAxes = Axes.Both, Children = new Drawable[] { - textDescription = new OsuSpriteText + textDescription = new TournamentSpriteText { Colour = Color4.Black, Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre }, - textName = new OsuSpriteText + textName = new TournamentSpriteText { - Font = OsuFont.GetFont(weight: FontWeight.Bold), + Font = OsuFont.Torus.With(weight: FontWeight.Bold), Colour = Color4.Black, Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index c3875716b8..c42d0a6da3 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Beatmaps; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Tournament.Components; using osu.Game.Tournament.IPC; @@ -56,7 +55,7 @@ namespace osu.Game.Tournament.Screens.MapPool { Children = new Drawable[] { - new OsuSpriteText + new TournamentSpriteText { Text = "Current Mode" }, diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index 140ae54bfb..080570eac4 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Platform; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.Ladder.Components; @@ -107,20 +106,20 @@ namespace osu.Game.Tournament.Screens.Schedule Height = 0.25f, Children = new Drawable[] { - new OsuSpriteText + new TournamentSpriteText { Margin = new MarginPadding { Left = -10, Bottom = 10, Top = -5 }, Spacing = new Vector2(10, 0), Text = match.NewValue.Round.Value?.Name.Value, Colour = Color4.Black, - Font = OsuFont.GetFont(size: 20) + Font = OsuFont.Torus.With(size: 20) }, new ScheduleMatch(match.NewValue, false), - new OsuSpriteText + new TournamentSpriteText { Text = "Start Time " + match.NewValue.Date.Value.ToUniversalTime().ToString("HH:mm UTC"), Colour = Color4.Black, - Font = OsuFont.GetFont(size: 20) + Font = OsuFont.Torus.With(size: 20) }, } } @@ -150,7 +149,7 @@ namespace osu.Game.Tournament.Screens.Schedule Alpha = conditional ? 0.6f : 1, Margin = new MarginPadding { Horizontal = 10, Vertical = 5 }, }); - AddInternal(new OsuSpriteText + AddInternal(new TournamentSpriteText { Anchor = Anchor.BottomRight, Origin = Anchor.BottomLeft, @@ -174,13 +173,13 @@ namespace osu.Game.Tournament.Screens.Schedule Padding = new MarginPadding { Left = 30, Top = 30 }; InternalChildren = new Drawable[] { - new OsuSpriteText + new TournamentSpriteText { X = 30, Text = title, Colour = Color4.Black, Spacing = new Vector2(10, 0), - Font = OsuFont.GetFont(size: 30) + Font = OsuFont.Torus.With(size: 30) }, content = new FillFlowContainer { diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 8e1481d87c..023582166c 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.API; @@ -147,7 +146,7 @@ namespace osu.Game.Tournament.Screens public Action Action; - private OsuSpriteText valueText; + private TournamentSpriteText valueText; protected override Drawable CreateComponent() => new Container { @@ -155,7 +154,7 @@ namespace osu.Game.Tournament.Screens RelativeSizeAxes = Axes.X, Children = new Drawable[] { - valueText = new OsuSpriteText + valueText = new TournamentSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs index 20928499bf..d809dfc994 100644 --- a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs +++ b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs @@ -10,7 +10,7 @@ namespace osu.Game.Tournament.Screens.Showcase [BackgroundDependencyLoader] private void load() { - AddInternal(new TournamentLogo(false)); + AddInternal(new TournamentLogo()); } } } diff --git a/osu.Game.Tournament/Screens/Showcase/TournamentLogo.cs b/osu.Game.Tournament/Screens/Showcase/TournamentLogo.cs index 1fee2b29e8..6ad5ccaf0c 100644 --- a/osu.Game.Tournament/Screens/Showcase/TournamentLogo.cs +++ b/osu.Game.Tournament/Screens/Showcase/TournamentLogo.cs @@ -11,20 +11,12 @@ namespace osu.Game.Tournament.Screens.Showcase { public class TournamentLogo : CompositeDrawable { - public TournamentLogo(bool includeRoundBackground = true) + public TournamentLogo() { RelativeSizeAxes = Axes.X; Margin = new MarginPadding { Vertical = 5 }; - if (includeRoundBackground) - { - AutoSizeAxes = Axes.Y; - } - else - { - Masking = true; - Height = 100; - } + Height = 100; } [BackgroundDependencyLoader] @@ -32,9 +24,11 @@ namespace osu.Game.Tournament.Screens.Showcase { InternalChild = new Sprite { - Texture = textures.Get("game-screen-logo"), Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Texture = textures.Get("game-screen-logo"), }; } } diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs index bff5c6aac3..6559113f55 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs @@ -7,12 +7,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; -using osu.Game.Tournament.Screens.Showcase; using osuTK; -using osuTK.Graphics; namespace osu.Game.Tournament.Screens.TeamIntro { @@ -34,7 +31,6 @@ namespace osu.Game.Tournament.Screens.TeamIntro RelativeSizeAxes = Axes.Both, Loop = true, }, - new TournamentLogo(false), mainContainer = new Container { RelativeSizeAxes = Axes.Both, @@ -75,8 +71,9 @@ namespace osu.Game.Tournament.Screens.TeamIntro { RelativeSizeAxes = Axes.Both, Height = 0.25f, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Y = 180, } }; } @@ -85,8 +82,6 @@ namespace osu.Game.Tournament.Screens.TeamIntro { public RoundDisplay(TournamentMatch match) { - var col = OsuColour.Gray(0.33f); - InternalChildren = new Drawable[] { new FillFlowContainer @@ -98,31 +93,13 @@ namespace osu.Game.Tournament.Screens.TeamIntro Spacing = new Vector2(0, 10), Children = new Drawable[] { - new OsuSpriteText + new TournamentSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Colour = col, - Text = "COMING UP NEXT", - Spacing = new Vector2(2, 0), - Font = OsuFont.GetFont(size: 15, weight: FontWeight.Black) - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Colour = col, + Colour = OsuColour.Gray(0.33f), Text = match.Round.Value?.Name.Value ?? "Unknown Round", - Spacing = new Vector2(10, 0), - Font = OsuFont.GetFont(size: 50, weight: FontWeight.Light) - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Colour = col, - Text = match.Date.Value.ToUniversalTime().ToString("dd MMMM HH:mm UTC"), - Font = OsuFont.GetFont(size: 20) + Font = OsuFont.Torus.With(size: 26, weight: FontWeight.Light) }, } } @@ -132,21 +109,19 @@ namespace osu.Game.Tournament.Screens.TeamIntro private class TeamWithPlayers : CompositeDrawable { - private readonly Color4 red = new Color4(129, 68, 65, 255); - private readonly Color4 blue = new Color4(41, 91, 97, 255); - public TeamWithPlayers(TournamentTeam team, bool left = false) { FillFlowContainer players; - var colour = left ? red : blue; + var colour = left ? TournamentGame.COLOUR_RED : TournamentGame.COLOUR_BLUE; InternalChildren = new Drawable[] { - new TeamDisplay(team, left ? "Team Red" : "Team Blue", colour) + new TeamDisplay(team) { Anchor = left ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = Anchor.Centre, + Origin = Anchor.TopCentre, RelativePositionAxes = Axes.Both, - X = (left ? -1 : 1) * 0.36f, + X = (left ? -1 : 1) * 0.3145f, + Y = -0.077f, }, players = new FillFlowContainer { @@ -157,7 +132,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro Anchor = left ? Anchor.CentreRight : Anchor.CentreLeft, Origin = left ? Anchor.CentreRight : Anchor.CentreLeft, RelativePositionAxes = Axes.Both, - X = (left ? -1 : 1) * 0.66f, + X = (left ? -1 : 1) * 0.58f, }, }; @@ -165,10 +140,10 @@ namespace osu.Game.Tournament.Screens.TeamIntro { foreach (var p in team.Players) { - players.Add(new OsuSpriteText + players.Add(new TournamentSpriteText { Text = p.Username, - Font = OsuFont.GetFont(size: 24), + Font = OsuFont.Torus.With(size: 24), Colour = colour, Anchor = left ? Anchor.CentreRight : Anchor.CentreLeft, Origin = left ? Anchor.CentreRight : Anchor.CentreLeft, @@ -179,7 +154,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro private class TeamDisplay : DrawableTournamentTeam { - public TeamDisplay(TournamentTeam team, string teamName, Color4 colour) + public TeamDisplay(TournamentTeam team) : base(team) { AutoSizeAxes = Axes.Both; @@ -187,33 +162,24 @@ namespace osu.Game.Tournament.Screens.TeamIntro Flag.Anchor = Flag.Origin = Anchor.TopCentre; Flag.RelativeSizeAxes = Axes.None; Flag.Size = new Vector2(300, 200); - Flag.Scale = new Vector2(0.4f); - Flag.Margin = new MarginPadding { Bottom = 20 }; + Flag.Scale = new Vector2(0.32f); InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), + Spacing = new Vector2(160), Children = new Drawable[] { Flag, - new OsuSpriteText + new TournamentSpriteText { - Text = team?.FullName.Value.ToUpper() ?? "???", - Font = OsuFont.Torus.With(size: 40, weight: FontWeight.Light), - Colour = Color4.Black, + Text = team?.FullName.Value ?? "???", + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Regular), + Colour = OsuColour.Gray(0.2f), Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, }, - new OsuSpriteText - { - Text = teamName.ToUpper(), - Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Regular), - Colour = colour, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - } } }; } diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index cc1f2a73ae..30b86f8421 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -7,10 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; -using osu.Game.Tournament.Screens.Showcase; using osuTK; using osuTK.Graphics; @@ -45,10 +43,6 @@ namespace osu.Game.Tournament.Screens.TeamWin RelativeSizeAxes = Axes.Both, Loop = true, }, - new TournamentLogo(false) - { - Y = 40, - }, mainContainer = new Container { RelativeSizeAxes = Axes.Both, @@ -85,141 +79,99 @@ namespace osu.Game.Tournament.Screens.TeamWin mainContainer.Children = new Drawable[] { + new TeamFlagDisplay(match.Winner) + { + Size = new Vector2(300, 200), + Scale = new Vector2(0.5f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + X = -387, + }, + new TournamentSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.TopLeft, + Position = new Vector2(78, -70), + Colour = OsuColour.Gray(0.33f), + Text = match.Round.Value?.Name.Value ?? "Unknown Round", + Font = OsuFont.Torus.With(size: 30, weight: FontWeight.Regular) + }, new TeamWithPlayers(match.Winner, redWin) { RelativeSizeAxes = Axes.Both, Width = 0.5f, Height = 0.6f, Anchor = Anchor.Centre, - Origin = Anchor.Centre + Origin = Anchor.TopLeft, + Position = new Vector2(78, 0), }, - new RoundDisplay(match) - { - RelativeSizeAxes = Axes.Both, - Height = 0.25f, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - } }; } - private class RoundDisplay : CompositeDrawable - { - public RoundDisplay(TournamentMatch match) - { - var col = OsuColour.Gray(0.33f); - - InternalChildren = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - Children = new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Colour = col, - Text = "WINNER", - Font = OsuFont.Torus.With(size: 15, weight: FontWeight.Regular), - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Colour = col, - Text = match.Round.Value?.Name.Value ?? "Unknown Round", - Font = OsuFont.Torus.With(size: 50, weight: FontWeight.Light), - Spacing = new Vector2(10, 0), - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Colour = col, - Text = match.Date.Value.ToUniversalTime().ToString("dd MMMM HH:mm UTC"), - Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Light), - }, - } - } - }; - } - } - private class TeamWithPlayers : CompositeDrawable { - private readonly Color4 red = new Color4(129, 68, 65, 255); - private readonly Color4 blue = new Color4(41, 91, 97, 255); - public TeamWithPlayers(TournamentTeam team, bool left = false) { - var colour = left ? red : blue; + FillFlowContainer players; + + var colour = left ? TournamentGame.COLOUR_RED : TournamentGame.COLOUR_BLUE; InternalChildren = new Drawable[] { - new TeamDisplay(team, left ? "Team Red" : "Team Blue", colour) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, new FillFlowContainer { Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, - Spacing = new Vector2(0, 5), - Padding = new MarginPadding(20), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Both, - }, - }; - } - - private class TeamDisplay : DrawableTournamentTeam - { - public TeamDisplay(TournamentTeam team, string teamName, Color4 colour) - : base(team) - { - AutoSizeAxes = Axes.Both; - - Flag.Anchor = Flag.Origin = Anchor.TopCentre; - Flag.RelativeSizeAxes = Axes.None; - Flag.Size = new Vector2(300, 200); - Flag.Scale = new Vector2(0.4f); - Flag.Margin = new MarginPadding { Bottom = 20 }; - - InternalChild = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), Children = new Drawable[] { - Flag, - new OsuSpriteText + new TournamentSpriteText { - Text = team?.FullName.Value.ToUpper() ?? "???", - Font = OsuFont.Torus.With(size: 40, weight: FontWeight.Light), + Text = "WINNER", + Font = OsuFont.Torus.With(size: 24, weight: FontWeight.SemiBold), Colour = Color4.Black, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, }, - new OsuSpriteText + new TournamentSpriteText { - Text = teamName.ToUpper(), - Font = OsuFont.GetFont(size: 20), - Colour = colour, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - } + Text = team?.FullName.Value ?? "???", + Font = OsuFont.Torus.With(size: 30, weight: FontWeight.SemiBold), + Colour = Color4.Black, + }, + players = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 10 }, + }, } - }; + }, + }; + + if (team != null) + { + foreach (var p in team.Players) + { + players.Add(new TournamentSpriteText + { + Text = p.Username, + Font = OsuFont.Torus.With(size: 24), + Colour = colour, + Anchor = left ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = left ? Anchor.CentreRight : Anchor.CentreLeft, + }); + } } } } + + private class TeamFlagDisplay : DrawableTournamentTeam + { + public TeamFlagDisplay(TournamentTeam team) + : base(team) + { + InternalChildren = new Drawable[] + { + Flag + }; + } + } } } diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 7dbcf37af6..608fc5f04a 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -3,11 +3,15 @@ using osu.Framework.Graphics; using osu.Game.Graphics.Cursor; +using osuTK.Graphics; namespace osu.Game.Tournament { public class TournamentGame : TournamentGameBase { + public static readonly Color4 COLOUR_RED = new Color4(144, 0, 0, 255); + public static readonly Color4 COLOUR_BLUE = new Color4(0, 84, 144, 255); + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 64a5618d73..435f315c8d 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -18,7 +18,6 @@ using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; @@ -101,10 +100,10 @@ namespace osu.Game.Tournament Colour = Color4.Red, RelativeSizeAxes = Axes.Both, }, - new OsuSpriteText + new TournamentSpriteText { Text = "Please make the window wider", - Font = OsuFont.Default.With(weight: "bold"), + Font = OsuFont.Torus.With(weight: FontWeight.Bold), Colour = Color4.White, Padding = new MarginPadding(20) } From db56fb57594ad14d1713c68778d207c21a3892e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 22:28:47 +0900 Subject: [PATCH 122/192] Fix venera font usage --- osu.Game/Graphics/OsuFont.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 54a25875bf..113816a14a 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -17,7 +17,7 @@ namespace osu.Game.Graphics /// public static FontUsage Default => GetFont(); - public static FontUsage Numeric => GetFont(Typeface.Venera, weight: FontWeight.Regular); + public static FontUsage Numeric => GetFont(Typeface.Venera); public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular); From 5e218697c5a525d8289cfa3d9959b1e0df6ac2d6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Mar 2020 09:46:53 +0900 Subject: [PATCH 123/192] Use stacked positions --- .../TestSceneFollowPoints.cs | 46 +++++++++++++++++++ .../Connections/FollowPointConnection.cs | 4 +- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 94ca2d4cd1..87da7ef417 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -2,9 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Osu.Objects; @@ -114,6 +117,22 @@ namespace osu.Game.Rulesets.Osu.Tests assertGroups(); } + [Test] + public void TestStackedObjects() + { + addObjectsStep(() => new OsuHitObject[] + { + new HitCircle { Position = new Vector2(300, 100) }, + new HitCircle + { + Position = new Vector2(300, 300), + StackHeight = 20 + }, + }); + + assertDirections(); + } + private void addMultipleObjectsStep() => addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) }, @@ -207,6 +226,33 @@ namespace osu.Game.Rulesets.Osu.Tests }); } + private void assertDirections() + { + AddAssert("group directions are correct", () => + { + for (int i = 0; i < hitObjectContainer.Count; i++) + { + DrawableOsuHitObject expectedStart = getObject(i); + DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; + + if (expectedEnd == null) + continue; + + var points = getGroup(i).ChildrenOfType().ToArray(); + if (points.Length == 0) + continue; + + float expectedDirection = MathF.Atan2(expectedStart.Position.Y - expectedEnd.Position.Y, expectedStart.Position.X - expectedEnd.Position.X); + float realDirection = MathF.Atan2(expectedStart.Position.Y - points[^1].Position.Y, expectedStart.Position.X - points[^1].Position.X); + + if (!Precision.AlmostEquals(expectedDirection, realDirection)) + throw new AssertionException($"Expected group {i} in direction {expectedDirection}, but was {realDirection}."); + } + + return true; + }); + } + private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index]; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 921b23cb13..3e9c0f341b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -104,8 +104,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections return; } - Vector2 startPosition = osuStart.EndPosition; - Vector2 endPosition = osuEnd.Position; + Vector2 startPosition = osuStart.StackedEndPosition; + Vector2 endPosition = osuEnd.StackedPosition; double endTime = osuEnd.StartTime; Vector2 distanceVector = endPosition - startPosition; From c3f840cc1a5071ee92c758315d5c41ee12cd1dec Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Tue, 3 Mar 2020 17:12:01 -0800 Subject: [PATCH 124/192] Fix Autoplay = false and AllowFail behavior --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 6 +++--- osu.Game/Tests/Visual/ModSandboxTestScene.cs | 13 ++++++++----- osu.Game/Tests/Visual/PlayerTestScene.cs | 2 ++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 1fc9ccccd1..20cb9ef05d 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -42,14 +42,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 }); - protected override TestPlayer CreateReplayPlayer(Score score) => new ScoreAccessibleTestPlayer(score); + protected override TestPlayer CreateReplayPlayer(Score score, bool allowFail) => new ScoreAccessibleTestPlayer(score, allowFail); private class ScoreAccessibleTestPlayer : TestPlayer { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; - public ScoreAccessibleTestPlayer(Score score) - : base(score) + public ScoreAccessibleTestPlayer(Score score, bool allowFail) + : base(score, allowFail) { } } diff --git a/osu.Game/Tests/Visual/ModSandboxTestScene.cs b/osu.Game/Tests/Visual/ModSandboxTestScene.cs index 11612d0eca..8a9cdf009b 100644 --- a/osu.Game/Tests/Visual/ModSandboxTestScene.cs +++ b/osu.Game/Tests/Visual/ModSandboxTestScene.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using osu.Game.Beatmaps; -using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -56,22 +55,26 @@ namespace osu.Game.Tests.Visual var score = currentTest.Autoplay ? ruleset.GetAutoplayMod().CreateReplayScore(Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, SelectedMods.Value)) - : new Score { Replay = new Replay() }; + : null; - return CreateReplayPlayer(score); + return CreateReplayPlayer(score, AllowFail); } /// /// Creates the for a test case. /// /// The . - protected virtual TestPlayer CreateReplayPlayer(Score score) => new TestPlayer(score); + /// Whether the player can fail. + protected virtual TestPlayer CreateReplayPlayer(Score score, bool allowFail) => new TestPlayer(score, allowFail); protected class TestPlayer : TestReplayPlayer { - public TestPlayer(Score score) + protected override bool AllowFail { get; } + + public TestPlayer(Score score, bool allowFail) : base(score, false, false) { + AllowFail = allowFail; } } diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 0d5aac8cfd..17ad6e80df 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -68,6 +68,8 @@ namespace osu.Game.Tests.Visual Beatmap.Value = CreateWorkingBeatmap(beatmap); + SelectedMods.Value = Array.Empty(); + if (!AllowFail) { var noFailMod = ruleset.GetAllMods().FirstOrDefault(m => m is ModNoFail); From 4294ed4b6495e40bcb9c2eb9664f9e369cd949d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 11:21:37 +0900 Subject: [PATCH 125/192] Better align fonts to weights --- osu.Game/Graphics/OsuFont.cs | 37 ++++++++++++++++---- osu.Game/Online/Leaderboards/DrawableRank.cs | 2 +- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 113816a14a..4dd7bcdf31 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -17,7 +17,7 @@ namespace osu.Game.Graphics /// public static FontUsage Default => GetFont(); - public static FontUsage Numeric => GetFont(Typeface.Venera); + public static FontUsage Numeric => GetFont(Typeface.Venera, weight: FontWeight.Bold); public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular); @@ -103,11 +103,34 @@ namespace osu.Game.Graphics public enum FontWeight { - Light, - Regular, - Medium, - SemiBold, - Bold, - Black + /// + /// equivalent to weight 300 + /// + Light = 300, + + /// + /// equivalent to weight 400 + /// + Regular = 400, + + /// + /// equivalent to weight 500 + /// + Medium = 500, + + /// + /// equivalent to weight 600 + /// + SemiBold = 600, + + /// + /// equivalent to weight 700 + /// + Bold = 700, + + /// + /// equivalent to weight 900 + /// + Black = 900 } } diff --git a/osu.Game/Online/Leaderboards/DrawableRank.cs b/osu.Game/Online/Leaderboards/DrawableRank.cs index 50cb58c6ab..20bda4601f 100644 --- a/osu.Game/Online/Leaderboards/DrawableRank.cs +++ b/osu.Game/Online/Leaderboards/DrawableRank.cs @@ -58,7 +58,7 @@ namespace osu.Game.Online.Leaderboards Spacing = new Vector2(-3, 0), Padding = new MarginPadding { Top = 5 }, Colour = getRankNameColour(), - Font = OsuFont.GetFont(Typeface.Venera, 25), + Font = OsuFont.Numeric.With(size: 25), Text = getRankName(), ShadowColour = Color4.Black.Opacity(0.3f), ShadowOffset = new Vector2(0, 0.08f), From 75968fb4baa8657c49f0bf9865a83c842553279b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 11:46:48 +0900 Subject: [PATCH 126/192] Update resources once more --- osu.Android.props | 2 +- osu.Game/OsuGameBase.cs | 4 ++-- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index d62011bf1d..1c4a6ffe75 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 676b4b6c7a..a890331f05 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -162,9 +162,9 @@ namespace osu.Game AddFont(Resources, @"Fonts/Torus-Regular"); AddFont(Resources, @"Fonts/Torus-Light"); - AddFont(Resources, @"Fonts/Venera"); AddFont(Resources, @"Fonts/Venera-Light"); - AddFont(Resources, @"Fonts/Venera-Medium"); + AddFont(Resources, @"Fonts/Venera-Bold"); + AddFont(Resources, @"Fonts/Venera-Black"); runMigrations(); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e407fca8b7..4d59b709aa 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index cc0b8074bb..6897d3e625 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + From 275d075acda463cafa23bb6ac97145d3a1262597 Mon Sep 17 00:00:00 2001 From: mcendu Date: Wed, 4 Mar 2020 10:53:30 +0800 Subject: [PATCH 127/192] comment out log locations in bug report template --- .github/ISSUE_TEMPLATE/01-bug-issues.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/01-bug-issues.md b/.github/ISSUE_TEMPLATE/01-bug-issues.md index 0aff276d03..0b80ce44dd 100644 --- a/.github/ISSUE_TEMPLATE/01-bug-issues.md +++ b/.github/ISSUE_TEMPLATE/01-bug-issues.md @@ -9,6 +9,8 @@ about: Issues regarding encountered bugs. **osu!lazer version:** **Logs:** + From 0f7316d41d54a6c1c522aa3bdbac2992095ebd45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 12:05:23 +0900 Subject: [PATCH 128/192] Move file to other file --- .../Ladder/Components/LadderEditorSettings.cs | 62 ------------------- .../Components/LadderSettingsDropdown.cs | 26 ++++++++ .../Ladder/Components/SettingsTeamDropdown.cs | 55 ++++++++++++++++ 3 files changed, 81 insertions(+), 62 deletions(-) create mode 100644 osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs create mode 100644 osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index 8ab083ddaf..4aea7ff4c0 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Input.Events; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Tournament.Components; @@ -126,66 +125,5 @@ namespace osu.Game.Tournament.Screens.Ladder.Components }); } } - - private class SettingsTeamDropdown : LadderSettingsDropdown - { - public SettingsTeamDropdown(BindableList teams) - { - foreach (var t in teams.Prepend(new TournamentTeam())) - add(t); - - teams.CollectionChanged += (_, args) => - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - args.NewItems.Cast().ForEach(add); - break; - - case NotifyCollectionChangedAction.Remove: - args.OldItems.Cast().ForEach(i => Control.RemoveDropdownItem(i)); - break; - } - }; - } - - private readonly List refBindables = new List(); - - private T boundReference(T obj) - where T : IBindable - { - obj = (T)obj.GetBoundCopy(); - refBindables.Add(obj); - return obj; - } - - private void add(TournamentTeam team) - { - Control.AddDropdownItem(team); - boundReference(team.FullName).BindValueChanged(_ => - { - Control.RemoveDropdownItem(team); - Control.AddDropdownItem(team); - }); - } - } - - private class LadderSettingsDropdown : SettingsDropdown - { - protected override OsuDropdown CreateDropdown() => new DropdownControl(); - - private new class DropdownControl : SettingsDropdown.DropdownControl - { - protected override DropdownMenu CreateMenu() => new Menu(); - - private new class Menu : OsuDropdownMenu - { - public Menu() - { - MaxHeight = 200; - } - } - } - } } } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs new file mode 100644 index 0000000000..347e4d91e0 --- /dev/null +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Tournament.Screens.Ladder.Components +{ + public class LadderSettingsDropdown : SettingsDropdown + { + protected override OsuDropdown CreateDropdown() => new DropdownControl(); + + private new class DropdownControl : SettingsDropdown.DropdownControl + { + protected override DropdownMenu CreateMenu() => new Menu(); + + private new class Menu : OsuDropdownMenu + { + public Menu() + { + MaxHeight = 200; + } + } + } + } +} diff --git a/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs b/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs new file mode 100644 index 0000000000..a630e51e44 --- /dev/null +++ b/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.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.Collections.Specialized; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Tournament.Models; + +namespace osu.Game.Tournament.Screens.Ladder.Components +{ + public class SettingsTeamDropdown : LadderSettingsDropdown + { + public SettingsTeamDropdown(BindableList teams) + { + foreach (var t in teams.Prepend(new TournamentTeam())) + add(t); + + teams.CollectionChanged += (_, args) => + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + args.NewItems.Cast().ForEach(add); + break; + + case NotifyCollectionChangedAction.Remove: + args.OldItems.Cast().ForEach(i => Control.RemoveDropdownItem(i)); + break; + } + }; + } + + private readonly List refBindables = new List(); + + private T boundReference(T obj) + where T : IBindable + { + obj = (T)obj.GetBoundCopy(); + refBindables.Add(obj); + return obj; + } + + private void add(TournamentTeam team) + { + Control.AddDropdownItem(team); + boundReference(team.FullName).BindValueChanged(_ => + { + Control.RemoveDropdownItem(team); + Control.AddDropdownItem(team); + }); + } + } +} From fc0821f1942d141854b9ff6823d0762256068a37 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Mar 2020 12:37:42 +0900 Subject: [PATCH 129/192] Fix nullref when deleting teams in the ladder screen --- osu.Game.Tournament/Screens/Ladder/LadderScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index 8ea366e1b4..86d1081241 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tournament.Screens.Ladder break; case NotifyCollectionChangedAction.Remove: - foreach (var p in args.NewItems.Cast()) + foreach (var p in args.OldItems.Cast()) { foreach (var d in MatchesContainer.Where(d => d.Match == p)) d.Expire(); From e9b0770f6418e904686f0744b82475d9db41f026 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 12:44:44 +0900 Subject: [PATCH 130/192] Apply suggestions from code review Co-Authored-By: Dan Balasescu --- osu.Game/Graphics/OsuFont.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 4dd7bcdf31..12e36871f0 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -109,27 +109,27 @@ namespace osu.Game.Graphics Light = 300, /// - /// equivalent to weight 400 + /// Equivalent to weight 400. /// Regular = 400, /// - /// equivalent to weight 500 + /// Equivalent to weight 500. /// Medium = 500, /// - /// equivalent to weight 600 + /// Equivalent to weight 600. /// SemiBold = 600, /// - /// equivalent to weight 700 + /// Equivalent to weight 700. /// Bold = 700, /// - /// equivalent to weight 900 + /// Equivalent to weight 900. /// Black = 900 } From aa17a64a0e383e7a6f7b011439a84f2cf1bef191 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 4 Mar 2020 12:47:01 +0900 Subject: [PATCH 131/192] Apply missed xmldoc suggestion --- osu.Game/Graphics/OsuFont.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 12e36871f0..841936d2c5 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -104,7 +104,7 @@ namespace osu.Game.Graphics public enum FontWeight { /// - /// equivalent to weight 300 + /// Equivalent to weight 300. /// Light = 300, From 6b7144e21bf923b3f72af0a2980bab8c416ba718 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 13:19:52 +0900 Subject: [PATCH 132/192] Fix non-matching filename --- .../Screens/Editors/{SeedingEditor.cs => SeedingEditorScreen.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Tournament/Screens/Editors/{SeedingEditor.cs => SeedingEditorScreen.cs} (100%) diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditor.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs similarity index 100% rename from osu.Game.Tournament/Screens/Editors/SeedingEditor.cs rename to osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs From dade58adf39afb94677c26936adc9df2a36b3b2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 13:21:14 +0900 Subject: [PATCH 133/192] Fix crash from editor screen test scene when trying to view seeding editor --- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index d6fa1a29a8..ca8bce1cca 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -154,7 +154,7 @@ namespace osu.Game.Tournament.Screens.Editors Text = "Edit seeding results", Action = () => { - sceneManager.SetScreen(new SeedingEditorScreen(team)); + sceneManager?.SetScreen(new SeedingEditorScreen(team)); } }, } From 003aeeb05294889a74d99dc59f63747b320c17e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 13:26:25 +0900 Subject: [PATCH 134/192] Add test scene for seedings editor --- .../Screens/TestSceneSeedingEditorScreen.cs | 25 +++ .../Screens/TestSceneSeedingScreen.cs | 194 +++++++++--------- 2 files changed, 123 insertions(+), 96 deletions(-) create mode 100644 osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs new file mode 100644 index 0000000000..014cd4663b --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs @@ -0,0 +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.Framework.Allocation; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.Editors; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneSeedingEditorScreen : LadderTestScene + { + [Cached] + private readonly LadderInfo ladder = new LadderInfo(); + + public TestSceneSeedingEditorScreen() + { + var match = TestSceneSeedingScreen.CreateSampleSeededMatch(); + + Add(new SeedingEditorScreen(match.Team1.Value) + { + Width = 0.85f // create room for control panel + }); + } + } +} diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs index eb46d75705..335a6c80a1 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs @@ -18,102 +18,7 @@ namespace osu.Game.Tournament.Tests.Screens [BackgroundDependencyLoader] private void load() { - ladder.CurrentMatch.Value = new TournamentMatch - { - Team1 = - { - Value = new TournamentTeam - { - FlagName = { Value = "JP" }, - FullName = { Value = "Japan" }, - LastYearPlacing = { Value = 10 }, - Seed = { Value = "Low" }, - SeedingResults = - { - new SeedingResult - { - Mod = { Value = "NM" }, - Seed = { Value = 10 }, - Beatmaps = - { - new SeedingBeatmap - { - BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, - Score = 12345672, - Seed = { Value = 24 }, - }, - new SeedingBeatmap - { - BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, - Score = 1234567, - Seed = { Value = 12 }, - }, - new SeedingBeatmap - { - BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, - Score = 1234567, - Seed = { Value = 16 }, - } - } - }, - new SeedingResult - { - Mod = { Value = "DT" }, - Seed = { Value = 5 }, - Beatmaps = - { - new SeedingBeatmap - { - BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, - Score = 234567, - Seed = { Value = 3 }, - }, - new SeedingBeatmap - { - BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, - Score = 234567, - Seed = { Value = 6 }, - }, - new SeedingBeatmap - { - BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, - Score = 234567, - Seed = { Value = 12 }, - } - } - } - }, - Players = - { - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } }, - } - } - }, - Team2 = - { - Value = new TournamentTeam - { - FlagName = { Value = "US" }, - FullName = { Value = "United States" }, - Players = - { - new User { Username = "Hello" }, - new User { Username = "Hello" }, - new User { Username = "Hello" }, - new User { Username = "Hello" }, - new User { Username = "Hello" }, - } - } - }, - Round = - { - Value = new TournamentRound { Name = { Value = "Quarterfinals" } } - } - }; + ladder.CurrentMatch.Value = CreateSampleSeededMatch(); Add(new SeedingScreen { @@ -121,5 +26,102 @@ namespace osu.Game.Tournament.Tests.Screens FillAspectRatio = 16 / 9f }); } + + public static TournamentMatch CreateSampleSeededMatch() => new TournamentMatch + { + Team1 = + { + Value = new TournamentTeam + { + FlagName = { Value = "JP" }, + FullName = { Value = "Japan" }, + LastYearPlacing = { Value = 10 }, + Seed = { Value = "Low" }, + SeedingResults = + { + new SeedingResult + { + Mod = { Value = "NM" }, + Seed = { Value = 10 }, + Beatmaps = + { + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 12345672, + Seed = { Value = 24 }, + }, + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 1234567, + Seed = { Value = 12 }, + }, + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 1234567, + Seed = { Value = 16 }, + } + } + }, + new SeedingResult + { + Mod = { Value = "DT" }, + Seed = { Value = 5 }, + Beatmaps = + { + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 234567, + Seed = { Value = 3 }, + }, + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 234567, + Seed = { Value = 6 }, + }, + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 234567, + Seed = { Value = 12 }, + } + } + } + }, + Players = + { + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } }, + } + } + }, + Team2 = + { + Value = new TournamentTeam + { + FlagName = { Value = "US" }, + FullName = { Value = "United States" }, + Players = + { + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + } + } + }, + Round = + { + Value = new TournamentRound { Name = { Value = "Quarterfinals" } } + } + }; } } From a3709ae2684d5dae521a6ba6c798505bddc75613 Mon Sep 17 00:00:00 2001 From: mcendu Date: Wed, 4 Mar 2020 13:39:37 +0800 Subject: [PATCH 135/192] Also comment out log locations in crash template --- .github/ISSUE_TEMPLATE/02-crash-issues.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/02-crash-issues.md b/.github/ISSUE_TEMPLATE/02-crash-issues.md index 9c3ae33161..ada8de73c0 100644 --- a/.github/ISSUE_TEMPLATE/02-crash-issues.md +++ b/.github/ISSUE_TEMPLATE/02-crash-issues.md @@ -9,8 +9,10 @@ about: Issues regarding crashes or permanent freezes. **osu!lazer version:** **Logs:** + **Computer Specifications:** From 416b9e4e6f38d64113ed35654bf6435be44c0cda Mon Sep 17 00:00:00 2001 From: mcendu Date: Wed, 4 Mar 2020 14:28:18 +0800 Subject: [PATCH 136/192] fix beatmap status display --- osu.Game/Overlays/BeatmapSet/AuthorInfo.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index 446a075ae4..e80034f494 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -59,7 +59,25 @@ namespace osu.Game.Overlays.BeatmapSet if (online.Ranked.HasValue) { - fields.Add(new Field("ranked", online.Ranked.Value, OsuFont.GetFont(weight: FontWeight.Bold))); + string verb = "ranked"; + + switch (online.Status) + { + case BeatmapSetOnlineStatus.Ranked: + verb = "ranked"; + break; + case BeatmapSetOnlineStatus.Approved: + verb = "approved"; + break; + case BeatmapSetOnlineStatus.Qualified: + verb = "qualified"; + break; + case BeatmapSetOnlineStatus.Loved: + verb = "loved"; + break; + } + + fields.Add(new Field(verb, online.Ranked.Value, OsuFont.GetFont(weight: FontWeight.Bold))); } else if (online.LastUpdated.HasValue) { From fbd0dfd71b6c889794197e6e747ef9a99a185273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=9CNate?= Date: Wed, 4 Mar 2020 14:55:51 +0800 Subject: [PATCH 137/192] add blank lines --- osu.Game/Overlays/BeatmapSet/AuthorInfo.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index e80034f494..f3bf0b8e5e 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -66,12 +66,15 @@ namespace osu.Game.Overlays.BeatmapSet case BeatmapSetOnlineStatus.Ranked: verb = "ranked"; break; + case BeatmapSetOnlineStatus.Approved: verb = "approved"; break; + case BeatmapSetOnlineStatus.Qualified: verb = "qualified"; break; + case BeatmapSetOnlineStatus.Loved: verb = "loved"; break; From c306d3de2e1839a8cdf058bc0448c80d8caf4ddd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 16:03:43 +0900 Subject: [PATCH 138/192] Fix typo in button Co-Authored-By: Dan Balasescu --- osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs index 68003a1c43..e68946aaf2 100644 --- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tournament.Screens.Editors Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.None, Width = 150, - Text = "Delete SeeingResult", + Text = "Delete result", Action = () => { Expire(); From 7464a486d980a4180aa86f3404a77e8234688bcd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 16:35:45 +0900 Subject: [PATCH 139/192] Fix DummyWorkingBeatmap's track completion attempting to change game-wide beatmap --- osu.Game/OsuGame.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5781a7fbc4..69d5a9a583 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -428,11 +428,17 @@ namespace osu.Game } } - private void currentTrackCompleted() => Schedule(() => + private void currentTrackCompleted() { - if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled) - musicController.NextTrack(); - }); + if (Beatmap.Value is DummyWorkingBeatmap) + return; + + Schedule(() => + { + if (Beatmap.Value.Track.Looping && !Beatmap.Disabled) + musicController.NextTrack(); + }); + } #endregion From eaa77bce142cb905b8be09fdf5ecf2e5bd2e8242 Mon Sep 17 00:00:00 2001 From: mcendu Date: Wed, 4 Mar 2020 16:43:35 +0800 Subject: [PATCH 140/192] Use ToString().ToLowerInvariant() * https://github.com/ppy/osu/pull/8128#issuecomment-594360083 --- osu.Game/Overlays/BeatmapSet/AuthorInfo.cs | 23 +--------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index f3bf0b8e5e..31c1439c8f 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -59,28 +59,7 @@ namespace osu.Game.Overlays.BeatmapSet if (online.Ranked.HasValue) { - string verb = "ranked"; - - switch (online.Status) - { - case BeatmapSetOnlineStatus.Ranked: - verb = "ranked"; - break; - - case BeatmapSetOnlineStatus.Approved: - verb = "approved"; - break; - - case BeatmapSetOnlineStatus.Qualified: - verb = "qualified"; - break; - - case BeatmapSetOnlineStatus.Loved: - verb = "loved"; - break; - } - - fields.Add(new Field(verb, online.Ranked.Value, OsuFont.GetFont(weight: FontWeight.Bold))); + fields.Add(new Field(online.Status.ToString().ToLowerInvariant(), online.Ranked.Value, OsuFont.GetFont(weight: FontWeight.Bold))); } else if (online.LastUpdated.HasValue) { From 184d10a75a9f0c4cdc839d8b62d1540ac2650781 Mon Sep 17 00:00:00 2001 From: recapitalverb <41869184+recapitalverb@users.noreply.github.com> Date: Wed, 4 Mar 2020 15:45:22 +0700 Subject: [PATCH 141/192] Revert "Reduce social overlay/direct overlay paddings" This reverts commit cb1129218135bd2b1e2d3a287d381c13e08725d6. --- osu.Game/Overlays/SearchableList/SearchableListOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index 72796df6d5..0783c64c20 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.SearchableList { public abstract class SearchableListOverlay : FullscreenOverlay { - public const float WIDTH_PADDING = 10; + public const float WIDTH_PADDING = 80; protected SearchableListOverlay(OverlayColourScheme colourScheme) : base(colourScheme) From a1dc59500699d1d83875c58a48bf9b0e489ec187 Mon Sep 17 00:00:00 2001 From: recapitalverb <41869184+recapitalverb@users.noreply.github.com> Date: Wed, 4 Mar 2020 15:46:35 +0700 Subject: [PATCH 142/192] Change scroll container padding --- osu.Game/Overlays/SearchableList/SearchableListOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index 0783c64c20..d6174e0733 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -80,7 +80,7 @@ namespace osu.Game.Overlays.SearchableList { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = WIDTH_PADDING, Bottom = 50 }, + Padding = new MarginPadding { Horizontal = 10, Bottom = 50 }, Direction = FillDirection.Vertical, }, }, From bac35b2a68f15a0abe079f94a17883e6c040ef68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 19:09:52 +0900 Subject: [PATCH 143/192] Handle beatmap track changing in a saner way --- osu.Game/OsuGame.cs | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 69d5a9a583..0be9e6cdaa 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -396,18 +396,27 @@ namespace osu.Game private void beatmapChanged(ValueChangedEvent beatmap) { - var nextBeatmap = beatmap.NewValue; - if (nextBeatmap?.Track != null) - nextBeatmap.Track.Completed += currentTrackCompleted; - - var oldBeatmap = beatmap.OldValue; - if (oldBeatmap?.Track != null) - oldBeatmap.Track.Completed -= currentTrackCompleted; + beatmap.OldValue?.CancelAsyncLoad(); updateModDefaults(); - oldBeatmap?.CancelAsyncLoad(); - nextBeatmap?.BeginAsyncLoad(); + var newBeatmap = beatmap.NewValue; + + if (newBeatmap != null) + { + newBeatmap.Track.Completed += () => Scheduler.AddOnce(() => trackCompleted(newBeatmap)); + newBeatmap.BeginAsyncLoad(); + } + + void trackCompleted(WorkingBeatmap b) + { + // the source of track completion is the audio thread, so the beatmap may have changed before a firing. + if (Beatmap.Value != b) + return; + + if (Beatmap.Value.Track.Looping && !Beatmap.Disabled) + musicController.NextTrack(); + } } private void modsChanged(ValueChangedEvent> mods) @@ -428,18 +437,6 @@ namespace osu.Game } } - private void currentTrackCompleted() - { - if (Beatmap.Value is DummyWorkingBeatmap) - return; - - Schedule(() => - { - if (Beatmap.Value.Track.Looping && !Beatmap.Disabled) - musicController.NextTrack(); - }); - } - #endregion private ScheduledDelegate performFromMainMenuTask; From 38d91ccd0d20dcd4f9f4e016f03da00da2ba8152 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 19:07:34 +0900 Subject: [PATCH 144/192] Add comment regarding no-longer-required schedule --- osu.Game/OsuGameBase.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a890331f05..67aa4a8d4d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -211,6 +211,10 @@ namespace osu.Game Audio.Tracks.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8)); Beatmap = new NonNullableBindable(defaultBeatmap); + + // ScheduleAfterChildren is safety against something in the current frame accessing the previous beatmap's track + // and potentially causing a reload of it after just unloading. + // Note that the reason for this being added *has* been resolved, so it may be feasible to remover this if required. Beatmap.BindValueChanged(b => ScheduleAfterChildren(() => { // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) From 11ffe6a072e935cbcfeaef5546754c0359770470 Mon Sep 17 00:00:00 2001 From: McEndu Date: Wed, 4 Mar 2020 21:45:01 +0800 Subject: [PATCH 145/192] Remove LD_LIBRARY_PATH from vscode launch.json --- .vscode/launch.json | 42 +----------------------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 6480612b2e..4e8af405a2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,11 +11,6 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Debug)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}" - } - }, "console": "internalConsole" }, { @@ -28,11 +23,6 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Release)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}" - } - }, "console": "internalConsole" }, { @@ -45,11 +35,6 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build tests (Debug)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}" - } - }, "console": "internalConsole" }, { @@ -62,11 +47,6 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build tests (Release)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}" - } - }, "console": "internalConsole" }, { @@ -80,11 +60,6 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Debug)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}" - } - }, "console": "internalConsole" }, { @@ -98,11 +73,6 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Release)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}" - } - }, "console": "internalConsole" }, { @@ -116,11 +86,6 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build tournament tests (Debug)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}" - } - }, "console": "internalConsole" }, { @@ -134,11 +99,6 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build tournament tests (Release)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}" - } - }, "console": "internalConsole" }, { @@ -169,4 +129,4 @@ "externalConsole": false } ] -} \ No newline at end of file +} From 997be65be2329d3a3376f1ca16914622e767da0f Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Wed, 4 Mar 2020 21:19:26 +0100 Subject: [PATCH 146/192] Improve colouring logic --- .../Historical/DrawableMostPlayedBeatmap.cs | 17 +++++++++----- .../Profile/Sections/ProfileItemContainer.cs | 22 ++++++++++++++++--- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index dc8492562a..5b7c5efbe2 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -37,10 +37,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical } [BackgroundDependencyLoader] - private void load(OsuColour colours, OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider) { - ProfileItemContainer container; - AddRangeInternal(new Drawable[] { new UpdateableBeatmapSetCover @@ -63,7 +61,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical CornerRadius = corner_radius, Children = new Drawable[] { - container = new ProfileItemContainer + new MostPlayedBeatmapContainer { Child = new Container { @@ -108,9 +106,16 @@ namespace osu.Game.Overlays.Profile.Sections.Historical } } }); + } - container.IdleColour = colourProvider.Background4; - container.HoverColour = colourProvider.Background3; + private class MostPlayedBeatmapContainer : ProfileItemContainer + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + IdleColour = colourProvider.Background4; + HoverColour = colourProvider.Background3; + } } private class MostPlayedBeatmapMetadataContainer : BeatmapMetadataContainer diff --git a/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs b/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs index a48f21f52b..1ab9ed3f82 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs @@ -21,13 +21,29 @@ namespace osu.Game.Overlays.Profile.Sections private Color4 idleColour; - public Color4 IdleColour + protected Color4 IdleColour { get => idleColour; - set => idleColour = background.Colour = value; + set + { + idleColour = value; + if (!IsHovered) + background.Colour = value; + } } - public Color4 HoverColour { get; set; } + private Color4 hoverColour; + + protected Color4 HoverColour + { + get => hoverColour; + set + { + hoverColour = value; + if (IsHovered) + background.Colour = value; + } + } public ProfileItemContainer() { From e3e66991b08c4f26b0b8f46a4ef8c414f775a6b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 Mar 2020 22:02:36 +0100 Subject: [PATCH 147/192] Move initialisation logic to [SetUp] --- .../Visual/Online/TestSceneUserPanel.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index 54f06d6ad2..597ca00fb8 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -13,13 +13,16 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestSceneUserPanel : OsuTestScene { - private readonly UserPanel peppy; + private readonly Bindable activity = new Bindable(); - public TestSceneUserPanel() + private UserPanel peppy; + + [SetUp] + public void SetUp() => Schedule(() => { UserPanel flyte; - Add(new FillFlowContainer + Child = new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -44,11 +47,12 @@ namespace osu.Game.Tests.Visual.Online SupportLevel = 3, }) { Width = 300 }, }, - }); + }; flyte.Status.Value = new UserStatusOnline(); peppy.Status.Value = null; - } + peppy.Activity.BindTo(activity); + }); [Test] public void UserStatusesTests() @@ -62,10 +66,6 @@ namespace osu.Game.Tests.Visual.Online [Test] public void UserActivitiesTests() { - Bindable activity = new Bindable(); - - peppy.Activity.BindTo(activity); - AddStep("idle", () => { activity.Value = null; }); AddStep("spectating", () => { activity.Value = new UserActivity.Spectating(); }); AddStep("solo", () => { activity.Value = new UserActivity.SoloGame(null, null); }); From 5b25b5dfabf972d05f4829fd55b363c575be4421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 Mar 2020 22:04:49 +0100 Subject: [PATCH 148/192] Change brace style --- .../Visual/Online/TestSceneUserPanel.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index 597ca00fb8..55017db479 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -57,21 +57,21 @@ namespace osu.Game.Tests.Visual.Online [Test] public void UserStatusesTests() { - AddStep("online", () => { peppy.Status.Value = new UserStatusOnline(); }); - AddStep(@"do not disturb", () => { peppy.Status.Value = new UserStatusDoNotDisturb(); }); - AddStep(@"offline", () => { peppy.Status.Value = new UserStatusOffline(); }); - AddStep(@"null status", () => { peppy.Status.Value = null; }); + AddStep("online", () => peppy.Status.Value = new UserStatusOnline()); + AddStep(@"do not disturb", () => peppy.Status.Value = new UserStatusDoNotDisturb()); + AddStep(@"offline", () => peppy.Status.Value = new UserStatusOffline()); + AddStep(@"null status", () => peppy.Status.Value = null); } [Test] public void UserActivitiesTests() { - AddStep("idle", () => { activity.Value = null; }); - AddStep("spectating", () => { activity.Value = new UserActivity.Spectating(); }); - AddStep("solo", () => { activity.Value = new UserActivity.SoloGame(null, null); }); - AddStep("choosing", () => { activity.Value = new UserActivity.ChoosingBeatmap(); }); - AddStep("editing", () => { activity.Value = new UserActivity.Editing(null); }); - AddStep("modding", () => { activity.Value = new UserActivity.Modding(); }); + AddStep("idle", () => activity.Value = null); + AddStep("spectating", () => activity.Value = new UserActivity.Spectating()); + AddStep("solo", () => activity.Value = new UserActivity.SoloGame(null, null)); + AddStep("choosing", () => activity.Value = new UserActivity.ChoosingBeatmap()); + AddStep("editing", () => activity.Value = new UserActivity.Editing(null)); + AddStep("modding", () => activity.Value = new UserActivity.Modding()); } } } From 1bd49d50c771846168209054257ae132e854e8e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 Mar 2020 22:05:08 +0100 Subject: [PATCH 149/192] Remove unnecessary raw string prefixes --- osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index 55017db479..f128584b1a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -58,9 +58,9 @@ namespace osu.Game.Tests.Visual.Online public void UserStatusesTests() { AddStep("online", () => peppy.Status.Value = new UserStatusOnline()); - AddStep(@"do not disturb", () => peppy.Status.Value = new UserStatusDoNotDisturb()); - AddStep(@"offline", () => peppy.Status.Value = new UserStatusOffline()); - AddStep(@"null status", () => peppy.Status.Value = null); + AddStep("do not disturb", () => peppy.Status.Value = new UserStatusDoNotDisturb()); + AddStep("offline", () => peppy.Status.Value = new UserStatusOffline()); + AddStep("null status", () => peppy.Status.Value = null); } [Test] From 5fa2638e81619fe0ba9fa726561972ea9886ac1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 Mar 2020 22:05:48 +0100 Subject: [PATCH 150/192] Rename tests to adhere to convention --- osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index f128584b1a..ad5dfb371a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Online }); [Test] - public void UserStatusesTests() + public void TestUserStatus() { AddStep("online", () => peppy.Status.Value = new UserStatusOnline()); AddStep("do not disturb", () => peppy.Status.Value = new UserStatusDoNotDisturb()); @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void UserActivitiesTests() + public void TestUserActivity() { AddStep("idle", () => activity.Value = null); AddStep("spectating", () => activity.Value = new UserActivity.Spectating()); From afa3ce494da1856810c549bf84efa262f59d259f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 Mar 2020 22:07:02 +0100 Subject: [PATCH 151/192] Set online status in activity test The test would check nothing otherwise. --- osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index ad5dfb371a..fae3e9b17b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -66,6 +66,8 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestUserActivity() { + AddStep("set online status", () => peppy.Status.Value = new UserStatusOnline()); + AddStep("idle", () => activity.Value = null); AddStep("spectating", () => activity.Value = new UserActivity.Spectating()); AddStep("solo", () => activity.Value = new UserActivity.SoloGame(null, null)); From b8889318dbcc6d7b6c8c28c08d4b063b7918f6cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 Mar 2020 22:13:31 +0100 Subject: [PATCH 152/192] Pass rulesets to solo game status --- osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index fae3e9b17b..80fcef2ed2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets; using osu.Game.Users; using osuTK; @@ -17,6 +19,9 @@ namespace osu.Game.Tests.Visual.Online private UserPanel peppy; + [Resolved] + private RulesetStore rulesetStore { get; set; } + [SetUp] public void SetUp() => Schedule(() => { @@ -70,10 +75,15 @@ namespace osu.Game.Tests.Visual.Online AddStep("idle", () => activity.Value = null); AddStep("spectating", () => activity.Value = new UserActivity.Spectating()); - AddStep("solo", () => activity.Value = new UserActivity.SoloGame(null, null)); + AddStep("solo (osu!)", () => activity.Value = soloGameStatusForRuleset(0)); + AddStep("solo (osu!taiko)", () => activity.Value = soloGameStatusForRuleset(1)); + AddStep("solo (osu!catch)", () => activity.Value = soloGameStatusForRuleset(2)); + AddStep("solo (osu!mania)", () => activity.Value = soloGameStatusForRuleset(3)); AddStep("choosing", () => activity.Value = new UserActivity.ChoosingBeatmap()); AddStep("editing", () => activity.Value = new UserActivity.Editing(null)); AddStep("modding", () => activity.Value = new UserActivity.Modding()); } + + private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.SoloGame(null, rulesetStore.GetRuleset(rulesetId)); } } From ce3786cfd912b2cff9e7431e13cb7761352de8af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 10:11:27 +0900 Subject: [PATCH 153/192] Rename to ModTestScene (is no longer a sandbox) --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 2 +- .../Visual/{ModSandboxTestScene.cs => ModTestScene.cs} | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Tests/Visual/{ModSandboxTestScene.cs => ModTestScene.cs} (95%) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 20cb9ef05d..c2a7a5003f 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -12,7 +12,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests.Mods { - public class TestSceneOsuModDifficultyAdjust : ModSandboxTestScene + public class TestSceneOsuModDifficultyAdjust : ModTestScene { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(OsuModDifficultyAdjust)).ToList(); diff --git a/osu.Game/Tests/Visual/ModSandboxTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs similarity index 95% rename from osu.Game/Tests/Visual/ModSandboxTestScene.cs rename to osu.Game/Tests/Visual/ModTestScene.cs index 8a9cdf009b..1ff061dfac 100644 --- a/osu.Game/Tests/Visual/ModSandboxTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -13,16 +13,16 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { - public abstract class ModSandboxTestScene : PlayerTestScene + public abstract class ModTestScene : PlayerTestScene { protected sealed override bool HasCustomSteps => true; public override IReadOnlyList RequiredTypes => new[] { - typeof(ModSandboxTestScene) + typeof(ModTestScene) }; - protected ModSandboxTestScene(Ruleset ruleset) + protected ModTestScene(Ruleset ruleset) : base(ruleset) { } From 2a581ef24786102d41709f08cde6747060418ed1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 10:15:17 +0900 Subject: [PATCH 154/192] Remove required types --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index c2a7a5003f..4a284022e2 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -1,9 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; @@ -14,8 +11,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { public class TestSceneOsuModDifficultyAdjust : ModTestScene { - public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(OsuModDifficultyAdjust)).ToList(); - public TestSceneOsuModDifficultyAdjust() : base(new OsuRuleset()) { From 0f1f1d1a6b91ce361913fbf2aa8bb6fe8a23fa01 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 10:18:37 +0900 Subject: [PATCH 155/192] Remove unused "name" parameter --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 6 +++--- osu.Game/Tests/Visual/ModTestScene.cs | 8 +------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 4a284022e2..8ff55c9728 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -17,21 +17,21 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } [Test] - public void TestNoAdjustment() => CreateModTest(new ModTestCaseData("no adjustment", new OsuModDifficultyAdjust()) + public void TestNoAdjustment() => CreateModTest(new ModTestCaseData(new OsuModDifficultyAdjust()) { Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 }); [Test] - public void TestCircleSize10() => CreateModTest(new ModTestCaseData("cs = 10", new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }) + public void TestCircleSize10() => CreateModTest(new ModTestCaseData(new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }) { Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 }); [Test] - public void TestApproachRate10() => CreateModTest(new ModTestCaseData("ar = 10", new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }) + public void TestApproachRate10() => CreateModTest(new ModTestCaseData(new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }) { Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 1ff061dfac..04f93fc683 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -97,19 +97,13 @@ namespace osu.Game.Tests.Visual [CanBeNull] public Func PassCondition; - /// - /// The name of this test case, displayed in the test browser. - /// - public readonly string Name; - /// /// The this test case tests. /// public readonly Mod Mod; - public ModTestCaseData(string name, Mod mod) + public ModTestCaseData(Mod mod) { - Name = name; Mod = mod; } } From 3b19467eadbcdf0bec89b240a7d981a599391acd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 10:19:42 +0900 Subject: [PATCH 156/192] ModTestCaseData -> ModTestData --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 6 +++--- osu.Game/Tests/Visual/ModTestScene.cs | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 8ff55c9728..427f25fe11 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -17,21 +17,21 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } [Test] - public void TestNoAdjustment() => CreateModTest(new ModTestCaseData(new OsuModDifficultyAdjust()) + public void TestNoAdjustment() => CreateModTest(new ModTestData(new OsuModDifficultyAdjust()) { Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 }); [Test] - public void TestCircleSize10() => CreateModTest(new ModTestCaseData(new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }) + public void TestCircleSize10() => CreateModTest(new ModTestData(new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }) { Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 }); [Test] - public void TestApproachRate10() => CreateModTest(new ModTestCaseData(new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }) + public void TestApproachRate10() => CreateModTest(new ModTestData(new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }) { Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 04f93fc683..a8b40a5a68 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -27,11 +27,11 @@ namespace osu.Game.Tests.Visual { } - private ModTestCaseData currentTest; + private ModTestData currentTest; - protected void CreateModTest(ModTestCaseData testCaseData) => CreateTest(() => + protected void CreateModTest(ModTestData testData) => CreateTest(() => { - AddStep("set test data", () => currentTest = testCaseData); + AddStep("set test data", () => currentTest = testData); }); public override void TearDownSteps() @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual } } - protected class ModTestCaseData + protected class ModTestData { /// /// Whether to use a replay to simulate an auto-play. True by default. @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual /// public readonly Mod Mod; - public ModTestCaseData(Mod mod) + public ModTestData(Mod mod) { Mod = mod; } From fadebcdc03188cb171f453153530e55c6be6c7a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 10:21:25 +0900 Subject: [PATCH 157/192] Move all sets to object initialiser for code formatting reasons --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 9 ++++++--- osu.Game/Tests/Visual/ModTestScene.cs | 7 +------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 427f25fe11..e4b1e30bcd 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -17,22 +17,25 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } [Test] - public void TestNoAdjustment() => CreateModTest(new ModTestData(new OsuModDifficultyAdjust()) + public void TestNoAdjustment() => CreateModTest(new ModTestData() { + Mod = new OsuModDifficultyAdjust(), Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 }); [Test] - public void TestCircleSize10() => CreateModTest(new ModTestData(new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }) + public void TestCircleSize10() => CreateModTest(new ModTestData { + Mod = new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }, Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 }); [Test] - public void TestApproachRate10() => CreateModTest(new ModTestData(new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }) + public void TestApproachRate10() => CreateModTest(new ModTestData { + Mod = new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }, Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 }); diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index a8b40a5a68..3d12001cca 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -100,12 +100,7 @@ namespace osu.Game.Tests.Visual /// /// The this test case tests. /// - public readonly Mod Mod; - - public ModTestData(Mod mod) - { - Mod = mod; - } + public Mod Mod; } } } From 5200633f9fa19b8f6994ed6b7ef0ec51d18befdf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 11:25:07 +0900 Subject: [PATCH 158/192] Centralise TestPlayer implementations as much as possible --- .../TestSceneAutoJuiceStream.cs | 3 +- .../TestSceneOsuFlashlight.cs | 4 +- .../TestSceneSkinFallbacks.cs | 5 +- .../TestSceneSpinnerRotation.cs | 5 +- .../TestSceneSwellJudgements.cs | 27 ---------- .../TestSceneTaikoSuddenDeath.cs | 18 ++----- .../Background/TestSceneUserDimBackgrounds.cs | 10 ++-- .../Visual/Gameplay/TestSceneAutoplay.cs | 24 +++------ .../Gameplay/TestSceneGameplayRewinding.cs | 49 ++++--------------- .../Visual/Gameplay/TestScenePause.cs | 7 +-- .../Gameplay/TestScenePauseWhenInactive.cs | 5 +- .../Visual/Gameplay/TestScenePlayerLoader.cs | 13 +---- osu.Game/Tests/Visual/PlayerTestScene.cs | 5 +- osu.Game/Tests/Visual/TestPlayer.cs | 28 +++++++++++ 14 files changed, 63 insertions(+), 140 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index 74a9c05bf9..ed7bfb9a44 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -7,7 +7,6 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Screens.Play; using osu.Game.Tests.Visual; using osuTK; @@ -51,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Tests return beatmap; } - protected override Player CreatePlayer(Ruleset ruleset) + protected override TestPlayer CreatePlayer(Ruleset ruleset) { SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); return base.CreatePlayer(ruleset); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs index 412effe176..19736a7709 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs @@ -3,13 +3,13 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { public class TestSceneOsuFlashlight : TestSceneOsuPlayer { - protected override Player CreatePlayer(Ruleset ruleset) + protected override TestPlayer CreatePlayer(Ruleset ruleset) { SelectedMods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), }; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 4da1b1dae0..d39e24fc1f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -18,7 +18,6 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Screens.Play; using osu.Game.Skinning; using osu.Game.Storyboards; using osu.Game.Tests.Visual; @@ -56,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void checkNextHitObject(string skin) => AddUntilStep($"check skin from {skin}", () => { - var firstObject = ((TestPlayer)Player).DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.OfType().FirstOrDefault(); + var firstObject = Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.OfType().FirstOrDefault(); if (firstObject == null) return false; @@ -75,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Resolved] private AudioManager audio { get; set; } - protected override Player CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin); + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin); protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, audio, testBeatmapSkin); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 5cf571d961..ea006ec607 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -11,7 +11,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Tests.Visual; using osuTK; using System.Collections.Generic; using System.Linq; @@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests base.SetUpSteps(); AddUntilStep("wait for track to start running", () => track.IsRunning); - AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)((TestPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.First()); + AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)Player.DrawableRuleset.Playfield.AllHitObjects.First()); } [Test] @@ -89,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep($"seek to {time}", () => track.Seek(time)); - AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, ((TestPlayer)Player).DrawableRuleset.FrameStableClock.CurrentTime, 100)); + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs index ccacc50de1..303f0163b1 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs @@ -1,23 +1,16 @@ // 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 NUnit.Framework; -using osu.Framework.Allocation; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Screens.Play; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { public class TestSceneSwellJudgements : PlayerTestScene { - protected new TestPlayer Player => (TestPlayer)base.Player; - public TestSceneSwellJudgements() : base(new TaikoRuleset()) { @@ -49,25 +42,5 @@ namespace osu.Game.Rulesets.Taiko.Tests return beatmap; } - - protected override Player CreatePlayer(Ruleset ruleset) => new TestPlayer(); - - protected class TestPlayer : Player - { - public readonly List Results = new List(); - - public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; - - public TestPlayer() - : base(false, false) - { - } - - [BackgroundDependencyLoader] - private void load() - { - ScoreProcessor.NewJudgement += r => Results.Add(r); - } - } } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs index 140433a523..2ab041e191 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs @@ -4,11 +4,9 @@ using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Screens.Play; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests @@ -22,10 +20,10 @@ namespace osu.Game.Rulesets.Taiko.Tests protected override bool AllowFail => true; - protected override Player CreatePlayer(Ruleset ruleset) + protected override TestPlayer CreatePlayer(Ruleset ruleset) { SelectedMods.Value = SelectedMods.Value.Concat(new[] { new TaikoModSuddenDeath() }).ToArray(); - return new ScoreAccessiblePlayer(); + return base.CreatePlayer(ruleset); } protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => @@ -49,20 +47,10 @@ namespace osu.Game.Rulesets.Taiko.Tests AddStep("Setup judgements", () => { judged = false; - ((ScoreAccessiblePlayer)Player).ScoreProcessor.NewJudgement += b => judged = true; + Player.ScoreProcessor.NewJudgement += b => judged = true; }); AddUntilStep("swell judged", () => judged); AddAssert("not failed", () => !Player.HasFailed); } - - private class ScoreAccessiblePlayer : TestPlayer - { - public ScoreAccessiblePlayer() - : base(false, false) - { - } - - public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; - } } } diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 6d014ca1ca..06a155e78b 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Background private DummySongSelect songSelect; private TestPlayerLoader playerLoader; - private TestPlayer player; + private LoadBlockingTestPlayer player; private BeatmapManager manager; private RulesetStore rulesets; @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Background public void PlayerLoaderSettingsHoverTest() { setupUserSettings(); - AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer { BlockLoad = true }))); + AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new LoadBlockingTestPlayer { BlockLoad = true }))); AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false); AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent()); AddStep("Trigger background preview", () => @@ -268,7 +268,7 @@ namespace osu.Game.Tests.Visual.Background { setupUserSettings(); - AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer(allowPause)))); + AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new LoadBlockingTestPlayer(allowPause)))); AddUntilStep("Wait for Player Loader to load", () => playerLoader.IsLoaded); AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); @@ -347,7 +347,7 @@ namespace osu.Game.Tests.Visual.Background public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR); } - private class TestPlayer : Visual.TestPlayer + private class LoadBlockingTestPlayer : TestPlayer { protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); @@ -360,7 +360,7 @@ namespace osu.Game.Tests.Visual.Background public readonly Bindable ReplacesBackground = new Bindable(); public readonly Bindable IsPaused = new Bindable(); - public TestPlayer(bool allowPause = true) + public LoadBlockingTestPlayer(bool allowPause = true) : base(allowPause) { } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 4daab8d137..756f31e0bf 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -5,7 +5,6 @@ using System.ComponentModel; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Storyboards; @@ -14,20 +13,22 @@ namespace osu.Game.Tests.Visual.Gameplay [Description("Player instantiated with an autoplay mod.")] public class TestSceneAutoplay : TestSceneAllRulesetPlayers { + protected new TestPlayer Player => (TestPlayer)base.Player; + private ClockBackedTestWorkingBeatmap.TrackVirtualManual track; protected override Player CreatePlayer(Ruleset ruleset) { SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); - return new ScoreAccessiblePlayer(); + return new TestPlayer(false, false); } protected override void AddCheckSteps() { - AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0); - AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2)); + AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0); + AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2)); AddStep("rewind", () => track.Seek(-10000)); - AddUntilStep("key counter reset", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); + AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); } protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) @@ -38,18 +39,5 @@ namespace osu.Game.Tests.Visual.Gameplay return working; } - - private class ScoreAccessiblePlayer : TestPlayer - { - public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; - public new HUDOverlay HUDOverlay => base.HUDOverlay; - - public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; - - public ScoreAccessiblePlayer() - : base(false, false) - { - } - } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs index 78c3b22fb9..310746d179 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -11,12 +10,8 @@ using osu.Framework.Utils; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; using osu.Game.Storyboards; using osuTK; @@ -24,8 +19,6 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneGameplayRewinding : PlayerTestScene { - private RulesetExposingPlayer player => (RulesetExposingPlayer)Player; - [Resolved] private AudioManager audioManager { get; set; } @@ -48,13 +41,13 @@ namespace osu.Game.Tests.Visual.Gameplay { AddUntilStep("wait for track to start running", () => track.IsRunning); addSeekStep(3000); - AddAssert("all judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged)); - AddUntilStep("key counter counted keys", () => player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7)); - AddStep("clear results", () => player.AppliedResults.Clear()); + AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged)); + AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7)); + AddStep("clear results", () => Player.Results.Clear()); addSeekStep(0); - AddAssert("none judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged)); - AddUntilStep("key counters reset", () => player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); - AddAssert("no results triggered", () => player.AppliedResults.Count == 0); + AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged)); + AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); + AddAssert("no results triggered", () => Player.Results.Count == 0); } private void addSeekStep(double time) @@ -62,13 +55,13 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep($"seek to {time}", () => track.Seek(time)); // Allow a few frames of lenience - AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } - protected override Player CreatePlayer(Ruleset ruleset) + protected override TestPlayer CreatePlayer(Ruleset ruleset) { SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); - return new RulesetExposingPlayer(); + return base.CreatePlayer(ruleset); } protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) @@ -89,29 +82,5 @@ namespace osu.Game.Tests.Visual.Gameplay return beatmap; } - - private class RulesetExposingPlayer : Player - { - public readonly List AppliedResults = new List(); - - public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; - - public new HUDOverlay HUDOverlay => base.HUDOverlay; - - public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; - - public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; - - public RulesetExposingPlayer() - : base(false, false) - { - } - - [BackgroundDependencyLoader] - private void load() - { - ScoreProcessor.NewJudgement += r => AppliedResults.Add(r); - } - } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index ad5bab4681..944e6ca6be 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -11,7 +11,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osuTK; using osuTK.Input; @@ -282,14 +281,10 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool AllowFail => true; - protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer(); + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new PausePlayer(); protected class PausePlayer : TestPlayer { - public new HealthProcessor HealthProcessor => base.HealthProcessor; - - public new HUDOverlay HUDOverlay => base.HUDOverlay; - public bool FailOverlayVisible => FailOverlay.State.Value == Visibility.Visible; public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible; diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs index 3513b6c25a..a83320048b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs @@ -9,15 +9,12 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; -using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual.Gameplay { [HeadlessTest] // we alter unsafe properties on the game host to test inactive window state. public class TestScenePauseWhenInactive : PlayerTestScene { - protected new TestPlayer Player => (TestPlayer)base.Player; - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { var beatmap = (Beatmap)base.CreateBeatmap(ruleset); @@ -46,6 +43,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("time of pause is after gameplay start time", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= Player.DrawableRuleset.GameplayStartTime); } - protected override Player CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true); + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 100f99d130..175f909a5a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; @@ -307,17 +306,7 @@ namespace osu.Game.Tests.Visual.Gameplay public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; } - private class TestPlayer : Visual.TestPlayer - { - public new Bindable> Mods => base.Mods; - - public TestPlayer(bool allowPause = true, bool showResults = true) - : base(allowPause, showResults) - { - } - } - - protected class SlowLoadPlayer : Visual.TestPlayer + protected class SlowLoadPlayer : TestPlayer { public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(false); diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 7c5ba7d30f..eee31fe014 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -8,7 +8,6 @@ using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { @@ -16,7 +15,7 @@ namespace osu.Game.Tests.Visual { private readonly Ruleset ruleset; - protected Player Player; + protected TestPlayer Player; protected PlayerTestScene(Ruleset ruleset) { @@ -69,6 +68,6 @@ namespace osu.Game.Tests.Visual LoadScreen(Player); } - protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); + protected virtual TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); } } diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 8e3821f1a0..f016d29f38 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -1,23 +1,51 @@ // 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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { + /// + /// A player that exposes many components that would otherwise not be available, for testing purposes. + /// public class TestPlayer : Player { protected override bool PauseOnFocusLost { get; } public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; + /// + /// Mods from *player* (not OsuScreen). + /// + public new Bindable> Mods => base.Mods; + + public new HUDOverlay HUDOverlay => base.HUDOverlay; + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + public new HealthProcessor HealthProcessor => base.HealthProcessor; + + public readonly List Results = new List(); + public TestPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) : base(allowPause, showResults) { PauseOnFocusLost = pauseOnFocusLost; } + + [BackgroundDependencyLoader] + private void load() + { + ScoreProcessor.NewJudgement += r => Results.Add(r); + } } } From 26ce0d05d6f45f07cb6214b3f1036200f98513e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 11:33:30 +0900 Subject: [PATCH 159/192] Use autoplay mod rather than local replay provider --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 22 ++-------- osu.Game/Tests/Visual/ModTestScene.cs | 41 ++++++++----------- osu.Game/Tests/Visual/TestReplayPlayer.cs | 24 ----------- 3 files changed, 21 insertions(+), 66 deletions(-) delete mode 100644 osu.Game/Tests/Visual/TestReplayPlayer.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index e4b1e30bcd..0a98f49526 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -3,8 +3,6 @@ using NUnit.Framework; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests.Mods @@ -17,11 +15,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } [Test] - public void TestNoAdjustment() => CreateModTest(new ModTestData() + public void TestNoAdjustment() => CreateModTest(new ModTestData { Mod = new OsuModDifficultyAdjust(), Autoplay = true, - PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 + PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 }); [Test] @@ -29,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }, Autoplay = true, - PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 + PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 }); [Test] @@ -37,19 +35,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }, Autoplay = true, - PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 + PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 }); - - protected override TestPlayer CreateReplayPlayer(Score score, bool allowFail) => new ScoreAccessibleTestPlayer(score, allowFail); - - private class ScoreAccessibleTestPlayer : TestPlayer - { - public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; - - public ScoreAccessibleTestPlayer(Score score, bool allowFail) - : base(score, allowFail) - { - } - } } } diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 3d12001cca..9abe543bf6 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -3,13 +3,10 @@ using System; using System.Collections.Generic; -using System.Linq; using JetBrains.Annotations; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Scoring; -using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { @@ -27,52 +24,48 @@ namespace osu.Game.Tests.Visual { } - private ModTestData currentTest; + private ModTestData currentTestData; protected void CreateModTest(ModTestData testData) => CreateTest(() => { - AddStep("set test data", () => currentTest = testData); + AddStep("set test data", () => currentTestData = testData); }); public override void TearDownSteps() { AddUntilStep("test passed", () => { - if (currentTest == null) + if (currentTestData == null) return true; - return currentTest.PassCondition?.Invoke() ?? false; + return currentTestData.PassCondition?.Invoke() ?? false; }); base.TearDownSteps(); } - protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTest?.Beatmap ?? base.CreateBeatmap(ruleset); + protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestData?.Beatmap ?? base.CreateBeatmap(ruleset); - protected sealed override Player CreatePlayer(Ruleset ruleset) + protected sealed override TestPlayer CreatePlayer(Ruleset ruleset) { - SelectedMods.Value = SelectedMods.Value.Append(currentTest.Mod).ToArray(); + var mods = new List(SelectedMods.Value); - var score = currentTest.Autoplay - ? ruleset.GetAutoplayMod().CreateReplayScore(Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, SelectedMods.Value)) - : null; + if (currentTestData.Mod != null) + mods.Add(currentTestData.Mod); + if (currentTestData.Autoplay) + mods.Add(ruleset.GetAutoplayMod()); - return CreateReplayPlayer(score, AllowFail); + SelectedMods.Value = mods; + + return new ModTestPlayer(AllowFail); } - /// - /// Creates the for a test case. - /// - /// The . - /// Whether the player can fail. - protected virtual TestPlayer CreateReplayPlayer(Score score, bool allowFail) => new TestPlayer(score, allowFail); - - protected class TestPlayer : TestReplayPlayer + protected class ModTestPlayer : TestPlayer { protected override bool AllowFail { get; } - public TestPlayer(Score score, bool allowFail) - : base(score, false, false) + public ModTestPlayer(bool allowFail) + : base(false, false) { AllowFail = allowFail; } diff --git a/osu.Game/Tests/Visual/TestReplayPlayer.cs b/osu.Game/Tests/Visual/TestReplayPlayer.cs deleted file mode 100644 index e99fcc1e37..0000000000 --- a/osu.Game/Tests/Visual/TestReplayPlayer.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.UI; -using osu.Game.Scoring; -using osu.Game.Screens.Play; - -namespace osu.Game.Tests.Visual -{ - public class TestReplayPlayer : ReplayPlayer - { - protected override bool PauseOnFocusLost { get; } - - public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; - - public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; - - public TestReplayPlayer(Score score, bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) - : base(score, allowPause, showResults) - { - PauseOnFocusLost = pauseOnFocusLost; - } - } -} From 9a12909f09a377f9348a496f0e95a52c2b51cedf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 11:53:04 +0900 Subject: [PATCH 160/192] Test ModDifficultyAdjust is actually taking effect --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 0a98f49526..69415b70e3 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -1,8 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests.Mods @@ -19,7 +25,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModDifficultyAdjust(), Autoplay = true, - PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 + PassCondition = checkSomeHit + }); + + [Test] + public void TestCircleSize1() => CreateModTest(new ModTestData + { + Mod = new OsuModDifficultyAdjust { CircleSize = { Value = 1 } }, + Autoplay = true, + PassCondition = () => checkSomeHit() && checkObjectsScale(0.78f) }); [Test] @@ -27,7 +41,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }, Autoplay = true, - PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 + PassCondition = () => checkSomeHit() && checkObjectsScale(0.15f) + }); + + [Test] + public void TestApproachRate1() => CreateModTest(new ModTestData + { + Mod = new OsuModDifficultyAdjust { ApproachRate = { Value = 1 } }, + Autoplay = true, + PassCondition = () => checkSomeHit() && checkObjectsPreempt(1680) }); [Test] @@ -35,7 +57,30 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }, Autoplay = true, - PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 + PassCondition = () => checkSomeHit() && checkObjectsPreempt(450) }); + + private bool checkObjectsPreempt(double target) + { + var objects = Player.ChildrenOfType(); + if (!objects.Any()) + return false; + + return objects.All(o => o.HitObject.TimePreempt == target); + } + + private bool checkObjectsScale(float target) + { + var objects = Player.ChildrenOfType(); + if (!objects.Any()) + return false; + + return objects.All(o => Precision.AlmostEquals(o.ChildrenOfType().First().Children.OfType().Single().Scale.X, target)); + } + + private bool checkSomeHit() + { + return Player.ScoreProcessor.JudgedHits >= 2; + } } } From 7229131d369c19ac8b54ab840c125e3bc83ee9a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 13:18:42 +0900 Subject: [PATCH 161/192] Fix song select max displayable star difficulty getting stuck at wrong maximum --- osu.Game/Configuration/OsuConfigManager.cs | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index ce959e9057..c5d68e4efe 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.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 osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Configuration.Tracking; using osu.Framework.Extensions; @@ -126,6 +127,34 @@ namespace osu.Game.Configuration public OsuConfigManager(Storage storage) : base(storage) { + Migrate(); + } + + public void Migrate() + { + // arrives as 2020.123.0 + var rawVersion = Get(OsuSetting.Version); + + if (rawVersion.Length < 6) + return; + + var pieces = rawVersion.Split('.'); + + if (!int.TryParse(pieces[0], out int year)) return; + if (!int.TryParse(pieces[1], out int monthDay)) return; + if (!int.TryParse(pieces[2], out int minor)) return; + + int combined = (year * 10000) + monthDay; + + if (combined < 20200305) + { + // the maximum value of this setting was changed. + // if we don't manually increase this, it causes song select to filter out beatmaps the user expects to see. + var maxStars = (BindableDouble)GetOriginalBindable(OsuSetting.DisplayStarsMaximum); + + if (maxStars.Value == 10) + maxStars.Value = maxStars.MaxValue; + } } public override TrackedSettings CreateTrackedSettings() => new TrackedSettings From 6477a7b73e1d7ede6f77cfb76b15e63d1be20bdb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 13:34:04 +0900 Subject: [PATCH 162/192] Centralise creation of UpdateManagers --- osu.Android/OsuGameAndroid.cs | 7 +------ osu.Desktop/OsuGameDesktop.cs | 19 ++++++++++++------- osu.Game/OsuGame.cs | 4 ++++ osu.iOS/OsuGameIOS.cs | 7 ------- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index a91c010809..84f215f930 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -30,11 +30,6 @@ namespace osu.Android } } - protected override void LoadComplete() - { - base.LoadComplete(); - - Add(new SimpleUpdateManager()); - } + protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); } } \ No newline at end of file diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index f70cc24159..f05ee48914 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -47,20 +47,25 @@ namespace osu.Desktop return null; } + protected override UpdateManager CreateUpdateManager() + { + switch (RuntimeInfo.OS) + { + case RuntimeInfo.Platform.Windows: + return new SquirrelUpdateManager(); + + default: + return new SimpleUpdateManager(); + } + } + protected override void LoadComplete() { base.LoadComplete(); if (!noVersionOverlay) - { LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add); - if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) - Add(new SquirrelUpdateManager()); - else - Add(new SimpleUpdateManager()); - } - LoadComponentAsync(new DiscordRichPresence(), Add); } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5781a7fbc4..916464ff53 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -43,6 +43,7 @@ using osu.Game.Overlays.Volume; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Select; +using osu.Game.Updater; using osu.Game.Utils; using LogLevel = osu.Framework.Logging.LogLevel; @@ -390,6 +391,8 @@ namespace osu.Game protected virtual Loader CreateLoader() => new Loader(); + protected virtual UpdateManager CreateUpdateManager() => new UpdateManager(); + protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything); #region Beatmap progression @@ -528,6 +531,7 @@ namespace osu.Game AddRange(new Drawable[] { + CreateUpdateManager(), new VolumeControlReceptor { RelativeSizeAxes = Axes.Both, diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index e5ff4aec95..3a16f81530 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -11,12 +11,5 @@ namespace osu.iOS public class OsuGameIOS : OsuGame { public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString()); - - protected override void LoadComplete() - { - base.LoadComplete(); - - Add(new UpdateManager()); - } } } From 74b5e76c0ebec3360fb998f4743e461329a12083 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 13:39:55 +0900 Subject: [PATCH 163/192] Fix dependency initialisation ordering --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 916464ff53..50aefb4dd2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -531,7 +531,6 @@ namespace osu.Game AddRange(new Drawable[] { - CreateUpdateManager(), new VolumeControlReceptor { RelativeSizeAxes = Axes.Both, @@ -632,6 +631,7 @@ namespace osu.Game chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible; Add(externalLinkOpener = new ExternalLinkOpener()); + Add(CreateUpdateManager()); // dependency on notification overlay // side overlays which cancel each other. var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications }; From 1e6710020e3adc3da750730ab6f66802dc50488f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 13:46:25 +0900 Subject: [PATCH 164/192] Remove minor version for now --- osu.Game/Configuration/OsuConfigManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index c5d68e4efe..5b20700086 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -142,7 +142,6 @@ namespace osu.Game.Configuration if (!int.TryParse(pieces[0], out int year)) return; if (!int.TryParse(pieces[1], out int monthDay)) return; - if (!int.TryParse(pieces[2], out int minor)) return; int combined = (year * 10000) + monthDay; From 1e1e8cbcb5915ad1d0db77d34926ebc54d0218aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 14:46:38 +0900 Subject: [PATCH 165/192] Always update version --- osu.Game/Updater/UpdateManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 48505a9891..f7a7795d9b 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -34,12 +34,12 @@ namespace osu.Game.Updater if (game.IsDeployedBuild && version != lastVersion) { - config.Set(OsuSetting.Version, version); - // only show a notification if we've previously saved a version to the config file (ie. not the first run). if (!string.IsNullOrEmpty(lastVersion)) Notifications.Post(new UpdateCompleteNotification(version)); } + + config.Set(OsuSetting.Version, version); } private class UpdateCompleteNotification : SimpleNotification From a311ace62656d854b8136c93ad7f7dcb91e1e9ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 14:46:07 +0900 Subject: [PATCH 166/192] Add migration test --- .../Visual/Navigation/OsuGameTestScene.cs | 23 +++++++---- .../Navigation/TestSettingsMigration.cs | 41 +++++++++++++++++++ osu.Game/OsuGameBase.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 4 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index 70d71d0952..b0bfb64d61 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -62,14 +62,7 @@ namespace osu.Game.Tests.Visual.Navigation var frameworkConfig = host.Dependencies.Get(); frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity).Disabled = false; - Game = new TestOsuGame(LocalStorage, API); - Game.SetHost(host); - - // todo: this can be removed once we can run audio tracks without a device present - // see https://github.com/ppy/osu/issues/1302 - Game.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles); - - Add(Game); + CreateGame(); }); AddUntilStep("Wait for load", () => Game.IsLoaded); @@ -78,6 +71,18 @@ namespace osu.Game.Tests.Visual.Navigation ConfirmAtMainMenu(); } + protected void CreateGame() + { + Game = new TestOsuGame(LocalStorage, API); + Game.SetHost(host); + + // todo: this can be removed once we can run audio tracks without a device present + // see https://github.com/ppy/osu/issues/1302 + Game.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles); + + Add(Game); + } + protected void PushAndConfirm(Func newScreen) { Screen screen = null; @@ -103,6 +108,8 @@ namespace osu.Game.Tests.Visual.Navigation public new Bindable Ruleset => base.Ruleset; + public override string Version => "test game"; + protected override Loader CreateLoader() => new TestLoader(); public new void PerformFromScreen(Action action, IEnumerable validScreens = null) => base.PerformFromScreen(action, validScreens); diff --git a/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs b/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs new file mode 100644 index 0000000000..c0b77b580e --- /dev/null +++ b/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Utils; +using osu.Game.Configuration; + +namespace osu.Game.Tests.Visual.Navigation +{ + public class TestSettingsMigration : OsuGameTestScene + { + public override void RecycleLocalStorage() + { + base.RecycleLocalStorage(); + + using (var config = new OsuConfigManager(LocalStorage)) + { + config.Set(OsuSetting.Version, "2020.101.0"); + config.Set(OsuSetting.DisplayStarsMaximum, 10.0); + } + } + + [Test] + public void TestDisplayStarsMigration() + { + AddAssert("config has migrated value", () => Precision.AlmostEquals(Game.LocalConfig.Get(OsuSetting.DisplayStarsMaximum), 10.1)); + + AddStep("set value again", () => Game.LocalConfig.Set(OsuSetting.DisplayStarsMaximum, 10)); + + AddStep("force save config", () => Game.LocalConfig.Save()); + + AddStep("remove game", () => Remove(Game)); + + AddStep("create game again", CreateGame); + + AddUntilStep("Wait for load", () => Game.IsLoaded); + + AddAssert("config did not migrate value", () => Precision.AlmostEquals(Game.LocalConfig.Get(OsuSetting.DisplayStarsMaximum), 10)); + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a890331f05..33333e592e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -97,7 +97,7 @@ namespace osu.Game public bool IsDeployedBuild => AssemblyVersion.Major > 0; - public string Version + public virtual string Version { get { diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index b203557fab..f102e2ece3 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual base.Content.Add(content = new DrawSizePreservingFillContainer()); } - public void RecycleLocalStorage() + public virtual void RecycleLocalStorage() { if (localStorage?.IsValueCreated == true) { From 507af4fa72cb9c7e98733eb5925198ab4caac756 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 15:35:27 +0900 Subject: [PATCH 167/192] Add comment about rationale behind always updating version in config --- osu.Game/Updater/UpdateManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index f7a7795d9b..28a295215f 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -39,6 +39,8 @@ namespace osu.Game.Updater Notifications.Post(new UpdateCompleteNotification(version)); } + // debug / local compilations will reset to a non-release string. + // can be useful to check when an install has transitioned between release and otherwise (see OsuConfigManager's migrations). config.Set(OsuSetting.Version, version); } From 5b8037ea7d2b69e29ae82eb54aba16e2bd07efe9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 15:36:36 +0900 Subject: [PATCH 168/192] Add note about early migration return on non-release transitions --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 5b20700086..21de654670 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -140,6 +140,8 @@ namespace osu.Game.Configuration var pieces = rawVersion.Split('.'); + // on a fresh install or when coming from a non-release build, execution will end here. + // we don't want to run migrations in such cases. if (!int.TryParse(pieces[0], out int year)) return; if (!int.TryParse(pieces[1], out int monthDay)) return; From 646c8fe077eca5b6630b397e38fc5b3c6330e7ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 15:40:48 +0900 Subject: [PATCH 169/192] Add note about version override --- osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index b0bfb64d61..ea8a06e990 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -108,6 +108,7 @@ namespace osu.Game.Tests.Visual.Navigation public new Bindable Ruleset => base.Ruleset; + // if we don't do this, when running under nUnit the version that gets populated is that of nUnit. public override string Version => "test game"; protected override Loader CreateLoader() => new TestLoader(); From 9307caa3bfb60e7636033fe78311549da1653487 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Mar 2020 16:58:07 +0900 Subject: [PATCH 170/192] Fix typos --- osu.Game/OsuGame.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0be9e6cdaa..e54bbaabb2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -410,7 +410,7 @@ namespace osu.Game void trackCompleted(WorkingBeatmap b) { - // the source of track completion is the audio thread, so the beatmap may have changed before a firing. + // the source of track completion is the audio thread, so the beatmap may have changed before firing. if (Beatmap.Value != b) return; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 67aa4a8d4d..1048b37348 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -214,7 +214,7 @@ namespace osu.Game // ScheduleAfterChildren is safety against something in the current frame accessing the previous beatmap's track // and potentially causing a reload of it after just unloading. - // Note that the reason for this being added *has* been resolved, so it may be feasible to remover this if required. + // Note that the reason for this being added *has* been resolved, so it may be feasible to removed this if required. Beatmap.BindValueChanged(b => ScheduleAfterChildren(() => { // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) From 0c1775b52281517e8f6f0518fc930b42b0f26aca Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Mar 2020 17:12:14 +0900 Subject: [PATCH 171/192] Fix incorrect condition and add test --- .../Visual/Navigation/OsuGameTestScene.cs | 2 ++ .../Navigation/TestSceneScreenNavigation.cs | 16 ++++++++++++++++ osu.Game/OsuGame.cs | 10 +++++----- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index 70d71d0952..e984806dc9 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -97,6 +97,8 @@ namespace osu.Game.Tests.Visual.Navigation public new SettingsPanel Settings => base.Settings; + public new MusicController MusicController => base.MusicController; + public new OsuConfigManager LocalConfig => base.LocalConfig; public new Bindable Beatmap => base.Beatmap; diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 8258cc9465..9d603ac471 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -114,6 +114,22 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("Options overlay was closed", () => Game.Settings.State.Value == Visibility.Hidden); } + [Test] + public void TestWaitForNextTrackInMenu() + { + bool trackCompleted = false; + + AddUntilStep("Wait for music controller", () => Game.MusicController.IsLoaded); + AddStep("Seek close to end", () => + { + Game.MusicController.SeekTo(Game.Beatmap.Value.Track.Length - 1000); + Game.Beatmap.Value.Track.Completed += () => trackCompleted = true; + }); + + AddUntilStep("Track was completed", () => trackCompleted); + AddUntilStep("Track was restarted", () => Game.Beatmap.Value.Track.IsRunning); + } + private void pushEscape() => AddStep("Press escape", () => pressAndRelease(Key.Escape)); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e54bbaabb2..19602d524e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -414,8 +414,8 @@ namespace osu.Game if (Beatmap.Value != b) return; - if (Beatmap.Value.Track.Looping && !Beatmap.Disabled) - musicController.NextTrack(); + if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled) + MusicController.NextTrack(); } } @@ -588,7 +588,7 @@ namespace osu.Game loadComponentSingleFile(new OnScreenDisplay(), Add, true); - loadComponentSingleFile(musicController = new MusicController(), Add, true); + loadComponentSingleFile(MusicController = new MusicController(), Add, true); loadComponentSingleFile(notifications = new NotificationOverlay { @@ -896,7 +896,7 @@ namespace osu.Game private ScalingContainer screenContainer; - private MusicController musicController; + protected MusicController MusicController { get; private set; } protected override bool OnExiting() { @@ -954,7 +954,7 @@ namespace osu.Game { OverlayActivationMode.Value = newOsuScreen.InitialOverlayActivationMode; - musicController.AllowRateAdjustments = newOsuScreen.AllowRateAdjustments; + MusicController.AllowRateAdjustments = newOsuScreen.AllowRateAdjustments; if (newOsuScreen.HideOverlaysOnEnter) CloseAllOverlays(); From 583e2c3f4a616fea8d2b9abc7065135e917217d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Mar 2020 00:10:05 +0900 Subject: [PATCH 172/192] Actually check rate is applied --- .../Mods/TestSceneOsuModDoubleTime.cs | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs index deb733c581..dcf19ad993 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs @@ -2,14 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Utils; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests.Mods { - public class TestSceneOsuModDoubleTime : ModSandboxTestScene + public class TestSceneOsuModDoubleTime : ModTestScene { public TestSceneOsuModDoubleTime() : base(new OsuRuleset()) @@ -21,21 +20,16 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods [TestCase(1.5)] [TestCase(2)] [TestCase(5)] - public void TestDefaultRate(double rate) => CreateModTest(new ModTestCaseData("1.5x", new OsuModDoubleTime { SpeedChange = { Value = rate } }) + public void TestSpeedChangeCustomisation(double rate) { - PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 - }); + var mod = new OsuModDoubleTime { SpeedChange = { Value = rate } }; - protected override TestPlayer CreateReplayPlayer(Score score) => new ScoreAccessibleTestPlayer(score); - - private class ScoreAccessibleTestPlayer : TestPlayer - { - public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; - - public ScoreAccessibleTestPlayer(Score score) - : base(score) + CreateModTest(new ModTestData { - } + Mod = mod, + PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 && + Precision.AlmostEquals(Player.GameplayClockContainer.GameplayClock.Rate, mod.SpeedChange.Value) + }); } } } From e3509c742c4ec1bb9df56d4a3c1f733ad4587f58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Mar 2020 01:28:59 +0900 Subject: [PATCH 173/192] Track time in a simpler way in TrackVirtualManual --- osu.Game/Tests/Visual/OsuTestScene.cs | 47 ++++++++++++++++++++------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 917d12ebb1..3c95b990e1 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -229,10 +229,10 @@ namespace osu.Game.Tests.Visual /// public class TrackVirtualManual : Track { - private readonly StopwatchClock stopwatchClock = new StopwatchClock(); - private readonly IFrameBasedClock referenceClock; + private bool running; + public TrackVirtualManual(IFrameBasedClock referenceClock) { this.referenceClock = referenceClock; @@ -241,32 +241,55 @@ namespace osu.Game.Tests.Visual public override bool Seek(double seek) { - var offset = Math.Clamp(seek, 0, Length); + accumulated = Math.Min(seek, Length); + lastReferenceTime = null; - stopwatchClock.Seek(offset); - - return offset == seek; + return accumulated == seek; } - public override void Start() => stopwatchClock.Start(); + public override void Start() + { + running = true; + } public override void Reset() { - stopwatchClock.Seek(0); + Seek(0); base.Reset(); } - public override void Stop() => stopwatchClock.Stop(); + public override void Stop() + { + if (running) + { + running = false; + lastReferenceTime = null; + } + } - public override bool IsRunning => stopwatchClock.IsRunning; + public override bool IsRunning => running; - public override double CurrentTime => stopwatchClock.CurrentTime; + private double? lastReferenceTime; + + private double accumulated; + + public override double CurrentTime => Math.Min(accumulated, Length); protected override void UpdateState() { base.UpdateState(); - stopwatchClock.Rate = Rate * referenceClock.Rate; + if (running) + { + double refTime = referenceClock.CurrentTime; + + if (lastReferenceTime.HasValue) + accumulated += (refTime - lastReferenceTime.Value) * Rate; + + lastReferenceTime = refTime; + } + + Console.WriteLine($"t={CurrentTime}"); if (CurrentTime >= Length) { From ebc86c10754ede8686e06b06682c272ff117b562 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Mar 2020 02:08:49 +0900 Subject: [PATCH 174/192] Fix random test failure --- osu.Game/Screens/Play/SkipOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 3daf5b1ff1..ac7e509c2c 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -116,7 +116,7 @@ namespace osu.Game.Screens.Play { base.Update(); - var progress = Math.Max(0, 1 - (gameplayClock.CurrentTime - displayTime) / (fadeOutBeginTime - displayTime)); + var progress = fadeOutBeginTime <= displayTime ? 1 : Math.Max(0, 1 - (gameplayClock.CurrentTime - displayTime) / (fadeOutBeginTime - displayTime)); remainingTimeBox.Width = (float)Interpolation.Lerp(remainingTimeBox.Width, progress, Math.Clamp(Time.Elapsed / 40, 0, 1)); From c3ad08f230b635f4dcc8582c5ee2028f04cd25c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Mar 2020 02:18:35 +0900 Subject: [PATCH 175/192] Remove wild writeline --- osu.Game/Tests/Visual/OsuTestScene.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 3c95b990e1..5623435da4 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -289,8 +289,6 @@ namespace osu.Game.Tests.Visual lastReferenceTime = refTime; } - Console.WriteLine($"t={CurrentTime}"); - if (CurrentTime >= Length) { Stop(); From bd1dbea6f4b35937777ef3f5b017d2bf787e35e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 Mar 2020 23:10:14 +0100 Subject: [PATCH 176/192] Centralise background colour updates --- .../Profile/Sections/ProfileItemContainer.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs b/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs index 1ab9ed3f82..c057ebe12b 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs @@ -27,8 +27,7 @@ namespace osu.Game.Overlays.Profile.Sections set { idleColour = value; - if (!IsHovered) - background.Colour = value; + fadeBackgroundColour(); } } @@ -40,8 +39,7 @@ namespace osu.Game.Overlays.Profile.Sections set { hoverColour = value; - if (IsHovered) - background.Colour = value; + fadeBackgroundColour(); } } @@ -73,14 +71,19 @@ namespace osu.Game.Overlays.Profile.Sections protected override bool OnHover(HoverEvent e) { - background.FadeColour(HoverColour, hover_duration, Easing.OutQuint); + fadeBackgroundColour(hover_duration); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - background.FadeColour(IdleColour, hover_duration, Easing.OutQuint); + fadeBackgroundColour(hover_duration); + } + + private void fadeBackgroundColour(double fadeDuration = 0) + { + background.FadeColour(IsHovered ? HoverColour : IdleColour, fadeDuration, Easing.OutQuint); } } } From 5b0846cb69a2f5aca2da311d179169fc1c0f5deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 Mar 2020 23:15:53 +0100 Subject: [PATCH 177/192] Handle hover explicitly --- osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs b/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs index c057ebe12b..afa6bd9f79 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs @@ -72,7 +72,7 @@ namespace osu.Game.Overlays.Profile.Sections protected override bool OnHover(HoverEvent e) { fadeBackgroundColour(hover_duration); - return base.OnHover(e); + return true; } protected override void OnHoverLost(HoverLostEvent e) From ac88ba717b7f6589b01950ac915a4801eac4a007 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Mar 2020 16:03:26 +0900 Subject: [PATCH 178/192] Ensure screens respect aspect ratio in tests --- osu.Game.Tournament/Screens/TournamentScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tournament/Screens/TournamentScreen.cs b/osu.Game.Tournament/Screens/TournamentScreen.cs index 0b5b3e728b..5da7c7a5d2 100644 --- a/osu.Game.Tournament/Screens/TournamentScreen.cs +++ b/osu.Game.Tournament/Screens/TournamentScreen.cs @@ -18,6 +18,9 @@ namespace osu.Game.Tournament.Screens protected TournamentScreen() { RelativeSizeAxes = Axes.Both; + + FillMode = FillMode.Fit; + FillAspectRatio = 16 / 9f; } public override void Hide() => this.FadeOut(FADE_DELAY); From 8ff3370273fb9c857e0117bab3235b6408caf6c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Mar 2020 16:27:38 +0900 Subject: [PATCH 179/192] Add a short load delay for avatars to avoid unnecessary fetching --- osu.Game/Users/Drawables/UpdateableAvatar.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index 59fbb5f910..171462f3fc 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -43,6 +43,8 @@ namespace osu.Game.Users.Drawables set => base.EdgeEffect = value; } + protected override double LoadDelay => 200; + /// /// Whether to show a default guest representation on null user (as opposed to nothing). /// From 88759e65a0b2702defc8e036561ad2d18fbb02dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Mar 2020 16:31:36 +0900 Subject: [PATCH 180/192] Remove layout durations from tournament editor screns for better performance --- osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs | 2 -- osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs | 2 -- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 2 -- osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs | 2 -- 4 files changed, 8 deletions(-) diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs index 7119533743..8b8078e119 100644 --- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -129,8 +129,6 @@ namespace osu.Game.Tournament.Screens.Editors RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - LayoutDuration = 200, - LayoutEasing = Easing.OutQuint, ChildrenEnumerable = round.Beatmaps.Select(p => new RoundBeatmapRow(round, p)) }; } diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs index e68946aaf2..46bb7b83e3 100644 --- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs @@ -124,8 +124,6 @@ namespace osu.Game.Tournament.Screens.Editors RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - LayoutDuration = 200, - LayoutEasing = Easing.OutQuint, ChildrenEnumerable = round.Beatmaps.Select(p => new SeedingBeatmapRow(round, p)) }; } diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index ca8bce1cca..631393c6f4 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -202,8 +202,6 @@ namespace osu.Game.Tournament.Screens.Editors RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - LayoutDuration = 200, - LayoutEasing = Easing.OutQuint, ChildrenEnumerable = team.Players.Select(p => new PlayerRow(team, p)) }; } diff --git a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs index 5598910824..e4256e727d 100644 --- a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs @@ -48,8 +48,6 @@ namespace osu.Game.Tournament.Screens.Editors Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - LayoutDuration = 200, - LayoutEasing = Easing.OutQuint, Spacing = new Vector2(20) }, }, From 40074f10dbd5999dca3f38dc0a6de093c28de463 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Mar 2020 17:55:05 +0900 Subject: [PATCH 181/192] Remove unnecessary override --- osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 9140cccafd..2a8f77210a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -32,12 +32,5 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000)); AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); } - - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - { - var working = base.CreateWorkingBeatmap(beatmap, storyboard); - - return working; - } } } From 3b0e3cd71a3ae257ec3f21f774c1ba2c63cf8e53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Mar 2020 17:55:57 +0900 Subject: [PATCH 182/192] Remove using statements --- osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 2a8f77210a..afeda5fb7c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -3,10 +3,8 @@ using System.ComponentModel; using System.Linq; -using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Screens.Play; -using osu.Game.Storyboards; namespace osu.Game.Tests.Visual.Gameplay { From 0ccf691c972f3de8253c357b101e2f5c8ba5e93d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Mar 2020 18:00:07 +0900 Subject: [PATCH 183/192] Remove unnecessary interpolation --- osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index 10827bc0b9..227ada70fe 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 1); addPressKeyStep(); AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 2); - AddStep($"Disable counting", () => testCounter.IsCounting = false); + AddStep("Disable counting", () => testCounter.IsCounting = false); addPressKeyStep(); AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses == 2); From a59c3d997da57bcfa33e2f3342222ccaa825d674 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Mar 2020 18:00:17 +0900 Subject: [PATCH 184/192] Refactor implementation to better match what already existed --- osu.Game/Screens/Play/Player.cs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 2d49c707ec..bcadba14af 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -157,10 +157,7 @@ namespace osu.Game.Screens.Play addGameplayComponents(GameplayClockContainer, Beatmap.Value); addOverlayComponents(GameplayClockContainer, Beatmap.Value); - DrawableRuleset.HasReplayLoaded.BindValueChanged(e => - { - updatePauseOnFocusLostState(e.NewValue, BreakOverlay.IsBreakTime.Value); - }, true); + DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); @@ -232,7 +229,11 @@ namespace osu.Game.Screens.Play IsPaused = { BindTarget = GameplayClockContainer.IsPaused } }, PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, - KeyCounter = { AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded }, IsCounting = false }, + KeyCounter = + { + AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded }, + IsCounting = false + }, RequestSeek = GameplayClockContainer.Seek, Anchor = Anchor.Centre, Origin = Anchor.Centre @@ -289,16 +290,16 @@ namespace osu.Game.Screens.Play HealthProcessor.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); } - private void onBreakTimeChanged(ValueChangedEvent changeEvent) + private void onBreakTimeChanged(ValueChangedEvent isBreakTime) { - updatePauseOnFocusLostState(DrawableRuleset.HasReplayLoaded.Value, changeEvent.NewValue); - HUDOverlay.KeyCounter.IsCounting = !changeEvent.NewValue; + updatePauseOnFocusLostState(); + HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue; } - private void updatePauseOnFocusLostState(bool replayLoaded, bool isBreakTime) => + private void updatePauseOnFocusLostState() => HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost - && !replayLoaded - && !isBreakTime; + && !DrawableRuleset.HasReplayLoaded.Value + && !BreakOverlay.IsBreakTime.Value; private IBeatmap loadPlayableBeatmap() { From 2d95f2992534444949ebbf6a66dd53b333a94c9f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Mar 2020 16:06:23 +0900 Subject: [PATCH 185/192] Add gameplay screen specific video --- osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index 6a3095d42d..c74302a869 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Graphics.UserInterface; using osu.Game.Tournament.Components; @@ -19,7 +20,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Gameplay { - public class GameplayScreen : BeatmapInfoScreen + public class GameplayScreen : BeatmapInfoScreen, IProvideVideo { private readonly BindableBool warmup = new BindableBool(); @@ -39,12 +40,17 @@ namespace osu.Game.Tournament.Screens.Gameplay private TournamentMatchChatDisplay chat { get; set; } [BackgroundDependencyLoader] - private void load(LadderInfo ladder, MatchIPCInfo ipc) + private void load(LadderInfo ladder, MatchIPCInfo ipc, Storage storage) { this.ipc = ipc; AddRangeInternal(new Drawable[] { + new TourneyVideo(storage.GetStream("videos/gameplay.m4v")) + { + Loop = true, + RelativeSizeAxes = Axes.Both, + }, new MatchHeader(), new Container { From 0a72fa69ab70bbb14fa7742e19a6d0075aefa21f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Mar 2020 18:38:29 +0900 Subject: [PATCH 186/192] Simplify video creation (and handle fallback better) --- .../Components/TourneyVideo.cs | 38 ++++++++++++------- .../Screens/Gameplay/GameplayScreen.cs | 2 +- .../Screens/Ladder/LadderScreen.cs | 2 +- .../Screens/Schedule/ScheduleScreen.cs | 4 +- .../Screens/TeamIntro/SeedingScreen.cs | 2 +- .../Screens/TeamIntro/TeamIntroScreen.cs | 2 +- .../Screens/TeamWin/TeamWinScreen.cs | 4 +- osu.Game.Tournament/TournamentSceneManager.cs | 2 +- 8 files changed, 34 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 206689ca1a..7d2eaff515 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -1,12 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.IO; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Video; +using osu.Framework.Platform; using osu.Framework.Timing; using osu.Game.Graphics; @@ -14,21 +15,24 @@ namespace osu.Game.Tournament.Components { public class TourneyVideo : CompositeDrawable { - private readonly VideoSprite video; + private readonly string filename; + private readonly bool drawFallbackGradient; + private VideoSprite video; - private readonly ManualClock manualClock; + private ManualClock manualClock; - public TourneyVideo(Stream stream) + public TourneyVideo(string filename, bool drawFallbackGradient = false) { - if (stream == null) - { - InternalChild = new Box - { - Colour = ColourInfo.GradientVertical(OsuColour.Gray(0.3f), OsuColour.Gray(0.6f)), - RelativeSizeAxes = Axes.Both, - }; - } - else + this.filename = filename; + this.drawFallbackGradient = drawFallbackGradient; + } + + [BackgroundDependencyLoader] + private void load(Storage storage) + { + var stream = storage.GetStream($@"videos/{filename}.m4v"); + + if (stream != null) { InternalChild = video = new VideoSprite(stream) { @@ -37,6 +41,14 @@ namespace osu.Game.Tournament.Components Clock = new FramedClock(manualClock = new ManualClock()) }; } + else if (drawFallbackGradient) + { + InternalChild = new Box + { + Colour = ColourInfo.GradientVertical(OsuColour.Gray(0.3f), OsuColour.Gray(0.6f)), + RelativeSizeAxes = Axes.Both, + }; + } } public bool Loop diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index c74302a869..d632e7c5f3 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tournament.Screens.Gameplay AddRangeInternal(new Drawable[] { - new TourneyVideo(storage.GetStream("videos/gameplay.m4v")) + new TourneyVideo("gameplay") { Loop = true, RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index 293f6e0068..7b265ded32 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tournament.Screens.Ladder RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new TourneyVideo(storage.GetStream(@"videos/ladder.m4v")) + new TourneyVideo("ladder") { RelativeSizeAxes = Axes.Both, Loop = true, diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index 080570eac4..4c93c04fcf 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -18,7 +18,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Schedule { - public class ScheduleScreen : TournamentScreen, IProvideVideo + public class ScheduleScreen : TournamentScreen { private readonly Bindable currentMatch = new Bindable(); private Container mainContainer; @@ -33,7 +33,7 @@ namespace osu.Game.Tournament.Screens.Schedule InternalChildren = new Drawable[] { - new TourneyVideo(storage.GetStream(@"videos/schedule.m4v")) + new TourneyVideo("schedule") { RelativeSizeAxes = Axes.Both, Loop = true, diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index db5363c155..513d84b594 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro InternalChildren = new Drawable[] { - new TourneyVideo(storage.GetStream(@"videos/seeding.m4v")) + new TourneyVideo("seeding") { RelativeSizeAxes = Axes.Both, Loop = true, diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs index 6559113f55..d584c21058 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro InternalChildren = new Drawable[] { - new TourneyVideo(storage.GetStream(@"videos/teamintro.m4v")) + new TourneyVideo("teamintro") { RelativeSizeAxes = Axes.Both, Loop = true, diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index 30b86f8421..1765ab7ba2 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -31,13 +31,13 @@ namespace osu.Game.Tournament.Screens.TeamWin InternalChildren = new Drawable[] { - blueWinVideo = new TourneyVideo(storage.GetStream(@"videos/teamwin-blue.m4v")) + blueWinVideo = new TourneyVideo("teamwin-blue") { Alpha = 1, RelativeSizeAxes = Axes.Both, Loop = true, }, - redWinVideo = new TourneyVideo(storage.GetStream(@"videos/teamwin-red.m4v")) + redWinVideo = new TourneyVideo("teamwin-red") { Alpha = 0, RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 9f5f2b6827..287e25b1fb 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tournament //Masking = true, Children = new Drawable[] { - video = new TourneyVideo(storage.GetStream("videos/main.m4v")) + video = new TourneyVideo("main", true) { Loop = true, RelativeSizeAxes = Axes.Both, From 16cc49daa00d3c4af18130162ccee7f74a1bf098 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Mar 2020 18:47:31 +0900 Subject: [PATCH 187/192] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 1c4a6ffe75..97f7a7edb1 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4d59b709aa..855bda3679 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 6897d3e625..e2c4c09047 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + @@ -79,7 +79,7 @@ - + From 3295f8657ac3d74a95c10016273068fbbdedde26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Mar 2020 22:44:11 +0900 Subject: [PATCH 188/192] Restore clamp behaviour --- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 5623435da4..d1d8059cb1 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -241,7 +241,7 @@ namespace osu.Game.Tests.Visual public override bool Seek(double seek) { - accumulated = Math.Min(seek, Length); + accumulated = Math.Clamp(seek, 0, Length); lastReferenceTime = null; return accumulated == seek; From 491840b17d215cd983dca0badaac0a502e6d0d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 Mar 2020 21:10:23 +0100 Subject: [PATCH 189/192] Add failing tests --- .../TestSceneHitCircleArea.cs | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs new file mode 100644 index 0000000000..06eccdafdb --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs @@ -0,0 +1,108 @@ +// 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 NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneHitCircleArea : ManualInputManagerTestScene + { + private HitCircle hitCircle; + private DrawableHitCircle drawableHitCircle; + private DrawableHitCircle.HitReceptor hitAreaReceptor => drawableHitCircle.HitArea; + + [SetUp] + public new void SetUp() + { + base.SetUp(); + + Schedule(() => + { + hitCircle = new HitCircle + { + Position = new Vector2(100, 100), + StartTime = Time.Current + 500 + }; + + hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + Child = new SkinProvidingContainer(new DefaultSkin()) + { + RelativeSizeAxes = Axes.Both, + Child = drawableHitCircle = new DrawableHitCircle(hitCircle) + { + Size = new Vector2(100) + } + }; + }); + } + + [Test] + public void TestCircleHitCentre() + { + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(hitAreaReceptor.ScreenSpaceDrawQuad.Centre)); + scheduleHit(); + + AddAssert("hit registered", () => hitAreaReceptor.HitAction == OsuAction.LeftButton); + } + + [Test] + public void TestCircleHitLeftEdge() + { + AddStep("move mouse to left edge", () => + { + var drawQuad = hitAreaReceptor.ScreenSpaceDrawQuad; + var mousePosition = new Vector2(drawQuad.TopLeft.X, drawQuad.Centre.Y); + + InputManager.MoveMouseTo(mousePosition); + }); + scheduleHit(); + + AddAssert("hit registered", () => hitAreaReceptor.HitAction == OsuAction.LeftButton); + } + + [Test] + public void TestCircleHitTopLeftEdge() + { + AddStep("move mouse to top left circle edge", () => + { + var drawQuad = hitAreaReceptor.ScreenSpaceDrawQuad; + // sqrt(2) / 2 = sin(45deg) = cos(45deg) + // draw width halved to get radius + // 0.95f taken for leniency + float correction = 0.95f * (float)Math.Sqrt(2) / 2 * (drawQuad.Width / 2); + var mousePosition = new Vector2(drawQuad.Centre.X - correction, drawQuad.Centre.Y - correction); + + InputManager.MoveMouseTo(mousePosition); + }); + scheduleHit(); + + AddAssert("hit registered", () => hitAreaReceptor.HitAction == OsuAction.LeftButton); + } + + [Test] + public void TestCircleMissBoundingBoxCorner() + { + AddStep("move mouse to top left corner of bounding box", () => InputManager.MoveMouseTo(hitAreaReceptor.ScreenSpaceDrawQuad.TopLeft)); + scheduleHit(); + + AddAssert("hit not registered", () => hitAreaReceptor.HitAction == null); + } + + private void scheduleHit() => AddStep("schedule action", () => + { + var delay = hitCircle.StartTime - hitCircle.HitWindows.WindowFor(HitResult.Great) - Time.Current; + Scheduler.AddDelayed(() => hitAreaReceptor.OnPressed(OsuAction.LeftButton), delay); + }); + } +} From 77fd7480352b3dbc834fe7fa85b01d17491466c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 Mar 2020 21:21:20 +0100 Subject: [PATCH 190/192] Fix incorrect circle piece hitbox Hitboxes of circle pieces in osu! have regressed with commit 8592335. The reason for the regression was that hit detection was moved from DrawableHitCircle itself to a newly-introduced private HitArea class (now named HitReceptor). As HitArea inherited from Drawable, it would return IsHovered == true over its entire bounding box. This meant that the hit area could wrongly pick up actions that are not within circle radius and make them into hits. To resolve, make HitReceptor a CompositeDrawable and set its corner radius to match the circle piece. This fixes the invalid hitbox, as IsHovered takes radius into account. --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 4ef63bb2a0..da1e666aba 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -170,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Drawable ProxiedLayer => ApproachCircle; - public class HitReceptor : Drawable, IKeyBindingHandler + public class HitReceptor : CompositeDrawable, IKeyBindingHandler { // IsHovered is used public override bool HandlePositionalInput => true; @@ -185,6 +185,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Anchor = Anchor.Centre; Origin = Anchor.Centre; + + CornerRadius = OsuHitObject.OBJECT_RADIUS; + CornerExponent = 2; } public bool OnPressed(OsuAction action) From b60876455481d8dd756d938fe07865628b872128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 Mar 2020 22:09:02 +0100 Subject: [PATCH 191/192] Cover area just outside circle in test --- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs index 06eccdafdb..67b6dac787 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs @@ -71,23 +71,23 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("hit registered", () => hitAreaReceptor.HitAction == OsuAction.LeftButton); } - [Test] - public void TestCircleHitTopLeftEdge() + [TestCase(0.95f, OsuAction.LeftButton)] + [TestCase(1.05f, null)] + public void TestHitsCloseToEdge(float relativeDistanceFromCentre, OsuAction? expectedAction) { AddStep("move mouse to top left circle edge", () => { var drawQuad = hitAreaReceptor.ScreenSpaceDrawQuad; // sqrt(2) / 2 = sin(45deg) = cos(45deg) // draw width halved to get radius - // 0.95f taken for leniency - float correction = 0.95f * (float)Math.Sqrt(2) / 2 * (drawQuad.Width / 2); + float correction = relativeDistanceFromCentre * (float)Math.Sqrt(2) / 2 * (drawQuad.Width / 2); var mousePosition = new Vector2(drawQuad.Centre.X - correction, drawQuad.Centre.Y - correction); InputManager.MoveMouseTo(mousePosition); }); scheduleHit(); - AddAssert("hit registered", () => hitAreaReceptor.HitAction == OsuAction.LeftButton); + AddAssert($"hit {(expectedAction == null ? "not " : string.Empty)}registered", () => hitAreaReceptor.HitAction == expectedAction); } [Test] From 3a3a2ad2a71126973caac66e7b98ee7acf938caa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 8 Mar 2020 14:47:29 +0900 Subject: [PATCH 192/192] Fix video looping not propagating when set too early in initialisation --- osu.Game.Tournament/Components/TourneyVideo.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 7d2eaff515..786b7b3c67 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -38,7 +38,8 @@ namespace osu.Game.Tournament.Components { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, - Clock = new FramedClock(manualClock = new ManualClock()) + Clock = new FramedClock(manualClock = new ManualClock()), + Loop = loop, }; } else if (drawFallbackGradient) @@ -51,10 +52,13 @@ namespace osu.Game.Tournament.Components } } + private bool loop; + public bool Loop { set { + loop = value; if (video != null) video.Loop = value; }