From c5995acfff37684fabc9b53e3c005eb5f5184faa Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 30 Jan 2020 12:30:25 +0800 Subject: [PATCH 001/457] linkify metadata --- osu.Game/Online/Chat/MessageFormatter.cs | 3 +- osu.Game/OsuGame.cs | 10 ++++++ osu.Game/Overlays/BeatmapSet/Info.cs | 43 +++++++++++++++++------ osu.Game/Overlays/DirectOverlay.cs | 6 ++++ osu.Game/Screens/Select/BeatmapDetails.cs | 42 +++++++++++++++++----- 5 files changed, 85 insertions(+), 19 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 717de18c14..cfd0dcca50 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -279,7 +279,8 @@ namespace osu.Game.Online.Chat JoinMultiplayerMatch, Spectate, OpenUserProfile, - Custom + Custom, + OpenDirectWithSearch } public class Link : IComparable diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ff46c6d849..78e033c972 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -244,6 +244,10 @@ namespace osu.Game ShowChannel(link.Argument); break; + case LinkAction.OpenDirectWithSearch: + ShowDirectWithSearch(link.Argument); + break; + case LinkAction.OpenEditorTimestamp: case LinkAction.JoinMultiplayerMatch: case LinkAction.Spectate: @@ -310,6 +314,12 @@ namespace osu.Game /// The beatmap to show. public void ShowBeatmap(int beatmapId) => waitForReady(() => beatmapSetOverlay, _ => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId)); + /// + /// Show Direct with a query. + /// + /// The query to search for.null + public void ShowDirectWithSearch(string query) => waitForReady(() => direct, _ => direct.ShowWithSearch(query)); + /// /// Present a beatmap at song select immediately. /// The user should have already requested this interactively. diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 16d6236051..b621cf1cce 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -8,10 +8,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; +using osu.Game.Screens.Select; using osuTK; using osuTK.Graphics; @@ -68,7 +71,7 @@ namespace osu.Game.Overlays.BeatmapSet Child = new Container { RelativeSizeAxes = Axes.Both, - Child = new MetadataSection("Description"), + Child = new MetadataSection(MetadataType.Description), }, }, new Container @@ -86,10 +89,10 @@ namespace osu.Game.Overlays.BeatmapSet Direction = FillDirection.Full, Children = new[] { - source = new MetadataSection("Source"), - genre = new MetadataSection("Genre") { Width = 0.5f }, - language = new MetadataSection("Language") { Width = 0.5f }, - tags = new MetadataSection("Tags"), + source = new MetadataSection(MetadataType.Source), + genre = new MetadataSection(MetadataType.Genre) { Width = 0.5f }, + language = new MetadataSection(MetadataType.Language) { Width = 0.5f }, + tags = new MetadataSection(MetadataType.Tags), }, }, }, @@ -133,8 +136,9 @@ namespace osu.Game.Overlays.BeatmapSet private class MetadataSection : FillFlowContainer { + private readonly MetadataType type; private readonly OsuSpriteText header; - private readonly TextFlowContainer textFlow; + private readonly LinkFlowContainer textFlow; public string Text { @@ -147,13 +151,32 @@ namespace osu.Game.Overlays.BeatmapSet } this.FadeIn(transition_duration); + textFlow.Clear(); - textFlow.AddText(value, s => s.Font = s.Font.With(size: 14)); + static void format(SpriteText t) => t.Font = t.Font.With(size: 14); + + switch(type) + { + case MetadataType.Tags: + foreach (string tag in value.Split(" ")) + textFlow.AddLink(tag + " ", LinkAction.OpenDirectWithSearch, tag, null, format); + break; + + case MetadataType.Source: + textFlow.AddLink(value, LinkAction.OpenDirectWithSearch, value, null, format); + break; + + default: + textFlow.AddText(value, format); + break; + } } } - public MetadataSection(string title) + public MetadataSection(MetadataType type) { + this.type = type; + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Spacing = new Vector2(5f); @@ -162,12 +185,12 @@ namespace osu.Game.Overlays.BeatmapSet { header = new OsuSpriteText { - Text = title, + Text = this.type.ToString(), Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), Shadow = false, Margin = new MarginPadding { Top = 20 }, }, - textFlow = new OsuTextFlowContainer + textFlow = new LinkFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index e4cef319fe..c8e72cc6c9 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -154,6 +154,12 @@ namespace osu.Game.Overlays updateResultCounts(); } + public void ShowWithSearch(string query) + { + currentQuery.Value = query; + Show(); + } + [BackgroundDependencyLoader] private void load(OsuColour colours, RulesetStore rulesets, PreviewTrackManager previewTrackManager) { diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 577d999388..07a18ac5a3 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -19,6 +19,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; +using osu.Game.Overlays; +using osu.Game.Online.Chat; namespace osu.Game.Screens.Select { @@ -127,9 +129,9 @@ namespace osu.Game.Screens.Select Margin = new MarginPadding { Top = spacing * 2 }, Children = new[] { - description = new MetadataSection("Description"), - source = new MetadataSection("Source"), - tags = new MetadataSection("Tags"), + description = new MetadataSection(MetadataType.Description), + source = new MetadataSection(MetadataType.Source), + tags = new MetadataSection(MetadataType.Tags), }, }, }, @@ -299,10 +301,11 @@ namespace osu.Game.Screens.Select private class MetadataSection : Container { private readonly FillFlowContainer textContainer; + private readonly MetadataType type; private TextFlowContainer textFlow; - - public MetadataSection(string title) + public MetadataSection(MetadataType type) { + this.type = type; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -320,7 +323,7 @@ namespace osu.Game.Screens.Select AutoSizeAxes = Axes.Y, Child = new OsuSpriteText { - Text = title, + Text = this.type.ToString(), Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), }, }, @@ -346,15 +349,29 @@ namespace osu.Game.Screens.Select private void setTextAsync(string text) { - LoadComponentAsync(new OsuTextFlowContainer(s => s.Font = s.Font.With(size: 14)) + LoadComponentAsync(new LinkFlowContainer(s => s.Font = s.Font.With(size: 14)) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Colour = Color4.White.Opacity(0.75f), - Text = text }, loaded => { textFlow?.Expire(); + + switch(type) + { + case MetadataType.Tags: + foreach (string tag in text.Split(" ")) + loaded.AddLink(tag + " ", LinkAction.OpenDirectWithSearch, tag); + break; + case MetadataType.Source: + loaded.AddLink(text, LinkAction.OpenDirectWithSearch, text); + break; + default: + loaded.AddText(text); + break; + } + textContainer.Add(textFlow = loaded); // fade in if we haven't yet. @@ -363,4 +380,13 @@ namespace osu.Game.Screens.Select } } } + + public enum MetadataType + { + Tags, + Source, + Description, + Genre, + Language + } } From 2274d70dacbea11dbf6012eefa69cbdbeb88f860 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 31 Jan 2020 06:41:50 +0800 Subject: [PATCH 002/457] apply suggestions --- osu.Game/Online/Chat/MessageFormatter.cs | 2 +- osu.Game/OsuGame.cs | 8 ++++---- osu.Game/Overlays/BeatmapSet/Info.cs | 12 +++++++++--- osu.Game/Screens/Select/BeatmapDetails.cs | 12 +++++++++--- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index cfd0dcca50..28cb57227d 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -279,8 +279,8 @@ namespace osu.Game.Online.Chat JoinMultiplayerMatch, Spectate, OpenUserProfile, + SearchBeatmapSet, Custom, - OpenDirectWithSearch } public class Link : IComparable diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 78e033c972..1aa4017665 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -244,8 +244,8 @@ namespace osu.Game ShowChannel(link.Argument); break; - case LinkAction.OpenDirectWithSearch: - ShowDirectWithSearch(link.Argument); + case LinkAction.SearchBeatmapSet: + SearchBeatmapSet(link.Argument); break; case LinkAction.OpenEditorTimestamp: @@ -317,8 +317,8 @@ namespace osu.Game /// /// Show Direct with a query. /// - /// The query to search for.null - public void ShowDirectWithSearch(string query) => waitForReady(() => direct, _ => direct.ShowWithSearch(query)); + /// The query to search for. + public void SearchBeatmapSet(string query) => waitForReady(() => direct, _ => direct.ShowWithSearch(query)); /// /// Present a beatmap at song select immediately. diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index b621cf1cce..c566cfa859 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -158,12 +158,18 @@ namespace osu.Game.Overlays.BeatmapSet switch(type) { case MetadataType.Tags: - foreach (string tag in value.Split(" ")) - textFlow.AddLink(tag + " ", LinkAction.OpenDirectWithSearch, tag, null, format); + string[] tags = value.Split(" "); + for (int i = 0; i <= tags.Length - 1; i++) + { + textFlow.AddLink(tags[i], LinkAction.SearchBeatmapSet, tags[i], null, format); + + if (i != tags.Length - 1) + textFlow.AddText(" ", format); + } break; case MetadataType.Source: - textFlow.AddLink(value, LinkAction.OpenDirectWithSearch, value, null, format); + textFlow.AddLink(value, LinkAction.SearchBeatmapSet, value, null, format); break; default: diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 07a18ac5a3..42e4c22ff1 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -361,11 +361,17 @@ namespace osu.Game.Screens.Select switch(type) { case MetadataType.Tags: - foreach (string tag in text.Split(" ")) - loaded.AddLink(tag + " ", LinkAction.OpenDirectWithSearch, tag); + string[] tags = text.Split(" "); + for (int i = 0; i <= tags.Length - 1; i++) + { + loaded.AddLink(tags[i], LinkAction.SearchBeatmapSet, tags[i]); + + if (i != tags.Length - 1) + loaded.AddText(" "); + } break; case MetadataType.Source: - loaded.AddLink(text, LinkAction.OpenDirectWithSearch, text); + loaded.AddLink(text, LinkAction.SearchBeatmapSet, text); break; default: loaded.AddText(text); From b916366536b3bd0b4f13a377237aaa6788d1d0c2 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 31 Jan 2020 08:17:39 +0800 Subject: [PATCH 003/457] fix formatting --- osu.Game/Overlays/BeatmapSet/Info.cs | 2 +- osu.Game/Screens/Select/BeatmapDetails.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index c566cfa859..8be9462ffc 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -155,7 +155,7 @@ namespace osu.Game.Overlays.BeatmapSet textFlow.Clear(); static void format(SpriteText t) => t.Font = t.Font.With(size: 14); - switch(type) + switch (type) { case MetadataType.Tags: string[] tags = value.Split(" "); diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 42e4c22ff1..cd5d2e8e45 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -358,7 +358,7 @@ namespace osu.Game.Screens.Select { textFlow?.Expire(); - switch(type) + switch (type) { case MetadataType.Tags: string[] tags = text.Split(" "); From 2952fc8cc4ec21b146c1d2f6187608284bd28cae Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 31 Jan 2020 09:05:13 +0800 Subject: [PATCH 004/457] remove unused using --- osu.Game/Screens/Select/BeatmapDetails.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index cd5d2e8e45..59f97d696e 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -19,7 +19,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; -using osu.Game.Overlays; using osu.Game.Online.Chat; namespace osu.Game.Screens.Select From 4ecf2c0512ff830b69d4d62483ab664e4a42eb24 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 18 Mar 2020 14:19:15 +0800 Subject: [PATCH 005/457] remove unused property --- osu.Game/Overlays/BeatmapSet/Info.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index c789b06dd6..c69684f314 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -142,7 +142,6 @@ namespace osu.Game.Overlays.BeatmapSet private class MetadataSection : FillFlowContainer { private readonly MetadataType type; - private readonly OsuSpriteText header; private readonly LinkFlowContainer textFlow; public string Text From 2484ccd50c325ae9545f84e4ef3ecb9035a1d5d4 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Tue, 8 Jun 2021 11:49:25 -0400 Subject: [PATCH 006/457] Ensure scale x or y does not go below zero in SkinSelectionHandler.HandleScale --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 9cca0ba2c7..2d790eed5b 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -95,8 +95,8 @@ namespace osu.Game.Skinning.Editor // scale adjust applied to each individual item should match that of the quad itself. var scaledDelta = new Vector2( - adjustedRect.Width / selectionRect.Width, - adjustedRect.Height / selectionRect.Height + MathF.Max(adjustedRect.Width / selectionRect.Width, 0), + MathF.Max(adjustedRect.Height / selectionRect.Height, 0) ); foreach (var b in SelectedBlueprints) From cf40282f1f30fb2d7d7a9c588157a0429b967e43 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 9 Jun 2021 13:34:42 +0300 Subject: [PATCH 007/457] Convert `LegacySkinTransformer`s to accept raw `ISkin`s rather than a full `ISkinSource` --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- .../Legacy/CatchLegacySkinTransformer.cs | 14 +++--- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../Legacy/ManiaLegacySkinTransformer.cs | 32 ++++++------- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../Legacy/OsuLegacySkinTransformer.cs | 47 +++++++------------ .../Legacy/TaikoLegacySkinTransformer.cs | 20 +++----- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 6 +-- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Skinning/LegacySkinTransformer.cs | 25 ++++------ 11 files changed, 61 insertions(+), 93 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 23ce444560..ec4c5dfe4c 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Catch public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap); - public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new CatchLegacySkinTransformer(source); + public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new CatchLegacySkinTransformer(skin); public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new CatchPerformanceCalculator(this, attributes, score); diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 8c9e602cd4..9be47b3550 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -17,8 +17,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy /// private bool providesComboCounter => this.HasFont(LegacyFont.Combo); - public CatchLegacySkinTransformer(ISkinSource source) - : base(source) + public CatchLegacySkinTransformer(ISkin skin) + : base(skin) { } @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy switch (targetComponent.Target) { case SkinnableTarget.MainHUDComponents: - var components = Source.GetDrawableComponent(component) as SkinnableTargetComponentsContainer; + var components = Skin.GetDrawableComponent(component) as SkinnableTargetComponentsContainer; if (providesComboCounter && components != null) { @@ -79,13 +79,13 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy case CatchSkinComponents.CatchComboCounter: if (providesComboCounter) - return new LegacyCatchComboCounter(Source); + return new LegacyCatchComboCounter(Skin); return null; } } - return Source.GetDrawableComponent(component); + return Skin.GetDrawableComponent(component); } public override IBindable GetConfig(TLookup lookup) @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy switch (lookup) { case CatchSkinColour colour: - var result = (Bindable)Source.GetConfig(new SkinCustomColourLookup(colour)); + var result = (Bindable)Skin.GetConfig(new SkinCustomColourLookup(colour)); if (result == null) return null; @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy return (IBindable)result; } - return Source.GetConfig(lookup); + return Skin.GetConfig(lookup); } } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index fbb9b3c466..fe736766d9 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Mania public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this); - public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new ManiaLegacySkinTransformer(source, beatmap); + public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new ManiaLegacySkinTransformer(skin, beatmap); public override IEnumerable ConvertFromLegacyMods(LegacyMods mods) { diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 962a13ebea..7d4d303bc9 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -50,29 +50,25 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { HitResult.Miss, "mania-hit0" } }; - private Lazy isLegacySkin; + private readonly Lazy isLegacySkin; /// /// Whether texture for the keys exists. /// Used to determine if the mania ruleset is skinned. /// - private Lazy hasKeyTexture; + private readonly Lazy hasKeyTexture; - public ManiaLegacySkinTransformer(ISkinSource source, IBeatmap beatmap) - : base(source) + public ManiaLegacySkinTransformer(ISkin skin, IBeatmap beatmap) + : base(skin) { this.beatmap = (ManiaBeatmap)beatmap; - Source.SourceChanged += sourceChanged; - sourceChanged(); - } - - private void sourceChanged() - { - isLegacySkin = new Lazy(() => FindProvider(s => s.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null) != null); - hasKeyTexture = new Lazy(() => FindProvider(s => s.GetAnimation( - s.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value - ?? "mania-key1", true, true) != null) != null); + isLegacySkin = new Lazy(() => skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); + hasKeyTexture = new Lazy(() => + { + var keyImage = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value ?? "mania-key1"; + return skin.GetAnimation(keyImage, true, true) != null; + }); } public override Drawable GetDrawableComponent(ISkinComponent component) @@ -125,7 +121,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy break; } - return Source.GetDrawableComponent(component); + return Skin.GetDrawableComponent(component); } private Drawable getResult(HitResult result) @@ -146,15 +142,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered) return new SampleVirtual(); - return Source.GetSample(sampleInfo); + return Skin.GetSample(sampleInfo); } public override IBindable GetConfig(TLookup lookup) { if (lookup is ManiaSkinConfigurationLookup maniaLookup) - return Source.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn)); + return Skin.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn)); - return Source.GetConfig(lookup); + return Skin.GetConfig(lookup); } } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index b50d3ad2b4..69e22dc45d 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -218,7 +218,7 @@ namespace osu.Game.Rulesets.Osu public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this); - public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new OsuLegacySkinTransformer(source); + public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new OsuLegacySkinTransformer(skin); public int LegacyID => 0; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 3267b48ebf..3ad3b7d30b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public class OsuLegacySkinTransformer : LegacySkinTransformer { - private Lazy hasHitCircle; + private readonly Lazy hasHitCircle; /// /// On osu-stable, hitcircles have 5 pixels of transparent padding on each side to allow for shadows etc. @@ -20,16 +20,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy /// public const float LEGACY_CIRCLE_RADIUS = 64 - 5; - public OsuLegacySkinTransformer(ISkinSource source) - : base(source) + public OsuLegacySkinTransformer(ISkin skin) + : base(skin) { - Source.SourceChanged += sourceChanged; - sourceChanged(); - } - - private void sourceChanged() - { - hasHitCircle = new Lazy(() => FindProvider(s => s.GetTexture("hitcircle") != null) != null); + hasHitCircle = new Lazy(() => Skin.GetTexture("hitcircle") != null); } public override Drawable GetDrawableComponent(ISkinComponent component) @@ -49,16 +43,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return followCircle; case OsuSkinComponents.SliderBall: - // specular and nd layers must come from the same source as the ball texure. - var ballProvider = Source.FindProvider(s => s.GetTexture("sliderb") != null || s.GetTexture("sliderb0") != null); - - var sliderBallContent = ballProvider.GetAnimation("sliderb", true, true, animationSeparator: ""); + var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: ""); // todo: slider ball has a custom frame delay based on velocity // Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME); if (sliderBallContent != null) - return new LegacySliderBall(sliderBallContent, ballProvider); + return new LegacySliderBall(sliderBallContent, this); return null; @@ -87,18 +78,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; case OsuSkinComponents.Cursor: - var cursorProvider = Source.FindProvider(s => s.GetTexture("cursor") != null); - - if (cursorProvider != null) - return new LegacyCursor(cursorProvider); + if (GetTexture("cursor") != null) + return new LegacyCursor(this); return null; case OsuSkinComponents.CursorTrail: - var trailProvider = Source.FindProvider(s => s.GetTexture("cursortrail") != null); - - if (trailProvider != null) - return new LegacyCursorTrail(trailProvider); + if (GetTexture("cursortrail") != null) + return new LegacyCursorTrail(this); return null; @@ -113,9 +100,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy }; case OsuSkinComponents.SpinnerBody: - bool hasBackground = Source.GetTexture("spinner-background") != null; + bool hasBackground = GetTexture("spinner-background") != null; - if (Source.GetTexture("spinner-top") != null && !hasBackground) + if (GetTexture("spinner-top") != null && !hasBackground) return new LegacyNewStyleSpinner(); else if (hasBackground) return new LegacyOldStyleSpinner(); @@ -124,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } } - return Source.GetDrawableComponent(component); + return Skin.GetDrawableComponent(component); } public override IBindable GetConfig(TLookup lookup) @@ -132,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy switch (lookup) { case OsuSkinColour colour: - return Source.GetConfig(new SkinCustomColourLookup(colour)); + return Skin.GetConfig(new SkinCustomColourLookup(colour)); case OsuSkinConfiguration osuLookup: switch (osuLookup) @@ -146,14 +133,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy case OsuSkinConfiguration.HitCircleOverlayAboveNumber: // See https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D // HitCircleOverlayAboveNumer (with typo) should still be supported for now. - return Source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? - Source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); + return Skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? + Skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); } break; } - return Source.GetConfig(lookup); + return Skin.GetConfig(lookup); } } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index 7ce0f6b93b..0122f9a1cd 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs @@ -15,18 +15,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { public class TaikoLegacySkinTransformer : LegacySkinTransformer { - private Lazy hasExplosion; + private readonly Lazy hasExplosion; - public TaikoLegacySkinTransformer(ISkinSource source) - : base(source) + public TaikoLegacySkinTransformer(ISkin skin) + : base(skin) { - Source.SourceChanged += sourceChanged; - sourceChanged(); - } - - private void sourceChanged() - { - hasExplosion = new Lazy(() => Source.GetTexture(getHitName(TaikoSkinComponents.TaikoExplosionGreat)) != null); + hasExplosion = new Lazy(() => Skin.GetTexture(getHitName(TaikoSkinComponents.TaikoExplosionGreat)) != null); } public override Drawable GetDrawableComponent(ISkinComponent component) @@ -132,7 +126,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy } } - return Source.GetDrawableComponent(component); + return Skin.GetDrawableComponent(component); } private string getHitName(TaikoSkinComponents component) @@ -155,12 +149,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public override ISample GetSample(ISampleInfo sampleInfo) { if (sampleInfo is HitSampleInfo hitSampleInfo) - return Source.GetSample(new LegacyTaikoSampleInfo(hitSampleInfo)); + return Skin.GetSample(new LegacyTaikoSampleInfo(hitSampleInfo)); return base.GetSample(sampleInfo); } - public override IBindable GetConfig(TLookup lookup) => Source.GetConfig(lookup); + public override IBindable GetConfig(TLookup lookup) => Skin.GetConfig(lookup); private class LegacyTaikoSampleInfo : HitSampleInfo { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 5854d4770c..ab5fcf6336 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap, this); - public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new TaikoLegacySkinTransformer(source); + public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new TaikoLegacySkinTransformer(skin); public const string SHORT_NAME = "taiko"; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index cc53e50884..13e84e335d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -116,12 +116,12 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestOsuRuleset : OsuRuleset { - public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new TestOsuLegacySkinTransformer(source); + public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new TestOsuLegacySkinTransformer(skin); private class TestOsuLegacySkinTransformer : OsuLegacySkinTransformer { - public TestOsuLegacySkinTransformer(ISkinSource source) - : base(source) + public TestOsuLegacySkinTransformer(ISkin skin) + : base(skin) { } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 7bdf84ace4..9f9f42eda4 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets [CanBeNull] public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().FirstOrDefault(); - public virtual ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => null; + public virtual ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => null; protected Ruleset() { diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 651fdddb1b..cd896ab51e 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.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.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -16,16 +15,16 @@ namespace osu.Game.Skinning /// /// Transformer used to handle support of legacy features for individual rulesets. /// - public abstract class LegacySkinTransformer : ISkinSource + public abstract class LegacySkinTransformer : ISkin { /// - /// Source of the which is being transformed. + /// The which is being transformed. /// - protected ISkinSource Source { get; } + protected ISkin Skin { get; } - protected LegacySkinTransformer(ISkinSource source) + protected LegacySkinTransformer(ISkin skin) { - Source = source; + Skin = skin; } public abstract Drawable GetDrawableComponent(ISkinComponent component); @@ -33,28 +32,20 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName) => GetTexture(componentName, default, default); public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) - => Source.GetTexture(componentName, wrapModeS, wrapModeT); + => Skin.GetTexture(componentName, wrapModeS, wrapModeT); public virtual ISample GetSample(ISampleInfo sampleInfo) { if (!(sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample)) - return Source.GetSample(sampleInfo); + return Skin.GetSample(sampleInfo); var playLayeredHitSounds = GetConfig(LegacySetting.LayeredHitSounds); if (legacySample.IsLayered && playLayeredHitSounds?.Value == false) return new SampleVirtual(); - return Source.GetSample(sampleInfo); + return Skin.GetSample(sampleInfo); } public abstract IBindable GetConfig(TLookup lookup); - - public ISkin FindProvider(Func lookupFunction) => Source.FindProvider(lookupFunction); - - public event Action SourceChanged - { - add { throw new NotSupportedException(); } - remove { } - } } } From 6538d44708646a2b32b4aa0b112d7d31d82d38ff Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 9 Jun 2021 20:36:34 +0300 Subject: [PATCH 008/457] Make `SkinProvidingContainer` able to perform lookup on multiple skins Currently `protected` functionality for use in custom `SkinProvidingContainer`s, can be exposed to public constructors if it need to later on, but I'm not sure about doing that opposed to just nesting multiple `SkinProvidingContainer`. --- .../Gameplay/TestSceneSkinnableDrawable.cs | 2 +- .../Skinning/BeatmapSkinProvidingContainer.cs | 6 +- osu.Game/Skinning/SkinProvidingContainer.cs | 94 ++++++++++++------- 3 files changed, 64 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 96418f6d28..77966e925a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void Disable() { allow = false; - TriggerSourceChanged(); + OnSourceChanged(); } public SwitchableSkinProvidingContainer(ISkin skin) diff --git a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs index 57c08a903f..f12f44e347 100644 --- a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs +++ b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs @@ -83,9 +83,9 @@ namespace osu.Game.Skinning [BackgroundDependencyLoader] private void load() { - beatmapSkins.BindValueChanged(_ => TriggerSourceChanged()); - beatmapColours.BindValueChanged(_ => TriggerSourceChanged()); - beatmapHitsounds.BindValueChanged(_ => TriggerSourceChanged()); + beatmapSkins.BindValueChanged(_ => OnSourceChanged()); + beatmapColours.BindValueChanged(_ => OnSourceChanged()); + beatmapHitsounds.BindValueChanged(_ => OnSourceChanged()); } } } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 0e16cf43ee..cc9d8d0e8d 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; @@ -21,8 +22,10 @@ namespace osu.Game.Skinning { public event Action SourceChanged; - [CanBeNull] - private readonly ISkin skin; + /// + /// The list of skins provided by this . + /// + protected readonly List SkinLayers = new List(); [CanBeNull] private ISkinSource fallbackSource; @@ -38,23 +41,30 @@ namespace osu.Game.Skinning protected virtual bool AllowColourLookup => true; public SkinProvidingContainer(ISkin skin) + : this() { - this.skin = skin; + SkinLayers.Add(skin); + } + protected SkinProvidingContainer() + { RelativeSizeAxes = Axes.Both; } public ISkin FindProvider(Func lookupFunction) { - if (skin is ISkinSource source) + foreach (var skin in SkinLayers) { - if (source.FindProvider(lookupFunction) is ISkin found) - return found; - } - else if (skin != null) - { - if (lookupFunction(skin)) - return skin; + if (skin is ISkinSource source) + { + if (source.FindProvider(lookupFunction) is ISkin found) + return found; + } + else if (skin != null) + { + if (lookupFunction(skin)) + return skin; + } } return fallbackSource?.FindProvider(lookupFunction); @@ -62,57 +72,73 @@ namespace osu.Game.Skinning public Drawable GetDrawableComponent(ISkinComponent component) { - Drawable sourceDrawable; - if (AllowDrawableLookup(component) && (sourceDrawable = skin?.GetDrawableComponent(component)) != null) - return sourceDrawable; + if (AllowDrawableLookup(component)) + { + foreach (var skin in SkinLayers) + { + Drawable sourceDrawable; + if ((sourceDrawable = skin?.GetDrawableComponent(component)) != null) + return sourceDrawable; + } + } return fallbackSource?.GetDrawableComponent(component); } public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) { - Texture sourceTexture; - if (AllowTextureLookup(componentName) && (sourceTexture = skin?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) - return sourceTexture; + if (AllowTextureLookup(componentName)) + { + foreach (var skin in SkinLayers) + { + Texture sourceTexture; + if ((sourceTexture = skin?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) + return sourceTexture; + } + } return fallbackSource?.GetTexture(componentName, wrapModeS, wrapModeT); } public ISample GetSample(ISampleInfo sampleInfo) { - ISample sourceChannel; - if (AllowSampleLookup(sampleInfo) && (sourceChannel = skin?.GetSample(sampleInfo)) != null) - return sourceChannel; + if (AllowSampleLookup(sampleInfo)) + { + foreach (var skin in SkinLayers) + { + ISample sourceSample; + if ((sourceSample = skin?.GetSample(sampleInfo)) != null) + return sourceSample; + } + } return fallbackSource?.GetSample(sampleInfo); } public IBindable GetConfig(TLookup lookup) { - if (skin != null) - { - if (lookup is GlobalSkinColours || lookup is SkinCustomColourLookup) - return lookupWithFallback(lookup, AllowColourLookup); + if (lookup is GlobalSkinColours || lookup is SkinCustomColourLookup) + return lookupWithFallback(lookup, AllowColourLookup); - return lookupWithFallback(lookup, AllowConfigurationLookup); - } - - return fallbackSource?.GetConfig(lookup); + return lookupWithFallback(lookup, AllowConfigurationLookup); } private IBindable lookupWithFallback(TLookup lookup, bool canUseSkinLookup) { if (canUseSkinLookup) { - var bindable = skin?.GetConfig(lookup); - if (bindable != null) - return bindable; + foreach (var skin in SkinLayers) + { + IBindable bindable; + if ((bindable = skin?.GetConfig(lookup)) != null) + return bindable; + } } return fallbackSource?.GetConfig(lookup); } - protected virtual void TriggerSourceChanged() => SourceChanged?.Invoke(); + protected virtual void OnSourceChanged() => SourceChanged?.Invoke(); protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { @@ -120,7 +146,7 @@ namespace osu.Game.Skinning fallbackSource = dependencies.Get(); if (fallbackSource != null) - fallbackSource.SourceChanged += TriggerSourceChanged; + fallbackSource.SourceChanged += OnSourceChanged; dependencies.CacheAs(this); @@ -135,7 +161,7 @@ namespace osu.Game.Skinning base.Dispose(isDisposing); if (fallbackSource != null) - fallbackSource.SourceChanged -= TriggerSourceChanged; + fallbackSource.SourceChanged -= OnSourceChanged; } } } From 9e652715ced79d2a6f22180c03d3e6024a10da8d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 9 Jun 2021 20:40:47 +0300 Subject: [PATCH 009/457] Expose the skin lookup layers of `SkinManager` to a property --- osu.Game/Skinning/SkinManager.cs | 44 +++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 48d6b9254f..134156e44f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -42,6 +42,24 @@ namespace osu.Game.Skinning public readonly Bindable CurrentSkin = new Bindable(); public readonly Bindable CurrentSkinInfo = new Bindable(SkinInfo.Default) { Default = SkinInfo.Default }; + /// + /// The skin layers of the currently selected user skin for performing lookups on, + /// in order of preference (user skin first, then fallback skins). + /// + public IEnumerable CurrentSkinLayers + { + get + { + yield return CurrentSkin.Value; + + // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. + // When attempting to address this, we may want to move the full DefaultLegacySkin fallback logic to within Player itself (to better allow + // for beatmap skin visibility). + if (CurrentSkin.Value is LegacySkin) + yield return defaultLegacySkin; + } + } + public override IEnumerable HandledExtensions => new[] { ".osk" }; protected override string[] HashableFileTypes => new[] { ".ini" }; @@ -220,11 +238,11 @@ namespace osu.Game.Skinning public ISkin FindProvider(Func lookupFunction) { - if (lookupFunction(CurrentSkin.Value)) - return CurrentSkin.Value; - - if (CurrentSkin.Value is LegacySkin && lookupFunction(defaultLegacySkin)) - return defaultLegacySkin; + foreach (var skin in CurrentSkinLayers) + { + if (lookupFunction(skin)) + return skin; + } return null; } @@ -232,16 +250,12 @@ namespace osu.Game.Skinning private T lookupWithFallback(Func func) where T : class { - var selectedSkin = func(CurrentSkin.Value); - - if (selectedSkin != null) - return selectedSkin; - - // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. - // When attempting to address this, we may want to move the full DefaultLegacySkin fallback logic to within Player itself (to better allow - // for beatmap skin visibility). - if (CurrentSkin.Value is LegacySkin) - return func(defaultLegacySkin); + foreach (var skin in CurrentSkinLayers) + { + var result = func(skin); + if (result != null) + return result; + } return null; } From 33a9cac398f446bb01b5e9e9bd9463f81edd62ae Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 9 Jun 2021 20:41:16 +0300 Subject: [PATCH 010/457] Add special `RulesetSkinProvidingContainer` managing ruleset-compatible skin setup --- .../Skinning/RulesetSkinProvidingContainer.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 osu.Game/Skinning/RulesetSkinProvidingContainer.cs diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs new file mode 100644 index 0000000000..dcf6281e38 --- /dev/null +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -0,0 +1,59 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; + +namespace osu.Game.Skinning +{ + /// + /// A type of that provides access to the beatmap skin and user skin, + /// each transformed with the ruleset's own skin transformer individually. + /// + public class RulesetSkinProvidingContainer : SkinProvidingContainer + { + private readonly Ruleset ruleset; + private readonly IBeatmap beatmap; + + protected override Container Content { get; } + + public RulesetSkinProvidingContainer(Ruleset ruleset, IBeatmap beatmap, ISkin beatmapSkin) + { + this.ruleset = ruleset; + this.beatmap = beatmap; + + InternalChild = new BeatmapSkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkin, beatmap)) + { + Child = Content = new Container + { + RelativeSizeAxes = Axes.Both, + } + }; + } + + [Resolved] + private SkinManager skinManager { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + updateSkins(); + } + + protected override void OnSourceChanged() + { + updateSkins(); + base.OnSourceChanged(); + } + + private void updateSkins() + { + SkinLayers.Clear(); + SkinLayers.AddRange(skinManager.CurrentSkinLayers.Select(s => ruleset.CreateLegacySkinProvider(s, beatmap))); + } + } +} From e30f6581b384c35aa7dbe5e9d04ce254bb084e2d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 9 Jun 2021 21:30:26 +0300 Subject: [PATCH 011/457] Wrap gameplay content within a `RulesetSkinProvidingContainer` --- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 10 +--------- osu.Game/Screens/Play/Player.cs | 14 ++++---------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 61056aeced..b56f9bee14 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -73,15 +73,7 @@ namespace osu.Game.Screens.Edit.Compose { Debug.Assert(ruleset != null); - var beatmapSkinProvider = new BeatmapSkinProvidingContainer(beatmap.Value.Skin); - - // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation - // full access to all skin sources. - var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, EditorBeatmap.PlayableBeatmap)); - - // load the skinning hierarchy first. - // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. - return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(content)); + return new RulesetSkinProvidingContainer(ruleset, EditorBeatmap.PlayableBeatmap, beatmap.Value.Skin).WithChild(content); } #region Input Handling diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index f9036780aa..47c91cfc4d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -234,29 +234,23 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(GameplayBeatmap); - var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); - - // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation - // full access to all skin sources. - var rulesetSkinProvider = new SkinProvidingContainer(GameplayRuleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap)); + var rulesetSkinProvider = new RulesetSkinProvidingContainer(GameplayRuleset, playableBeatmap, Beatmap.Value.Skin); // load the skinning hierarchy first. // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. - GameplayClockContainer.Add(beatmapSkinProvider.WithChild(rulesetSkinProvider)); + GameplayClockContainer.Add(rulesetSkinProvider); rulesetSkinProvider.AddRange(new[] { - // underlay and gameplay should have access the to skinning sources. + // underlay and gameplay should have access to the skinning sources. createUnderlayComponents(), createGameplayComponents(Beatmap.Value, playableBeatmap) }); // also give the HUD a ruleset container to allow rulesets to potentially override HUD elements (used to disable combo counters etc.) // we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there. - var hudRulesetContainer = new SkinProvidingContainer(GameplayRuleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap)); - // add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components. - GameplayClockContainer.Add(hudRulesetContainer.WithChild(createOverlayComponents(Beatmap.Value))); + rulesetSkinProvider.Add(createOverlayComponents(Beatmap.Value)); if (!DrawableRuleset.AllowGameplayOverlays) { From 1aaad7bfd442d74cfc28903fb4f4b35eb178edb7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 9 Jun 2021 22:24:53 +0300 Subject: [PATCH 012/457] Apply few adjustments to skinning overlays comment --- osu.Game/Screens/Play/Player.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 47c91cfc4d..bec5181efe 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -247,9 +247,9 @@ namespace osu.Game.Screens.Play createGameplayComponents(Beatmap.Value, playableBeatmap) }); - // also give the HUD a ruleset container to allow rulesets to potentially override HUD elements (used to disable combo counters etc.) - // we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there. // add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components. + // also give the overlays the ruleset skin provider to allow rulesets to potentially override HUD elements (used to disable combo counters etc.) + // we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there. rulesetSkinProvider.Add(createOverlayComponents(Beatmap.Value)); if (!DrawableRuleset.AllowGameplayOverlays) From 18edbdd135dc31e989fe8f801b612a267ee6e415 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 11:55:22 +0300 Subject: [PATCH 013/457] Remove mentioning of "layer" in skin providers `SkinSources` sounds better. --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 4 ++-- osu.Game/Skinning/SkinProvidingContainer.cs | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index dcf6281e38..1fe86d2873 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -52,8 +52,8 @@ namespace osu.Game.Skinning private void updateSkins() { - SkinLayers.Clear(); - SkinLayers.AddRange(skinManager.CurrentSkinLayers.Select(s => ruleset.CreateLegacySkinProvider(s, beatmap))); + SkinSources.Clear(); + SkinSources.AddRange(skinManager.CurrentSkinLayers.Select(s => ruleset.CreateLegacySkinProvider(s, beatmap))); } } } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index cc9d8d0e8d..6686583a6f 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Skinning /// /// The list of skins provided by this . /// - protected readonly List SkinLayers = new List(); + protected readonly List SkinSources = new List(); [CanBeNull] private ISkinSource fallbackSource; @@ -43,7 +43,7 @@ namespace osu.Game.Skinning public SkinProvidingContainer(ISkin skin) : this() { - SkinLayers.Add(skin); + SkinSources.Add(skin); } protected SkinProvidingContainer() @@ -53,7 +53,7 @@ namespace osu.Game.Skinning public ISkin FindProvider(Func lookupFunction) { - foreach (var skin in SkinLayers) + foreach (var skin in SkinSources) { if (skin is ISkinSource source) { @@ -74,7 +74,7 @@ namespace osu.Game.Skinning { if (AllowDrawableLookup(component)) { - foreach (var skin in SkinLayers) + foreach (var skin in SkinSources) { Drawable sourceDrawable; if ((sourceDrawable = skin?.GetDrawableComponent(component)) != null) @@ -89,7 +89,7 @@ namespace osu.Game.Skinning { if (AllowTextureLookup(componentName)) { - foreach (var skin in SkinLayers) + foreach (var skin in SkinSources) { Texture sourceTexture; if ((sourceTexture = skin?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) @@ -104,7 +104,7 @@ namespace osu.Game.Skinning { if (AllowSampleLookup(sampleInfo)) { - foreach (var skin in SkinLayers) + foreach (var skin in SkinSources) { ISample sourceSample; if ((sourceSample = skin?.GetSample(sampleInfo)) != null) @@ -127,7 +127,7 @@ namespace osu.Game.Skinning { if (canUseSkinLookup) { - foreach (var skin in SkinLayers) + foreach (var skin in SkinSources) { IBindable bindable; if ((bindable = skin?.GetConfig(lookup)) != null) From 530026b6755c6e5891969dd1d0c594da67c3ffd2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 11:56:13 +0300 Subject: [PATCH 014/457] Add simple xmldoc to ctors explaining their deal with `SkinSources` --- osu.Game/Skinning/SkinProvidingContainer.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 6686583a6f..a7bc3ba379 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -40,12 +40,19 @@ namespace osu.Game.Skinning protected virtual bool AllowColourLookup => true; + /// + /// Constructs a new with a single skin added to the protected list. + /// public SkinProvidingContainer(ISkin skin) : this() { SkinSources.Add(skin); } + /// + /// Constructs a new with no sources. + /// Up to the implementation for adding to the list. + /// protected SkinProvidingContainer() { RelativeSizeAxes = Axes.Both; From 58cca9da06ecd1081b33f03ea57ea9cea8e88c0c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 11:57:28 +0300 Subject: [PATCH 015/457] Revert "Expose the skin lookup layers of `SkinManager` to a property" This reverts commit 9e652715ced79d2a6f22180c03d3e6024a10da8d. --- osu.Game/Skinning/SkinManager.cs | 44 +++++++++++--------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 134156e44f..48d6b9254f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -42,24 +42,6 @@ namespace osu.Game.Skinning public readonly Bindable CurrentSkin = new Bindable(); public readonly Bindable CurrentSkinInfo = new Bindable(SkinInfo.Default) { Default = SkinInfo.Default }; - /// - /// The skin layers of the currently selected user skin for performing lookups on, - /// in order of preference (user skin first, then fallback skins). - /// - public IEnumerable CurrentSkinLayers - { - get - { - yield return CurrentSkin.Value; - - // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. - // When attempting to address this, we may want to move the full DefaultLegacySkin fallback logic to within Player itself (to better allow - // for beatmap skin visibility). - if (CurrentSkin.Value is LegacySkin) - yield return defaultLegacySkin; - } - } - public override IEnumerable HandledExtensions => new[] { ".osk" }; protected override string[] HashableFileTypes => new[] { ".ini" }; @@ -238,11 +220,11 @@ namespace osu.Game.Skinning public ISkin FindProvider(Func lookupFunction) { - foreach (var skin in CurrentSkinLayers) - { - if (lookupFunction(skin)) - return skin; - } + if (lookupFunction(CurrentSkin.Value)) + return CurrentSkin.Value; + + if (CurrentSkin.Value is LegacySkin && lookupFunction(defaultLegacySkin)) + return defaultLegacySkin; return null; } @@ -250,12 +232,16 @@ namespace osu.Game.Skinning private T lookupWithFallback(Func func) where T : class { - foreach (var skin in CurrentSkinLayers) - { - var result = func(skin); - if (result != null) - return result; - } + var selectedSkin = func(CurrentSkin.Value); + + if (selectedSkin != null) + return selectedSkin; + + // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. + // When attempting to address this, we may want to move the full DefaultLegacySkin fallback logic to within Player itself (to better allow + // for beatmap skin visibility). + if (CurrentSkin.Value is LegacySkin) + return func(defaultLegacySkin); return null; } From 59be3588eb281d916ba68297f19e6eb4a5c362ed Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 12:17:51 +0300 Subject: [PATCH 016/457] Change `SkinSources` to a bindable list for binding `SourceChanged` events --- osu.Game/Skinning/SkinProvidingContainer.cs | 44 +++++++++++---------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 3739172367..fa0780ff29 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -2,7 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; @@ -25,7 +26,7 @@ namespace osu.Game.Skinning /// /// The list of skins provided by this . /// - protected readonly List SkinSources = new List(); + protected readonly BindableList SkinSources = new BindableList(); [CanBeNull] private ISkinSource fallbackSource; @@ -61,8 +62,23 @@ namespace osu.Game.Skinning noFallbackLookupProxy = new NoFallbackProxy(this); - if (skin is ISkinSource source) - source.SourceChanged += TriggerSourceChanged; + SkinSources.BindCollectionChanged(((_, args) => + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Remove: + foreach (var source in args.OldItems.Cast().OfType()) + source.SourceChanged -= OnSourceChanged; + + break; + + case NotifyCollectionChangedAction.Add: + foreach (var source in args.NewItems.Cast().OfType()) + source.SourceChanged += OnSourceChanged; + + break; + } + }), true); } public ISkin FindProvider(Func lookupFunction) @@ -146,21 +162,9 @@ namespace osu.Game.Skinning public IBindable GetConfig(TLookup lookup, bool fallback) { if (lookup is GlobalSkinColours || lookup is SkinCustomColourLookup) - return lookupWithFallback(lookup, AllowColourLookup); + return lookupWithFallback(lookup, AllowColourLookup, fallback); - return lookupWithFallback(lookup, AllowConfigurationLookup); - if (skin != null) - { - if (lookup is GlobalSkinColours || lookup is SkinCustomColourLookup) - return lookupWithFallback(lookup, AllowColourLookup, fallback); - - return lookupWithFallback(lookup, AllowConfigurationLookup, fallback); - } - - if (!fallback) - return null; - - return fallbackSource?.GetConfig(lookup); + return lookupWithFallback(lookup, AllowConfigurationLookup, fallback); } private IBindable lookupWithFallback(TLookup lookup, bool canUseSkinLookup, bool canUseFallback) @@ -205,10 +209,8 @@ namespace osu.Game.Skinning if (fallbackSource != null) fallbackSource.SourceChanged -= OnSourceChanged; - fallbackSource.SourceChanged -= TriggerSourceChanged; - if (skin is ISkinSource source) - source.SourceChanged -= TriggerSourceChanged; + SkinSources.Clear(); } private class NoFallbackProxy : ISkinSource From c3a2f2c2a4559b3740368065b78564c1306dd3cf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 13:04:34 +0300 Subject: [PATCH 017/457] Expose default `SkinManager` providers for use in `RulesetSkinProvidingContainer` --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 9 +++++++-- osu.Game/Skinning/SkinManager.cs | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 1fe86d2873..eadf0e05b9 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.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.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -53,7 +52,13 @@ namespace osu.Game.Skinning private void updateSkins() { SkinSources.Clear(); - SkinSources.AddRange(skinManager.CurrentSkinLayers.Select(s => ruleset.CreateLegacySkinProvider(s, beatmap))); + + SkinSources.Add(ruleset.CreateLegacySkinProvider(skinManager.CurrentSkin.Value, beatmap)); + + if (skinManager.CurrentSkin.Value is LegacySkin) + SkinSources.Add(ruleset.CreateLegacySkinProvider(skinManager.DefaultLegacySkin, beatmap)); + + SkinSources.Add(ruleset.CreateLegacySkinProvider(skinManager.DefaultSkin, beatmap)); } } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 9e274227a2..89f166dc2a 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -48,9 +48,19 @@ namespace osu.Game.Skinning protected override string ImportFromStablePath => "Skins"; + private readonly Skin defaultSkin; + + /// + /// An providing the resources of the default skin. + /// + public ISkin DefaultSkin => defaultSkin; + private readonly Skin defaultLegacySkin; - private readonly Skin defaultSkin; + /// + /// An providing the resources of the default legacy skin. + /// + public ISkin DefaultLegacySkin => defaultLegacySkin; public SkinManager(Storage storage, DatabaseContextFactory contextFactory, GameHost host, IResourceStore resources, AudioManager audio) : base(storage, contextFactory, new SkinStore(contextFactory, storage), host) @@ -84,7 +94,7 @@ namespace osu.Game.Skinning { var userSkins = GetAllUserSkins(); userSkins.Insert(0, SkinInfo.Default); - userSkins.Insert(1, DefaultLegacySkin.Info); + userSkins.Insert(1, Skinning.DefaultLegacySkin.Info); return userSkins; } From 26cdcc8d78f4bb29d2730c7ad291db0651014177 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 13:05:44 +0300 Subject: [PATCH 018/457] Remove stale access to `Source` from master merge --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index a196ebedd7..a5a1d1504f 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy return null; case CatchSkinComponents.Catcher: - var version = Source.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value ?? 1; + var version = GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value ?? 1; if (version < 2.3m) { From 5c9c424a0d032ec412d7be58953176d86886b233 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 13:15:18 +0300 Subject: [PATCH 019/457] Switch state case placements for consistency Tickled me. --- osu.Game/Skinning/SkinProvidingContainer.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index fa0780ff29..74a465a91f 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -66,17 +66,17 @@ namespace osu.Game.Skinning { switch (args.Action) { - case NotifyCollectionChangedAction.Remove: - foreach (var source in args.OldItems.Cast().OfType()) - source.SourceChanged -= OnSourceChanged; - - break; - case NotifyCollectionChangedAction.Add: foreach (var source in args.NewItems.Cast().OfType()) source.SourceChanged += OnSourceChanged; break; + + case NotifyCollectionChangedAction.Remove: + foreach (var source in args.OldItems.Cast().OfType()) + source.SourceChanged -= OnSourceChanged; + + break; } }), true); } From 9eecb23fefa623356228bc916dba930e784dc035 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Jun 2021 21:35:52 +0900 Subject: [PATCH 020/457] Only seek to the end on first frame arrival --- osu.Game/Screens/Play/SpectatorPlayer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 0286b6b8a6..304bc4d7ed 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -48,7 +48,6 @@ namespace osu.Game.Screens.Play base.StartGameplay(); spectatorClient.OnNewFrames += userSentFrames; - seekToGameplay(); } private void userSentFrames(int userId, FrameDataBundle bundle) From 5073ede115f1c3da87e4c8055b8b39bbde31bdd8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Jun 2021 21:38:20 +0900 Subject: [PATCH 021/457] Clear existing frames when starting gameplay --- osu.Game/Screens/Play/SpectatorPlayer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 304bc4d7ed..4d3b5eefb1 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -47,6 +47,8 @@ namespace osu.Game.Screens.Play { base.StartGameplay(); + // Start gameplay along with the very first arrival frame (the latest one). + score.Replay.Frames.Clear(); spectatorClient.OnNewFrames += userSentFrames; } From e18f4cd4bae79bd52801857a6e4deeefff3f35d5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Jun 2021 21:39:13 +0900 Subject: [PATCH 022/457] Combine methods --- osu.Game/Screens/Play/SpectatorPlayer.cs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 4d3b5eefb1..f662a479ec 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -63,6 +63,8 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; + bool isFirstBundle = score.Replay.Frames.Count == 0; + foreach (var frame in bundle.Frames) { IConvertibleReplayFrame convertibleFrame = GameplayRuleset.CreateConvertibleReplayFrame(); @@ -74,19 +76,8 @@ namespace osu.Game.Screens.Play score.Replay.Frames.Add(convertedFrame); } - seekToGameplay(); - } - - private bool seekedToGameplay; - - private void seekToGameplay() - { - if (seekedToGameplay || score.Replay.Frames.Count == 0) - return; - - NonFrameStableSeek(score.Replay.Frames[0].Time); - - seekedToGameplay = true; + if (isFirstBundle && score.Replay.Frames.Count > 0) + NonFrameStableSeek(score.Replay.Frames[0].Time); } protected override Score CreateScore() => score; From 09a2d008d226c8d0448067ce6b37d8d4cce86f87 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 13:41:41 +0300 Subject: [PATCH 023/457] Refrain from attempting to transform null skins --- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Skinning/LegacySkinTransformer.cs | 7 +++++-- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 5 +++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 9f9f42eda4..9fdaca88fd 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets [CanBeNull] public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().FirstOrDefault(); - public virtual ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => null; + public virtual ISkin CreateLegacySkinProvider([NotNull] ISkin skin, IBeatmap beatmap) => null; protected Ruleset() { diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index cd896ab51e..fedd63c7de 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -1,6 +1,8 @@ // 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 JetBrains.Annotations; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -20,11 +22,12 @@ namespace osu.Game.Skinning /// /// The which is being transformed. /// + [NotNull] protected ISkin Skin { get; } - protected LegacySkinTransformer(ISkin skin) + protected LegacySkinTransformer([NotNull] ISkin skin) { - Skin = skin; + Skin = skin ?? throw new ArgumentNullException(nameof(skin)); } public abstract Drawable GetDrawableComponent(ISkinComponent component); diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index eadf0e05b9..18399cbdb4 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,12 +21,12 @@ namespace osu.Game.Skinning protected override Container Content { get; } - public RulesetSkinProvidingContainer(Ruleset ruleset, IBeatmap beatmap, ISkin beatmapSkin) + public RulesetSkinProvidingContainer(Ruleset ruleset, IBeatmap beatmap, [CanBeNull] ISkin beatmapSkin) { this.ruleset = ruleset; this.beatmap = beatmap; - InternalChild = new BeatmapSkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkin, beatmap)) + InternalChild = new BeatmapSkinProvidingContainer(beatmapSkin == null ? null : ruleset.CreateLegacySkinProvider(beatmapSkin, beatmap)) { Child = Content = new Container { From ef2c4fd0d8ec68e8754aa12cf5cf381a5f839ed9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 15:05:08 +0300 Subject: [PATCH 024/457] Make `RulesetSkinProvidingContainer` able to be overriden for testing purposes --- osu.Game/Screens/Play/Player.cs | 4 +++- .../Skinning/RulesetSkinProvidingContainer.cs | 20 +++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index bec5181efe..fbcc7ea96f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -234,7 +234,7 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(GameplayBeatmap); - var rulesetSkinProvider = new RulesetSkinProvidingContainer(GameplayRuleset, playableBeatmap, Beatmap.Value.Skin); + var rulesetSkinProvider = CreateRulesetSkinProvider(GameplayRuleset, playableBeatmap, Beatmap.Value.Skin); // load the skinning hierarchy first. // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. @@ -315,6 +315,8 @@ namespace osu.Game.Screens.Play protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart); + protected virtual RulesetSkinProvidingContainer CreateRulesetSkinProvider(Ruleset ruleset, IBeatmap beatmap, ISkin beatmapSkin) => new RulesetSkinProvidingContainer(ruleset, beatmap, beatmapSkin); + private Drawable createUnderlayComponents() => DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 18399cbdb4..8087043230 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -16,15 +16,15 @@ namespace osu.Game.Skinning /// public class RulesetSkinProvidingContainer : SkinProvidingContainer { - private readonly Ruleset ruleset; - private readonly IBeatmap beatmap; + protected readonly Ruleset Ruleset; + protected readonly IBeatmap Beatmap; protected override Container Content { get; } public RulesetSkinProvidingContainer(Ruleset ruleset, IBeatmap beatmap, [CanBeNull] ISkin beatmapSkin) { - this.ruleset = ruleset; - this.beatmap = beatmap; + Ruleset = ruleset; + Beatmap = beatmap; InternalChild = new BeatmapSkinProvidingContainer(beatmapSkin == null ? null : ruleset.CreateLegacySkinProvider(beatmapSkin, beatmap)) { @@ -41,25 +41,25 @@ namespace osu.Game.Skinning [BackgroundDependencyLoader] private void load() { - updateSkins(); + UpdateSkins(); } protected override void OnSourceChanged() { - updateSkins(); + UpdateSkins(); base.OnSourceChanged(); } - private void updateSkins() + protected virtual void UpdateSkins() { SkinSources.Clear(); - SkinSources.Add(ruleset.CreateLegacySkinProvider(skinManager.CurrentSkin.Value, beatmap)); + SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.CurrentSkin.Value, Beatmap)); if (skinManager.CurrentSkin.Value is LegacySkin) - SkinSources.Add(ruleset.CreateLegacySkinProvider(skinManager.DefaultLegacySkin, beatmap)); + SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultLegacySkin, Beatmap)); - SkinSources.Add(ruleset.CreateLegacySkinProvider(skinManager.DefaultSkin, beatmap)); + SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultSkin, Beatmap)); } } } From 23d6c366acc34ee9f92a116ce2cace40008f3b04 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 15:36:53 +0300 Subject: [PATCH 025/457] Add method for assigning arbitrary skins to player in test scenes --- osu.Game/Tests/Visual/PlayerTestScene.cs | 8 +++++++ osu.Game/Tests/Visual/TestPlayer.cs | 28 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 088e997de9..e42a043eec 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -10,6 +10,7 @@ using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Skinning; namespace osu.Game.Tests.Visual { @@ -78,6 +79,8 @@ namespace osu.Game.Tests.Visual } Player = CreatePlayer(ruleset); + Player.Skin = GetPlayerSkin(); + LoadScreen(Player); } @@ -93,6 +96,11 @@ namespace osu.Game.Tests.Visual [NotNull] protected abstract Ruleset CreatePlayerRuleset(); + /// + /// Creates an to be put inside the 's ruleset skin providing container. + /// + protected virtual ISkin GetPlayerSkin() => null; + protected sealed override Ruleset CreateRuleset() => CreatePlayerRuleset(); 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 09da4db952..eecf8a2f6e 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -3,13 +3,17 @@ using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; 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; +using osu.Game.Skinning; namespace osu.Game.Tests.Visual { @@ -18,6 +22,8 @@ namespace osu.Game.Tests.Visual /// public class TestPlayer : Player { + public ISkin Skin { get; set; } + protected override bool PauseOnFocusLost { get; } public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; @@ -74,5 +80,27 @@ namespace osu.Game.Tests.Visual { ScoreProcessor.NewJudgement += r => Results.Add(r); } + + protected override RulesetSkinProvidingContainer CreateRulesetSkinProvider(Ruleset ruleset, IBeatmap beatmap, ISkin beatmapSkin) + => new TestSkinProvidingContainer(Skin, ruleset, beatmap, beatmapSkin); + + private class TestSkinProvidingContainer : RulesetSkinProvidingContainer + { + private readonly ISkin skin; + + public TestSkinProvidingContainer(ISkin skin, Ruleset ruleset, IBeatmap beatmap, [CanBeNull] ISkin beatmapSkin) + : base(ruleset, beatmap, beatmapSkin) + { + this.skin = skin; + } + + protected override void UpdateSkins() + { + base.UpdateSkins(); + + if (skin != null) + SkinSources.Insert(0, Ruleset.CreateLegacySkinProvider(skin, Beatmap)); + } + } } } From 680791301f70b8476bb40af982e6adba8254bcb5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 16:36:27 +0300 Subject: [PATCH 026/457] Consume new method rather than caching skin sources on top of `Player` --- .../TestSceneLegacyBeatmapSkin.cs | 7 +--- .../TestSceneSkinFallbacks.cs | 26 +----------- .../Tests/Beatmaps/HitObjectSampleTest.cs | 42 ++----------------- .../Beatmaps/LegacyBeatmapSkinColourTest.cs | 32 ++++---------- .../Tests/Visual/LegacySkinPlayerTestScene.cs | 17 +------- 5 files changed, 16 insertions(+), 108 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs index bc3daca16f..0077ff9e3c 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs @@ -101,15 +101,10 @@ namespace osu.Game.Rulesets.Catch.Tests AddAssert("is custom hyper dash fruit colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashFruitColour == TestSkin.HYPER_DASH_FRUIT_COLOUR); } - protected override ExposedPlayer CreateTestPlayer(bool userHasCustomColours) => new CatchExposedPlayer(userHasCustomColours); + protected override ExposedPlayer CreateTestPlayer() => new CatchExposedPlayer(); private class CatchExposedPlayer : ExposedPlayer { - public CatchExposedPlayer(bool userHasCustomColours) - : base(userHasCustomColours) - { - } - public Color4 UsableHyperDashColour => GameplayClockContainer.ChildrenOfType() .First() diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index fd523fffcb..2b45818aa9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -21,7 +21,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osu.Game.Storyboards; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { @@ -99,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Resolved] private AudioManager audio { get; set; } - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin); + protected override ISkin GetPlayerSkin() => testUserSkin; protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, audio, testBeatmapSkin); @@ -116,27 +115,6 @@ namespace osu.Game.Rulesets.Osu.Tests protected override ISkin GetSkin() => skin; } - public class SkinProvidingPlayer : TestPlayer - { - private readonly TestSource userSkin; - - public SkinProvidingPlayer(TestSource userSkin) - { - this.userSkin = userSkin; - } - - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - dependencies.CacheAs(userSkin); - - return dependencies; - } - } - public class TestSource : ISkinSource { private readonly string identifier; @@ -164,8 +142,8 @@ namespace osu.Game.Rulesets.Osu.Tests public ISample GetSample(ISampleInfo sampleInfo) => null; - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default; public IBindable GetConfig(TLookup lookup) => null; + public ISkin FindProvider(Func lookupFunction) => null; public event Action SourceChanged; diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index 7ee6c519b7..7af0397726 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -47,15 +47,11 @@ namespace osu.Game.Tests.Beatmaps private readonly TestResourceStore userSkinResourceStore = new TestResourceStore(); private readonly TestResourceStore beatmapSkinResourceStore = new TestResourceStore(); - private SkinSourceDependencyContainer dependencies; private IBeatmap currentTestBeatmap; protected sealed override bool HasCustomSteps => true; protected override bool Autoplay => true; - protected sealed override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - => new DependencyContainer(dependencies = new SkinSourceDependencyContainer(base.CreateChildDependencies(parent))); - protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestBeatmap; protected sealed override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) @@ -63,6 +59,8 @@ namespace osu.Game.Tests.Beatmaps protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false); + protected override ISkin GetPlayerSkin() => Skin; + protected void CreateTestWithBeatmap(string filename) { CreateTest(() => @@ -109,8 +107,7 @@ namespace osu.Game.Tests.Beatmaps } }; - // Need to refresh the cached skin source to refresh the skin resource store. - dependencies.SkinSource = new SkinProvidingContainer(Skin = new LegacySkin(userSkinInfo, this)); + Skin = new LegacySkin(userSkinInfo, this); }); } @@ -132,39 +129,6 @@ namespace osu.Game.Tests.Beatmaps #endregion - private class SkinSourceDependencyContainer : IReadOnlyDependencyContainer - { - public ISkinSource SkinSource; - - private readonly IReadOnlyDependencyContainer fallback; - - public SkinSourceDependencyContainer(IReadOnlyDependencyContainer fallback) - { - this.fallback = fallback; - } - - public object Get(Type type) - { - if (type == typeof(ISkinSource)) - return SkinSource; - - return fallback.Get(type); - } - - public object Get(Type type, CacheInfo info) - { - if (type == typeof(ISkinSource)) - return SkinSource; - - return fallback.Get(type, info); - } - - public void Inject(T instance) where T : class - { - // Never used directly - } - } - private class TestResourceStore : IResourceStore { public readonly List PerformedLookups = new List(); diff --git a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs index 2540b6d7da..1feb3eebbf 100644 --- a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs @@ -4,13 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Screens.Play; using osu.Game.Skinning; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -49,36 +47,24 @@ namespace osu.Game.Tests.Beatmaps protected virtual ExposedPlayer LoadBeatmap(bool userHasCustomColours) { - ExposedPlayer player; - Beatmap.Value = testBeatmap; - LoadScreen(player = CreateTestPlayer(userHasCustomColours)); + ExposedPlayer player = CreateTestPlayer(); + + player.Skin = new TestSkin(userHasCustomColours); + + LoadScreen(player); return player; } - protected virtual ExposedPlayer CreateTestPlayer(bool userHasCustomColours) => new ExposedPlayer(userHasCustomColours); + protected virtual ExposedPlayer CreateTestPlayer() => new ExposedPlayer(); - protected class ExposedPlayer : Player + protected class ExposedPlayer : TestPlayer { - protected readonly bool UserHasCustomColours; - - public ExposedPlayer(bool userHasCustomColours) - : base(new PlayerConfiguration - { - AllowPause = false, - ShowResults = false, - }) + public ExposedPlayer() + : base(false, false) { - UserHasCustomColours = userHasCustomColours; - } - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(new TestSkin(UserHasCustomColours)); - return dependencies; } public IReadOnlyList UsableComboColours => diff --git a/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs index b810bbf6ae..14a928d3c1 100644 --- a/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs +++ b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs @@ -5,7 +5,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; -using osu.Game.Rulesets; using osu.Game.Skinning; namespace osu.Game.Tests.Visual @@ -15,15 +14,12 @@ namespace osu.Game.Tests.Visual { protected LegacySkin LegacySkin { get; private set; } - private ISkinSource legacySkinSource; - - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(legacySkinSource); + protected override ISkin GetPlayerSkin() => LegacySkin; [BackgroundDependencyLoader] private void load(SkinManager skins) { LegacySkin = new DefaultLegacySkin(skins); - legacySkinSource = new SkinProvidingContainer(LegacySkin); } [SetUpSteps] @@ -48,16 +44,5 @@ namespace osu.Game.Tests.Visual t.Reload(); })); } - - public class SkinProvidingPlayer : TestPlayer - { - [Cached(typeof(ISkinSource))] - private readonly ISkinSource skinSource; - - public SkinProvidingPlayer(ISkinSource skinSource) - { - this.skinSource = skinSource; - } - } } } From 58d71e4aead45fd8a756d574d904ded7ee5e3e3e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Jun 2021 22:41:38 +0900 Subject: [PATCH 027/457] Remove local "next frame" storage --- .../Visual/Gameplay/TestSceneSpectator.cs | 17 +++---- .../TestSceneMultiSpectatorLeaderboard.cs | 4 +- .../TestSceneMultiSpectatorScreen.cs | 12 +---- .../Visual/Spectator/TestSpectatorClient.cs | 44 ++++++++++++++----- 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 6eeb3596a8..0926b8778b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -41,8 +41,6 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private OsuGameBase game { get; set; } - private int nextFrame; - private BeatmapSetInfo importedBeatmap; private int importedBeatmapId; @@ -51,8 +49,6 @@ namespace osu.Game.Tests.Visual.Gameplay { base.SetUpSteps(); - AddStep("reset sent frames", () => nextFrame = 0); - AddStep("import beatmap", () => { importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result; @@ -105,7 +101,8 @@ namespace osu.Game.Tests.Visual.Gameplay waitForPlayer(); checkPaused(true); - sendFrames(1000); // send enough frames to ensure play won't be paused + // send enough frames to ensure play won't be paused + sendFrames(100); checkPaused(false); } @@ -114,12 +111,12 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestSpectatingDuringGameplay() { start(); + sendFrames(300); loadSpectatingScreen(); waitForPlayer(); - AddStep("advance frame count", () => nextFrame = 300); - sendFrames(); + sendFrames(300); AddUntilStep("playing from correct point in time", () => player.ChildrenOfType().First().FrameStableClock.CurrentTime > 30000); } @@ -220,11 +217,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void sendFrames(int count = 10) { - AddStep("send frames", () => - { - testSpectatorClient.SendFrames(streamingUser.Id, nextFrame, count); - nextFrame += count; - }); + AddStep("send frames", () => testSpectatorClient.SendFrames(streamingUser.Id, count)); } private void loadSpectatingScreen() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 5ad35be0ec..7f8a44fb98 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -96,10 +96,10 @@ namespace osu.Game.Tests.Visual.Multiplayer // For player 2, send frames in sets of 10. for (int i = 0; i < 100; i++) { - spectatorClient.SendFrames(PLAYER_1_ID, i, 1); + spectatorClient.SendFrames(PLAYER_1_ID, 1); if (i % 10 == 0) - spectatorClient.SendFrames(PLAYER_2_ID, i, 10); + spectatorClient.SendFrames(PLAYER_2_ID, 10); } }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index b91391c409..f5032fc2a5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -37,7 +37,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private MultiSpectatorScreen spectatorScreen; private readonly List playingUserIds = new List(); - private readonly Dictionary nextFrame = new Dictionary(); private BeatmapSetInfo importedSet; private BeatmapInfo importedBeatmap; @@ -55,8 +54,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { base.SetUpSteps(); - AddStep("reset sent frames", () => nextFrame.Clear()); - AddStep("add streaming client", () => { Remove(spectatorClient); @@ -80,8 +77,6 @@ namespace osu.Game.Tests.Visual.Multiplayer Client.CurrentMatchPlayingUserIds.Add(PLAYER_2_ID); playingUserIds.Add(PLAYER_1_ID); playingUserIds.Add(PLAYER_2_ID); - nextFrame[PLAYER_1_ID] = 0; - nextFrame[PLAYER_2_ID] = 0; }); loadSpectateScreen(false); @@ -253,7 +248,6 @@ namespace osu.Game.Tests.Visual.Multiplayer Client.CurrentMatchPlayingUserIds.Add(id); spectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId); playingUserIds.Add(id); - nextFrame[id] = 0; } }); } @@ -264,7 +258,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { spectatorClient.EndPlay(userId); playingUserIds.Remove(userId); - nextFrame.Remove(userId); }); } @@ -275,10 +268,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("send frames", () => { foreach (int id in userIds) - { - spectatorClient.SendFrames(id, nextFrame[id], count); - nextFrame[id] += count; - } + spectatorClient.SendFrames(id, count); }); } diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index c7aa43b377..f206d4f8b0 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -20,9 +20,15 @@ namespace osu.Game.Tests.Visual.Spectator { public class TestSpectatorClient : SpectatorClient { + /// + /// Maximum number of frames sent per bundle via . + /// + public const int FRAME_BUNDLE_SIZE = 10; + public override IBindable IsConnected { get; } = new Bindable(true); private readonly Dictionary userBeatmapDictionary = new Dictionary(); + private readonly Dictionary userNextFrameDictionary = new Dictionary(); [Resolved] private IAPIProvider api { get; set; } = null!; @@ -35,6 +41,7 @@ namespace osu.Game.Tests.Visual.Spectator public void StartPlay(int userId, int beatmapId) { userBeatmapDictionary[userId] = beatmapId; + userNextFrameDictionary[userId] = 0; sendPlayingState(userId); } @@ -57,24 +64,41 @@ namespace osu.Game.Tests.Visual.Spectator public new void Schedule(Action action) => base.Schedule(action); /// - /// Sends frames for an arbitrary user. + /// Sends frames for an arbitrary user, in bundles containing 10 frames each. /// /// The user to send frames for. - /// The frame index. - /// The number of frames to send. - public void SendFrames(int userId, int index, int count) + /// The total number of frames to send. + public void SendFrames(int userId, int count) { var frames = new List(); - for (int i = index; i < index + count; i++) - { - var buttonState = i == index + count - 1 ? ReplayButtonState.None : ReplayButtonState.Left1; + int currentFrameIndex = userNextFrameDictionary[userId]; + int lastFrameIndex = currentFrameIndex + count - 1; - frames.Add(new LegacyReplayFrame(i * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState)); + for (; currentFrameIndex <= lastFrameIndex; currentFrameIndex++) + { + // This is done in the next frame so that currentFrameIndex is updated to the correct value. + if (frames.Count == FRAME_BUNDLE_SIZE) + flush(); + + var buttonState = currentFrameIndex == lastFrameIndex ? ReplayButtonState.None : ReplayButtonState.Left1; + frames.Add(new LegacyReplayFrame(currentFrameIndex * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState)); } - var bundle = new FrameDataBundle(new ScoreInfo { Combo = index + count }, frames); - ((ISpectatorClient)this).UserSentFrames(userId, bundle); + flush(); + + userNextFrameDictionary[userId] = currentFrameIndex; + + void flush() + { + if (frames.Count == 0) + return; + + var bundle = new FrameDataBundle(new ScoreInfo { Combo = currentFrameIndex }, frames.ToArray()); + ((ISpectatorClient)this).UserSentFrames(userId, bundle); + + frames.Clear(); + } } protected override Task BeginPlayingInternal(SpectatorState state) From 2240e2c39c7b1bf3ebe846b3d8c40119086b0e58 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 17:23:15 +0300 Subject: [PATCH 028/457] Refrain from attempting to clear skin sources in disposal `Drawable.Dispose` is usually in an asynchronous context (async disposals stuff) and therefore this could cause a "collection was modified; enumeration opeartion may not execute" exception. --- osu.Game/Skinning/SkinProvidingContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 74a465a91f..078c666472 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -210,7 +210,8 @@ namespace osu.Game.Skinning if (fallbackSource != null) fallbackSource.SourceChanged -= OnSourceChanged; - SkinSources.Clear(); + foreach (var source in SkinSources.OfType()) + source.SourceChanged -= OnSourceChanged; } private class NoFallbackProxy : ISkinSource From 169c98f963e3703e6d2166081b43ab3ac3dec66a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 00:25:19 +0300 Subject: [PATCH 029/457] Add skin providing ruleset resources in `RulesetSkinProvidingContainer` --- osu.Game/Skinning/RulesetResourcesSkin.cs | 51 +++++++++++++++++++ .../Skinning/RulesetSkinProvidingContainer.cs | 10 ++++ 2 files changed, 61 insertions(+) create mode 100644 osu.Game/Skinning/RulesetResourcesSkin.cs diff --git a/osu.Game/Skinning/RulesetResourcesSkin.cs b/osu.Game/Skinning/RulesetResourcesSkin.cs new file mode 100644 index 0000000000..1905a8a899 --- /dev/null +++ b/osu.Game/Skinning/RulesetResourcesSkin.cs @@ -0,0 +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 osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; +using osu.Framework.Platform; +using osu.Game.Audio; +using osu.Game.Rulesets; + +namespace osu.Game.Skinning +{ + /// + /// An providing the resources of the ruleset for accessibility during lookups. + /// + public class RulesetResourcesSkin : ISkin + { + private readonly TextureStore rulesetTextures; + private readonly ISampleStore rulesetSamples; + + public RulesetResourcesSkin(Ruleset ruleset, GameHost host, AudioManager audio) + { + IResourceStore rulesetResources = ruleset.CreateResourceStore(); + + rulesetTextures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(rulesetResources, @"Textures"))); + rulesetSamples = audio.GetSampleStore(new NamespacedResourceStore(rulesetResources, @"Samples")); + } + + public Drawable GetDrawableComponent(ISkinComponent component) => null; + + public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => rulesetTextures.Get(componentName, wrapModeS, wrapModeT); + + public ISample GetSample(ISampleInfo sampleInfo) + { + foreach (var lookup in sampleInfo.LookupNames) + { + ISample sample = rulesetSamples.Get(lookup); + if (sample != null) + return sample; + } + + return null; + } + + public IBindable GetConfig(TLookup lookup) => null; + } +} diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 8087043230..3d11b58a01 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -3,8 +3,10 @@ using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -35,6 +37,12 @@ namespace osu.Game.Skinning }; } + [Resolved] + private GameHost host { get; set; } + + [Resolved] + private AudioManager audio { get; set; } + [Resolved] private SkinManager skinManager { get; set; } @@ -60,6 +68,8 @@ namespace osu.Game.Skinning SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultLegacySkin, Beatmap)); SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultSkin, Beatmap)); + + SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); } } } From ca3c45363a8d7c20b6e42c5676f38c7fb97b4515 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 23:59:59 +0300 Subject: [PATCH 030/457] Add test coverage --- .../TestSceneRulesetSkinProvidingContainer.cs | 60 +++++++++++++++++++ .../Testing/TestSceneRulesetDependencies.cs | 2 +- 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs new file mode 100644 index 0000000000..41f1465c90 --- /dev/null +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Rulesets; +using osu.Game.Skinning; +using osu.Game.Tests.Testing; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Rulesets +{ + public class TestSceneRulesetSkinProvidingContainer : OsuTestScene + { + private RulesetSkinProvidingContainer rulesetSkinProvider; + private SkinRequester requester; + + protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = rulesetSkinProvider = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin) + .WithChild(requester = new SkinRequester()); + }); + + [Test] + public void TestRulesetResources() + { + AddAssert("ruleset texture retrieved via skin", () => requester.GetTexture("test-image") != null); + AddAssert("ruleset sample retrieved via skin", () => requester.GetSample(new SampleInfo("test-sample")) != null); + } + + private class SkinRequester : Drawable, ISkin + { + private ISkinSource skin; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + this.skin = skin; + } + + public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component); + + public Texture GetTexture(string componentName, WrapMode wrapModeS = default, WrapMode wrapModeT = default) => skin.GetTexture(componentName); + + public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo); + + public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); + } + } +} diff --git a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs index 97087e31ab..fb50da32f3 100644 --- a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs +++ b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Testing Dependencies.Get() != null); } - private class TestRuleset : Ruleset + public class TestRuleset : Ruleset { public override string Description => string.Empty; public override string ShortName => string.Empty; From fe48ce4bd5d80ec5b039a5a88a7fa5df9fcfa749 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 00:58:05 +0300 Subject: [PATCH 031/457] Remove unaccessed field It was a warning... --- .../Rulesets/TestSceneRulesetSkinProvidingContainer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index 41f1465c90..e9f9766725 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -19,7 +19,6 @@ namespace osu.Game.Tests.Rulesets { public class TestSceneRulesetSkinProvidingContainer : OsuTestScene { - private RulesetSkinProvidingContainer rulesetSkinProvider; private SkinRequester requester; protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); @@ -27,7 +26,7 @@ namespace osu.Game.Tests.Rulesets [SetUp] public void SetUp() => Schedule(() => { - Child = rulesetSkinProvider = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin) + Child = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin) .WithChild(requester = new SkinRequester()); }); From b9050f91a440c8d10cd3637d0fd179c38298a9c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 14:49:35 +0900 Subject: [PATCH 032/457] Expose as `Skin`s and consume `SkinInfo` from instances --- osu.Game/Skinning/SkinManager.cs | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 89f166dc2a..ffdbadf54c 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -48,19 +48,15 @@ namespace osu.Game.Skinning protected override string ImportFromStablePath => "Skins"; - private readonly Skin defaultSkin; - /// /// An providing the resources of the default skin. /// - public ISkin DefaultSkin => defaultSkin; - - private readonly Skin defaultLegacySkin; + public Skin DefaultSkin { get; } /// /// An providing the resources of the default legacy skin. /// - public ISkin DefaultLegacySkin => defaultLegacySkin; + public Skin DefaultLegacySkin { get; } public SkinManager(Storage storage, DatabaseContextFactory contextFactory, GameHost host, IResourceStore resources, AudioManager audio) : base(storage, contextFactory, new SkinStore(contextFactory, storage), host) @@ -69,12 +65,12 @@ namespace osu.Game.Skinning this.host = host; this.resources = resources; - defaultLegacySkin = new DefaultLegacySkin(this); - defaultSkin = new DefaultSkin(this); + DefaultLegacySkin = new DefaultLegacySkin(this); + DefaultSkin = new DefaultSkin(this); CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue); - CurrentSkin.Value = defaultSkin; + CurrentSkin.Value = DefaultSkin; CurrentSkin.ValueChanged += skin => { if (skin.NewValue.SkinInfo != CurrentSkinInfo.Value) @@ -93,8 +89,8 @@ namespace osu.Game.Skinning public List GetAllUsableSkins() { var userSkins = GetAllUserSkins(); - userSkins.Insert(0, SkinInfo.Default); - userSkins.Insert(1, Skinning.DefaultLegacySkin.Info); + userSkins.Insert(0, DefaultSkin.SkinInfo); + userSkins.Insert(1, DefaultLegacySkin.SkinInfo); return userSkins; } @@ -236,11 +232,11 @@ namespace osu.Game.Skinning if (lookupFunction(CurrentSkin.Value)) return CurrentSkin.Value; - if (CurrentSkin.Value is LegacySkin && lookupFunction(defaultLegacySkin)) - return defaultLegacySkin; + if (CurrentSkin.Value is LegacySkin && lookupFunction(DefaultLegacySkin)) + return DefaultLegacySkin; - if (lookupFunction(defaultSkin)) - return defaultSkin; + if (lookupFunction(DefaultSkin)) + return DefaultSkin; return null; } @@ -254,11 +250,11 @@ namespace osu.Game.Skinning // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. // When attempting to address this, we may want to move the full DefaultLegacySkin fallback logic to within Player itself (to better allow // for beatmap skin visibility). - if (CurrentSkin.Value is LegacySkin && lookupFunction(defaultLegacySkin) is T legacySourced) + if (CurrentSkin.Value is LegacySkin && lookupFunction(DefaultLegacySkin) is T legacySourced) return legacySourced; // Finally fall back to the (non-legacy) default. - return lookupFunction(defaultSkin); + return lookupFunction(DefaultSkin); } #region IResourceStorageProvider From debd359d2e5d36361757d37188b4498c530bdee0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 14:50:21 +0900 Subject: [PATCH 033/457] Update xmldoc --- osu.Game/Skinning/SkinManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index ffdbadf54c..660f44772c 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -49,12 +49,12 @@ namespace osu.Game.Skinning protected override string ImportFromStablePath => "Skins"; /// - /// An providing the resources of the default skin. + /// The default skin. /// public Skin DefaultSkin { get; } /// - /// An providing the resources of the default legacy skin. + /// The default legacy skin. /// public Skin DefaultLegacySkin { get; } From 1c67ef7c9190ae8bdfe36cce0ed6d6e5576b040c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 16:23:59 +0900 Subject: [PATCH 034/457] Make catchup clock support seeking --- .../Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs index 9e1a020eca..20d12d62a3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs @@ -34,7 +34,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public void Stop() => IsRunning = false; - public bool Seek(double position) => true; + public bool Seek(double position) + { + CurrentTime = position; + return true; + } public void ResetSpeedAdjustments() { From 75d825c85cb01be1a55e56e8a2adfb1dd745c4db Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 16:24:52 +0900 Subject: [PATCH 035/457] Dont control master clock from sync manager --- .../Multiplayer/Spectate/CatchUpSyncManager.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index efc12eaaa5..2d0e88238d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -63,7 +63,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } updateCatchup(); - updateMasterClock(); } /// @@ -133,21 +132,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } } } - - /// - /// Updates the master clock's running state. - /// - private void updateMasterClock() - { - bool anyInSync = playerClocks.Any(s => !s.IsCatchingUp); - - if (MasterClock.IsRunning != anyInSync) - { - if (anyInSync) - MasterClock.Start(); - else - MasterClock.Stop(); - } - } } } From a99cb79738f61d4cacbbda13c9b87283fba04733 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 16:25:45 +0900 Subject: [PATCH 036/457] Seek master clock on multi-spectator start --- .../Multiplayer/Spectate/CatchUpSyncManager.cs | 13 +++++++++++-- .../Multiplayer/Spectate/ISyncManager.cs | 6 ++++++ .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 14 ++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index 2d0e88238d..0f7466e44b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.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.Collections.Generic; using System.Linq; using osu.Framework.Graphics; @@ -33,6 +34,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public IAdjustableClock MasterClock { get; } + public event Action ReadyToStart; + /// /// The player clocks. /// @@ -80,14 +83,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate int readyCount = playerClocks.Count(s => !s.WaitingOnFrames.Value); if (readyCount == playerClocks.Count) - return hasStarted = true; + return performStart(); if (readyCount > 0) { firstStartAttemptTime ??= Time.Current; if (Time.Current - firstStartAttemptTime > MAXIMUM_START_DELAY) - return hasStarted = true; + return performStart(); + } + + bool performStart() + { + ReadyToStart?.Invoke(); + return hasStarted = true; } return false; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs index bd698108f6..f59c1a75f7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate @@ -15,6 +16,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// IAdjustableClock MasterClock { get; } + /// + /// An event which is invoked when gameplay is ready to start. + /// + event Action ReadyToStart; + /// /// Adds an to manage. /// diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 983daac909..e401c45933 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -101,6 +101,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, leaderboardContainer.Add); + + syncManager.ReadyToStart += onReadyToStart; } protected override void LoadComplete() @@ -129,6 +131,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private bool isCandidateAudioSource([CanBeNull] ISpectatorPlayerClock clock) => clock?.IsRunning == true && !clock.IsCatchingUp && !clock.WaitingOnFrames.Value; + private void onReadyToStart() + { + var startTime = instances.Where(i => i.Score != null) + .SelectMany(i => i.Score.Replay.Frames) + .Select(f => f.Time) + .DefaultIfEmpty(0) + .Max(); + + masterClockContainer.Seek(startTime); + masterClockContainer.Start(); + } + protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) { } From a985e3b8d3562e357d3f431482794999e40948b9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 11:25:07 +0300 Subject: [PATCH 037/457] Apply documentation settings for better readability Co-authored-by: Dean Herbert Co-authored-by: Dan Balasescu --- osu.Game/Skinning/SkinProvidingContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 078c666472..0f2d8e2c22 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Skinning public event Action SourceChanged; /// - /// The list of skins provided by this . + /// Skins which should be exposed by this container, in order of lookup precedence. /// protected readonly BindableList SkinSources = new BindableList(); @@ -44,7 +44,7 @@ namespace osu.Game.Skinning protected virtual bool AllowColourLookup => true; /// - /// Constructs a new with a single skin added to the protected list. + /// Constructs a new initialised with a single skin source. /// public SkinProvidingContainer(ISkin skin) : this() @@ -54,7 +54,7 @@ namespace osu.Game.Skinning /// /// Constructs a new with no sources. - /// Up to the implementation for adding to the list. + /// Implementations can add or change sources through the list. /// protected SkinProvidingContainer() { From 813285275307871b77479effcf17ce9d797c845e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 11:34:22 +0300 Subject: [PATCH 038/457] Add other affectable change action cases --- osu.Game/Skinning/SkinProvidingContainer.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 0f2d8e2c22..ab33a66265 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -72,11 +72,21 @@ namespace osu.Game.Skinning break; + case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Remove: foreach (var source in args.OldItems.Cast().OfType()) source.SourceChanged -= OnSourceChanged; break; + + case NotifyCollectionChangedAction.Replace: + foreach (var source in args.OldItems.Cast().OfType()) + source.SourceChanged -= OnSourceChanged; + + foreach (var source in args.NewItems.Cast().OfType()) + source.SourceChanged += OnSourceChanged; + + break; } }), true); } From 2e01e611775c6a143d7363a51a9f03a4ef9a59d6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 11:46:29 +0300 Subject: [PATCH 039/457] Move TODO comment to correct location --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 1 + osu.Game/Skinning/SkinManager.cs | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 8087043230..c57522726d 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -56,6 +56,7 @@ namespace osu.Game.Skinning SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.CurrentSkin.Value, Beatmap)); + // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. if (skinManager.CurrentSkin.Value is LegacySkin) SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultLegacySkin, Beatmap)); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 660f44772c..7acc52809f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -247,9 +247,6 @@ namespace osu.Game.Skinning if (lookupFunction(CurrentSkin.Value) is T skinSourced) return skinSourced; - // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. - // When attempting to address this, we may want to move the full DefaultLegacySkin fallback logic to within Player itself (to better allow - // for beatmap skin visibility). if (CurrentSkin.Value is LegacySkin && lookupFunction(DefaultLegacySkin) is T legacySourced) return legacySourced; From 9eaaac6bb790616db3dcb259669e2b5d48b74b39 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 17:59:27 +0900 Subject: [PATCH 040/457] Remove master clock state assertions --- .../OnlinePlay/TestSceneCatchUpSyncManager.cs | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs index d4e591cf09..6851df3832 100644 --- a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs +++ b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs @@ -31,32 +31,24 @@ namespace osu.Game.Tests.OnlinePlay } [Test] - public void TestMasterClockStartsWhenAllPlayerClocksHaveFrames() + public void TestPlayerClocksStartWhenAllHaveFrames() { setWaiting(() => player1, false); - assertMasterState(false); assertPlayerClockState(() => player1, false); assertPlayerClockState(() => player2, false); setWaiting(() => player2, false); - assertMasterState(true); assertPlayerClockState(() => player1, true); assertPlayerClockState(() => player2, true); } [Test] - public void TestMasterClockDoesNotStartWhenNoneReadyForMaximumDelayTime() - { - AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); - assertMasterState(false); - } - - [Test] - public void TestMasterClockStartsWhenAnyReadyForMaximumDelayTime() + public void TestReadyPlayersStartWhenReadyForMaximumDelayTime() { setWaiting(() => player1, false); AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); - assertMasterState(true); + assertPlayerClockState(() => player1, true); + assertPlayerClockState(() => player2, false); } [Test] @@ -153,9 +145,6 @@ namespace osu.Game.Tests.OnlinePlay private void setPlayerClockTime(Func playerClock, double offsetFromMaster) => AddStep($"set player clock {playerClock().Id} = master - {offsetFromMaster}", () => playerClock().Seek(master.CurrentTime - offsetFromMaster)); - private void assertMasterState(bool running) - => AddAssert($"master clock {(running ? "is" : "is not")} running", () => master.IsRunning == running); - private void assertCatchingUp(Func playerClock, bool catchingUp) => AddAssert($"player clock {playerClock().Id} {(catchingUp ? "is" : "is not")} catching up", () => playerClock().IsCatchingUp == catchingUp); @@ -201,6 +190,11 @@ namespace osu.Game.Tests.OnlinePlay private class TestManualClock : ManualClock, IAdjustableClock { + public TestManualClock() + { + IsRunning = true; + } + public void Start() => IsRunning = true; public void Stop() => IsRunning = false; From e9ebbd298d4c7129123e40c1f8b8352abfd9ed96 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 18:13:54 +0900 Subject: [PATCH 041/457] Add a few more tests --- .../TestSceneMultiSpectatorScreen.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index f5032fc2a5..35d096a92e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -12,6 +12,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.Spectator; +using osu.Game.Rulesets.UI; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; @@ -224,6 +225,36 @@ namespace osu.Game.Tests.Visual.Multiplayer assertMuted(PLAYER_2_ID, true); } + [Test] + public void TestSpectatingDuringGameplay() + { + var players = new[] { PLAYER_1_ID, PLAYER_2_ID }; + + start(players); + sendFrames(players, 300); + + loadSpectateScreen(); + sendFrames(players, 300); + + AddUntilStep("playing from correct point in time", () => this.ChildrenOfType().All(r => r.FrameStableClock.CurrentTime > 30000)); + } + + [Test] + public void TestSpectatingDuringGameplayWithLateFrames() + { + start(new[] { PLAYER_1_ID, PLAYER_2_ID }); + sendFrames(new[] { PLAYER_1_ID, PLAYER_2_ID }, 300); + + loadSpectateScreen(); + sendFrames(PLAYER_1_ID, 300); + + AddWaitStep("wait maximum start delay seconds", (int)(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); + checkPaused(PLAYER_1_ID, false); + + sendFrames(PLAYER_2_ID, 300); + AddUntilStep("player 2 playing from correct point in time", () => getPlayer(PLAYER_2_ID).ChildrenOfType().Single().FrameStableClock.CurrentTime > 30000); + } + private void loadSpectateScreen(bool waitForPlayerLoad = true) { AddStep("load screen", () => From 263b8ff097ed688e3fe0ff6e9bb90d1a17c93057 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 18:14:37 +0900 Subject: [PATCH 042/457] Wait for full player load --- osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index fe79e5db72..95ccc08608 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// /// Whether a is loaded in the area. /// - public bool PlayerLoaded => stack?.CurrentScreen is Player; + public bool PlayerLoaded => (stack?.CurrentScreen as Player)?.IsLoaded == true; /// /// The user id this corresponds to. From 9e16359f18a7e90049a1dacb80bad9ff2fe8d130 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 12:29:28 +0300 Subject: [PATCH 043/457] Refactor disallowing in `SkinProvidingContainer` to become per source Fixes `FindProvider` becoming completely broken, because of no way to perform the checks on one skin source. --- osu.Game/Skinning/SkinProvidingContainer.cs | 188 ++++++++++---------- 1 file changed, 96 insertions(+), 92 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index ab33a66265..c9bb3a6ec4 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using JetBrains.Annotations; @@ -28,11 +29,15 @@ namespace osu.Game.Skinning /// protected readonly BindableList SkinSources = new BindableList(); + /// + /// A dictionary mapping each from the + /// to one that performs the "allow lookup" checks before proceeding with a lookup. + /// + private readonly Dictionary disableableSkinSources = new Dictionary(); + [CanBeNull] private ISkinSource fallbackSource; - private readonly NoFallbackProxy noFallbackLookupProxy; - protected virtual bool AllowDrawableLookup(ISkinComponent component) => true; protected virtual bool AllowTextureLookup(string componentName) => true; @@ -60,31 +65,49 @@ namespace osu.Game.Skinning { RelativeSizeAxes = Axes.Both; - noFallbackLookupProxy = new NoFallbackProxy(this); - SkinSources.BindCollectionChanged(((_, args) => { switch (args.Action) { case NotifyCollectionChangedAction.Add: - foreach (var source in args.NewItems.Cast().OfType()) - source.SourceChanged += OnSourceChanged; + foreach (var skin in args.NewItems.Cast()) + { + disableableSkinSources.Add(skin, new DisableableSkinSource(skin, this)); + + if (skin is ISkinSource source) + source.SourceChanged += OnSourceChanged; + } break; case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Remove: - foreach (var source in args.OldItems.Cast().OfType()) - source.SourceChanged -= OnSourceChanged; + foreach (var skin in args.OldItems.Cast()) + { + disableableSkinSources.Remove(skin); + + if (skin is ISkinSource source) + source.SourceChanged -= OnSourceChanged; + } break; case NotifyCollectionChangedAction.Replace: - foreach (var source in args.OldItems.Cast().OfType()) - source.SourceChanged -= OnSourceChanged; + foreach (var skin in args.OldItems.Cast()) + { + disableableSkinSources.Remove(skin); - foreach (var source in args.NewItems.Cast().OfType()) - source.SourceChanged += OnSourceChanged; + if (skin is ISkinSource source) + source.SourceChanged -= OnSourceChanged; + } + + foreach (var skin in args.NewItems.Cast()) + { + disableableSkinSources.Add(skin, new DisableableSkinSource(skin, this)); + + if (skin is ISkinSource source) + source.SourceChanged += OnSourceChanged; + } break; } @@ -95,8 +118,7 @@ namespace osu.Game.Skinning { foreach (var skin in SkinSources) { - // a proxy must be used here to correctly pass through the "Allow" checks without implicitly falling back to the fallbackSource. - if (lookupFunction(noFallbackLookupProxy)) + if (lookupFunction(disableableSkinSources[skin])) return skin; } @@ -104,94 +126,50 @@ namespace osu.Game.Skinning } public Drawable GetDrawableComponent(ISkinComponent component) - => GetDrawableComponent(component, true); - - public Drawable GetDrawableComponent(ISkinComponent component, bool fallback) { - if (AllowDrawableLookup(component)) + foreach (var skin in SkinSources) { - foreach (var skin in SkinSources) - { - Drawable sourceDrawable; - if ((sourceDrawable = skin?.GetDrawableComponent(component)) != null) - return sourceDrawable; - } + Drawable sourceDrawable; + if ((sourceDrawable = disableableSkinSources[skin]?.GetDrawableComponent(component)) != null) + return sourceDrawable; } - if (!fallback) - return null; - return fallbackSource?.GetDrawableComponent(component); } public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) - => GetTexture(componentName, wrapModeS, wrapModeT, true); - - public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT, bool fallback) { - if (AllowTextureLookup(componentName)) + foreach (var skin in SkinSources) { - foreach (var skin in SkinSources) - { - Texture sourceTexture; - if ((sourceTexture = skin?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) - return sourceTexture; - } + Texture sourceTexture; + if ((sourceTexture = disableableSkinSources[skin]?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) + return sourceTexture; } - if (!fallback) - return null; - return fallbackSource?.GetTexture(componentName, wrapModeS, wrapModeT); } public ISample GetSample(ISampleInfo sampleInfo) - => GetSample(sampleInfo, true); - - public ISample GetSample(ISampleInfo sampleInfo, bool fallback) { - if (AllowSampleLookup(sampleInfo)) + foreach (var skin in SkinSources) { - foreach (var skin in SkinSources) - { - ISample sourceSample; - if ((sourceSample = skin?.GetSample(sampleInfo)) != null) - return sourceSample; - } + ISample sourceSample; + if ((sourceSample = disableableSkinSources[skin]?.GetSample(sampleInfo)) != null) + return sourceSample; } - if (!fallback) - return null; - return fallbackSource?.GetSample(sampleInfo); } public IBindable GetConfig(TLookup lookup) - => GetConfig(lookup, true); - - public IBindable GetConfig(TLookup lookup, bool fallback) { - if (lookup is GlobalSkinColours || lookup is SkinCustomColourLookup) - return lookupWithFallback(lookup, AllowColourLookup, fallback); - - return lookupWithFallback(lookup, AllowConfigurationLookup, fallback); - } - - private IBindable lookupWithFallback(TLookup lookup, bool canUseSkinLookup, bool canUseFallback) - { - if (canUseSkinLookup) + foreach (var skin in SkinSources) { - foreach (var skin in SkinSources) - { - IBindable bindable; - if ((bindable = skin?.GetConfig(lookup)) != null) - return bindable; - } + IBindable bindable; + if ((bindable = disableableSkinSources[skin]?.GetConfig(lookup)) != null) + return bindable; } - if (!canUseFallback) - return null; - return fallbackSource?.GetConfig(lookup); } @@ -224,35 +202,61 @@ namespace osu.Game.Skinning source.SourceChanged -= OnSourceChanged; } - private class NoFallbackProxy : ISkinSource + private class DisableableSkinSource : ISkin { + private readonly ISkin skin; private readonly SkinProvidingContainer provider; - public NoFallbackProxy(SkinProvidingContainer provider) + public DisableableSkinSource(ISkin skin, SkinProvidingContainer provider) { + this.skin = skin; this.provider = provider; } public Drawable GetDrawableComponent(ISkinComponent component) - => provider.GetDrawableComponent(component, false); - - public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) - => provider.GetTexture(componentName, wrapModeS, wrapModeT, false); - - public ISample GetSample(ISampleInfo sampleInfo) - => provider.GetSample(sampleInfo, false); - - public IBindable GetConfig(TLookup lookup) - => provider.GetConfig(lookup, false); - - public event Action SourceChanged { - add => provider.SourceChanged += value; - remove => provider.SourceChanged -= value; + if (provider.AllowDrawableLookup(component)) + return skin.GetDrawableComponent(component); + + return null; } - public ISkin FindProvider(Func lookupFunction) => - provider.FindProvider(lookupFunction); + public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) + { + if (provider.AllowTextureLookup(componentName)) + return skin.GetTexture(componentName, wrapModeS, wrapModeT); + + return null; + } + + public ISample GetSample(ISampleInfo sampleInfo) + { + if (provider.AllowSampleLookup(sampleInfo)) + return skin.GetSample(sampleInfo); + + return null; + } + + public IBindable GetConfig(TLookup lookup) + { + switch (lookup) + { + case GlobalSkinColours _: + case SkinCustomColourLookup _: + if (provider.AllowColourLookup) + return skin.GetConfig(lookup); + + break; + + default: + if (provider.AllowConfigurationLookup) + return skin.GetConfig(lookup); + + break; + } + + return null; + } } } } From 59eda70c12204f0ed71a551d711d1bb7aa1adbb3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 18:39:50 +0900 Subject: [PATCH 044/457] Seek to the least most-recent frame instead --- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index e401c45933..1ea63e454a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -133,11 +133,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private void onReadyToStart() { + // Seek the master clock to the gameplay time. + // This is chosen as the first available frame in the players' replays, which matches the seek by each individual SpectatorPlayer. var startTime = instances.Where(i => i.Score != null) .SelectMany(i => i.Score.Replay.Frames) .Select(f => f.Time) .DefaultIfEmpty(0) - .Max(); + .Min(); masterClockContainer.Seek(startTime); masterClockContainer.Start(); From e59beffc4e7057254c4790a3b9112c5e6c09f3ff Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 12:44:25 +0300 Subject: [PATCH 045/457] Forward all base transformer lookup methods to `Skin` --- osu.Game/Skinning/LegacySkinTransformer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index fedd63c7de..92b7a04dee 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -30,7 +30,7 @@ namespace osu.Game.Skinning Skin = skin ?? throw new ArgumentNullException(nameof(skin)); } - public abstract Drawable GetDrawableComponent(ISkinComponent component); + public virtual Drawable GetDrawableComponent(ISkinComponent component) => Skin.GetDrawableComponent(component); public Texture GetTexture(string componentName) => GetTexture(componentName, default, default); @@ -49,6 +49,6 @@ namespace osu.Game.Skinning return Skin.GetSample(sampleInfo); } - public abstract IBindable GetConfig(TLookup lookup); + public virtual IBindable GetConfig(TLookup lookup) => Skin.GetConfig(lookup); } } From fbb856d84bfdfb2e5c4c5ee3fbfde90302864000 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 12:44:44 +0300 Subject: [PATCH 046/457] Call `base` when overriding lookup methods Rather than arbitrarily accessing `Skin` here and there. --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 8 ++++---- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 12 ++++++------ .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 12 ++++++------ .../Skinning/Legacy/TaikoLegacySkinTransformer.cs | 12 +++--------- 4 files changed, 19 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index a5a1d1504f..287ed1b4c7 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy switch (targetComponent.Target) { case SkinnableTarget.MainHUDComponents: - var components = Skin.GetDrawableComponent(component) as SkinnableTargetComponentsContainer; + var components = base.GetDrawableComponent(component) as SkinnableTargetComponentsContainer; if (providesComboCounter && components != null) { @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy } } - return Skin.GetDrawableComponent(component); + return base.GetDrawableComponent(component); } public override IBindable GetConfig(TLookup lookup) @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy switch (lookup) { case CatchSkinColour colour: - var result = (Bindable)Skin.GetConfig(new SkinCustomColourLookup(colour)); + var result = (Bindable)base.GetConfig(new SkinCustomColourLookup(colour)); if (result == null) return null; @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy return (IBindable)result; } - return Skin.GetConfig(lookup); + return base.GetConfig(lookup); } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 7d4d303bc9..814a737034 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -63,11 +63,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { this.beatmap = (ManiaBeatmap)beatmap; - isLegacySkin = new Lazy(() => skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); + isLegacySkin = new Lazy(() => GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); hasKeyTexture = new Lazy(() => { var keyImage = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value ?? "mania-key1"; - return skin.GetAnimation(keyImage, true, true) != null; + return this.GetAnimation(keyImage, true, true) != null; }); } @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy break; } - return Skin.GetDrawableComponent(component); + return base.GetDrawableComponent(component); } private Drawable getResult(HitResult result) @@ -142,15 +142,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered) return new SampleVirtual(); - return Skin.GetSample(sampleInfo); + return base.GetSample(sampleInfo); } public override IBindable GetConfig(TLookup lookup) { if (lookup is ManiaSkinConfigurationLookup maniaLookup) - return Skin.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn)); + return base.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn)); - return Skin.GetConfig(lookup); + return base.GetConfig(lookup); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 3ad3b7d30b..41b0a88f11 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy public OsuLegacySkinTransformer(ISkin skin) : base(skin) { - hasHitCircle = new Lazy(() => Skin.GetTexture("hitcircle") != null); + hasHitCircle = new Lazy(() => GetTexture("hitcircle") != null); } public override Drawable GetDrawableComponent(ISkinComponent component) @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } } - return Skin.GetDrawableComponent(component); + return base.GetDrawableComponent(component); } public override IBindable GetConfig(TLookup lookup) @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy switch (lookup) { case OsuSkinColour colour: - return Skin.GetConfig(new SkinCustomColourLookup(colour)); + return base.GetConfig(new SkinCustomColourLookup(colour)); case OsuSkinConfiguration osuLookup: switch (osuLookup) @@ -133,14 +133,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy case OsuSkinConfiguration.HitCircleOverlayAboveNumber: // See https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D // HitCircleOverlayAboveNumer (with typo) should still be supported for now. - return Skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? - Skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); + return base.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? + base.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); } break; } - return Skin.GetConfig(lookup); + return base.GetConfig(lookup); } } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index 0122f9a1cd..a3ecbbc436 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using osu.Framework.Audio.Sample; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Audio; using osu.Game.Rulesets.Scoring; @@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public TaikoLegacySkinTransformer(ISkin skin) : base(skin) { - hasExplosion = new Lazy(() => Skin.GetTexture(getHitName(TaikoSkinComponents.TaikoExplosionGreat)) != null); + hasExplosion = new Lazy(() => GetTexture(getHitName(TaikoSkinComponents.TaikoExplosionGreat)) != null); } public override Drawable GetDrawableComponent(ISkinComponent component) @@ -50,7 +49,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy case TaikoSkinComponents.CentreHit: case TaikoSkinComponents.RimHit: - if (GetTexture("taikohitcircle") != null) return new LegacyHit(taikoComponent.Component); @@ -85,7 +83,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy return null; case TaikoSkinComponents.TaikoExplosionMiss: - var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false); if (missSprite != null) return new LegacyHitExplosion(missSprite); @@ -94,7 +91,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy case TaikoSkinComponents.TaikoExplosionOk: case TaikoSkinComponents.TaikoExplosionGreat: - var hitName = getHitName(taikoComponent.Component); var hitSprite = this.GetAnimation(hitName, true, false); @@ -126,7 +122,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy } } - return Skin.GetDrawableComponent(component); + return base.GetDrawableComponent(component); } private string getHitName(TaikoSkinComponents component) @@ -149,13 +145,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public override ISample GetSample(ISampleInfo sampleInfo) { if (sampleInfo is HitSampleInfo hitSampleInfo) - return Skin.GetSample(new LegacyTaikoSampleInfo(hitSampleInfo)); + return base.GetSample(new LegacyTaikoSampleInfo(hitSampleInfo)); return base.GetSample(sampleInfo); } - public override IBindable GetConfig(TLookup lookup) => Skin.GetConfig(lookup); - private class LegacyTaikoSampleInfo : HitSampleInfo { public LegacyTaikoSampleInfo(HitSampleInfo sampleInfo) From f20146d446d492ef9a1fa2036db0bc2dc27108b4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 12:58:38 +0300 Subject: [PATCH 047/457] Fix potentially adding null skin sources --- osu.Game/Skinning/SkinProvidingContainer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index c9bb3a6ec4..315571e79b 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -51,10 +51,11 @@ namespace osu.Game.Skinning /// /// Constructs a new initialised with a single skin source. /// - public SkinProvidingContainer(ISkin skin) + public SkinProvidingContainer([CanBeNull] ISkin skin) : this() { - SkinSources.Add(skin); + if (skin != null) + SkinSources.Add(skin); } /// From 0a8daab4f78e369481e4bff7c382e2bd845386fd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 19:15:53 +0900 Subject: [PATCH 048/457] Pause master clock when too far ahead --- .../TestSceneMultiSpectatorScreen.cs | 17 +++++++++++++++ .../Spectate/CatchUpSyncManager.cs | 21 ++++++++++++++++--- .../Multiplayer/Spectate/ISyncManager.cs | 10 +++++++-- .../Multiplayer/Spectate/MasterClockState.cs | 18 ++++++++++++++++ .../Spectate/MultiSpectatorScreen.cs | 10 +++++++++ 5 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MasterClockState.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 35d096a92e..18eb7e1c1e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -103,6 +103,23 @@ namespace osu.Game.Tests.Visual.Multiplayer AddWaitStep("wait a bit", 20); } + [Test] + public void TestTimeDoesNotProgressWhileAllPlayersPaused() + { + start(new[] { PLAYER_1_ID, PLAYER_2_ID }); + loadSpectateScreen(); + + sendFrames(PLAYER_1_ID, 20); + sendFrames(PLAYER_2_ID, 10); + + checkPaused(PLAYER_2_ID, true); + checkPausedInstant(PLAYER_1_ID, false); + AddAssert("master clock still running", () => this.ChildrenOfType().Single().IsRunning); + + checkPaused(PLAYER_1_ID, true); + AddUntilStep("master clock paused", () => !this.ChildrenOfType().Single().IsRunning); + } + [Test] public void TestPlayersMustStartSimultaneously() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index 0f7466e44b..781123f5bb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Timing; @@ -29,18 +30,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public const double MAXIMUM_START_DELAY = 15000; + public event Action ReadyToStart; + /// /// The master clock which is used to control the timing of all player clocks clocks. /// public IAdjustableClock MasterClock { get; } - public event Action ReadyToStart; + public IBindable MasterState => masterState; /// /// The player clocks. /// private readonly List playerClocks = new List(); + private readonly Bindable masterState = new Bindable(); + private bool hasStarted; private double? firstStartAttemptTime; @@ -65,7 +70,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate return; } - updateCatchup(); + updatePlayerCatchup(); + updateMasterState(); } /// @@ -105,7 +111,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// /// Updates the catchup states of all player clocks clocks. /// - private void updateCatchup() + private void updatePlayerCatchup() { for (int i = 0; i < playerClocks.Count; i++) { @@ -141,5 +147,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } } } + + /// + /// Updates the state of the master clock. + /// + private void updateMasterState() + { + bool anyInSync = playerClocks.Any(s => !s.IsCatchingUp); + masterState.Value = anyInSync ? MasterClockState.Synchronised : MasterClockState.TooFarAhead; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs index f59c1a75f7..3c644ccb78 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate @@ -11,15 +12,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public interface ISyncManager { + /// + /// An event which is invoked when gameplay is ready to start. + /// + event Action ReadyToStart; + /// /// The master clock which player clocks should synchronise to. /// IAdjustableClock MasterClock { get; } /// - /// An event which is invoked when gameplay is ready to start. + /// An event which is invoked when the state of is changed. /// - event Action ReadyToStart; + IBindable MasterState { get; } /// /// Adds an to manage. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MasterClockState.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MasterClockState.cs new file mode 100644 index 0000000000..8982d1669d --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MasterClockState.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. + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public enum MasterClockState + { + /// + /// The master clock is synchronised with at least one player clock. + /// + Synchronised, + + /// + /// The master clock is too far ahead of any player clock and needs to slow down. + /// + TooFarAhead + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 1ea63e454a..ee1968aa5f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.Multiplayer; @@ -103,6 +104,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate }, leaderboardContainer.Add); syncManager.ReadyToStart += onReadyToStart; + syncManager.MasterState.BindValueChanged(onMasterStateChanged, true); } protected override void LoadComplete() @@ -145,6 +147,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate masterClockContainer.Start(); } + private void onMasterStateChanged(ValueChangedEvent state) + { + if (state.NewValue == MasterClockState.Synchronised) + masterClockContainer.Start(); + else + masterClockContainer.Stop(); + } + protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) { } From 9f163f7f20ca155ab331d63d2fae99fdb1c2591d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 19:23:25 +0900 Subject: [PATCH 049/457] Use switch statement to be more explicit about state --- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index ee1968aa5f..013e5551cf 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -149,10 +149,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private void onMasterStateChanged(ValueChangedEvent state) { - if (state.NewValue == MasterClockState.Synchronised) - masterClockContainer.Start(); - else - masterClockContainer.Stop(); + switch (state.NewValue) + { + case MasterClockState.Synchronised: + masterClockContainer.Start(); + break; + + case MasterClockState.TooFarAhead: + masterClockContainer.Stop(); + break; + } } protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) From 108a3deb27382b833f297abf79e63a53e8da1b4d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 16:26:53 +0300 Subject: [PATCH 050/457] Also handle null `Ruleset.CreateLegacySkinProvider` values Let's just go this way for now, maybe it's a better choice to always create transformers and disallow null, but it's too much work and out of scope at this point --- .../Skinning/RulesetSkinProvidingContainer.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index c57522726d..b6a3bd7cda 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Skinning Ruleset = ruleset; Beatmap = beatmap; - InternalChild = new BeatmapSkinProvidingContainer(beatmapSkin == null ? null : ruleset.CreateLegacySkinProvider(beatmapSkin, beatmap)) + InternalChild = new BeatmapSkinProvidingContainer(GetRulesetTransformedSkin(beatmapSkin)) { Child = Content = new Container { @@ -54,13 +54,25 @@ namespace osu.Game.Skinning { SkinSources.Clear(); - SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.CurrentSkin.Value, Beatmap)); + SkinSources.Add(GetRulesetTransformedSkin(skinManager.CurrentSkin.Value)); // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. if (skinManager.CurrentSkin.Value is LegacySkin) - SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultLegacySkin, Beatmap)); + SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultLegacySkin)); - SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultSkin, Beatmap)); + SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultSkin)); + } + + protected ISkin GetRulesetTransformedSkin(ISkin skin) + { + if (skin == null) + return null; + + var rulesetTransformed = Ruleset.CreateLegacySkinProvider(skin, Beatmap); + if (rulesetTransformed != null) + return rulesetTransformed; + + return skin; } } } From d6d87e1975b3cd6145328e2849f358606acc17c9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 17:35:32 +0300 Subject: [PATCH 051/457] Move collection change bind to LoadComplete Best practice anyways --- osu.Game/Skinning/SkinProvidingContainer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 315571e79b..ac1b4d0395 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -65,6 +65,11 @@ namespace osu.Game.Skinning protected SkinProvidingContainer() { RelativeSizeAxes = Axes.Both; + } + + protected override void LoadComplete() + { + base.LoadComplete(); SkinSources.BindCollectionChanged(((_, args) => { From b6947c25ec5d5c3c40e50cf4ad7e3450009a8a68 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 17:55:07 +0300 Subject: [PATCH 052/457] Fix potentially adding the same skin multiple times --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index b6a3bd7cda..8a27899e89 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -57,10 +57,11 @@ namespace osu.Game.Skinning SkinSources.Add(GetRulesetTransformedSkin(skinManager.CurrentSkin.Value)); // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. - if (skinManager.CurrentSkin.Value is LegacySkin) + if (skinManager.CurrentSkin.Value is LegacySkin && skinManager.CurrentSkin.Value != skinManager.DefaultLegacySkin) SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultLegacySkin)); - SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultSkin)); + if (skinManager.CurrentSkin.Value != skinManager.DefaultSkin) + SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultSkin)); } protected ISkin GetRulesetTransformedSkin(ISkin skin) From 8de0d33c5a36b8b9e9af4c035eb9a21caa25d3cf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 17:59:28 +0300 Subject: [PATCH 053/457] Revert "Move collection change bind to LoadComplete" This reverts commit d6d87e1975b3cd6145328e2849f358606acc17c9. Actually that broke things due to the "disableable" instances not added early enough, revert for now. --- osu.Game/Skinning/SkinProvidingContainer.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index ac1b4d0395..315571e79b 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -65,11 +65,6 @@ namespace osu.Game.Skinning protected SkinProvidingContainer() { RelativeSizeAxes = Axes.Both; - } - - protected override void LoadComplete() - { - base.LoadComplete(); SkinSources.BindCollectionChanged(((_, args) => { From 521077b7148eb896792ed5c1a451074ab8957746 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Jun 2021 17:44:18 +0900 Subject: [PATCH 054/457] Make `getRulesetTransformedSkin` private --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 8a27899e89..1953bd499b 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Skinning Ruleset = ruleset; Beatmap = beatmap; - InternalChild = new BeatmapSkinProvidingContainer(GetRulesetTransformedSkin(beatmapSkin)) + InternalChild = new BeatmapSkinProvidingContainer(getRulesetTransformedSkin(beatmapSkin)) { Child = Content = new Container { @@ -54,17 +54,17 @@ namespace osu.Game.Skinning { SkinSources.Clear(); - SkinSources.Add(GetRulesetTransformedSkin(skinManager.CurrentSkin.Value)); + SkinSources.Add(getRulesetTransformedSkin(skinManager.CurrentSkin.Value)); // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. if (skinManager.CurrentSkin.Value is LegacySkin && skinManager.CurrentSkin.Value != skinManager.DefaultLegacySkin) - SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultLegacySkin)); + SkinSources.Add(getRulesetTransformedSkin(skinManager.DefaultLegacySkin)); if (skinManager.CurrentSkin.Value != skinManager.DefaultSkin) - SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultSkin)); + SkinSources.Add(getRulesetTransformedSkin(skinManager.DefaultSkin)); } - protected ISkin GetRulesetTransformedSkin(ISkin skin) + private ISkin getRulesetTransformedSkin(ISkin skin) { if (skin == null) return null; From 5ebf570ec4cdde860928b5c42da17695e3185d49 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Jun 2021 16:48:30 +0300 Subject: [PATCH 055/457] Revert `GetRulesetTransformedSkin` accessibility change This reverts commit 521077b7148eb896792ed5c1a451074ab8957746. Forgot to do it when I made this `protected`, but subclasses in test scenes require this. --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 10 +++++----- osu.Game/Tests/Visual/TestPlayer.cs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 1953bd499b..8a27899e89 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Skinning Ruleset = ruleset; Beatmap = beatmap; - InternalChild = new BeatmapSkinProvidingContainer(getRulesetTransformedSkin(beatmapSkin)) + InternalChild = new BeatmapSkinProvidingContainer(GetRulesetTransformedSkin(beatmapSkin)) { Child = Content = new Container { @@ -54,17 +54,17 @@ namespace osu.Game.Skinning { SkinSources.Clear(); - SkinSources.Add(getRulesetTransformedSkin(skinManager.CurrentSkin.Value)); + SkinSources.Add(GetRulesetTransformedSkin(skinManager.CurrentSkin.Value)); // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. if (skinManager.CurrentSkin.Value is LegacySkin && skinManager.CurrentSkin.Value != skinManager.DefaultLegacySkin) - SkinSources.Add(getRulesetTransformedSkin(skinManager.DefaultLegacySkin)); + SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultLegacySkin)); if (skinManager.CurrentSkin.Value != skinManager.DefaultSkin) - SkinSources.Add(getRulesetTransformedSkin(skinManager.DefaultSkin)); + SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultSkin)); } - private ISkin getRulesetTransformedSkin(ISkin skin) + protected ISkin GetRulesetTransformedSkin(ISkin skin) { if (skin == null) return null; diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index eecf8a2f6e..e1431b0658 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual base.UpdateSkins(); if (skin != null) - SkinSources.Insert(0, Ruleset.CreateLegacySkinProvider(skin, Beatmap)); + SkinSources.Insert(0, GetRulesetTransformedSkin(skin)); } } } From 52ddf08532fd4e31d042ec8a06d226814b769c52 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Jun 2021 16:51:20 +0300 Subject: [PATCH 056/457] Consider not adding legacy skin transformers to non-legacy skins --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 8a27899e89..13664897ac 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -12,7 +12,7 @@ namespace osu.Game.Skinning { /// /// A type of that provides access to the beatmap skin and user skin, - /// each transformed with the ruleset's own skin transformer individually. + /// with each legacy skin source transformed with the ruleset's legacy skin transformer. /// public class RulesetSkinProvidingContainer : SkinProvidingContainer { @@ -66,7 +66,7 @@ namespace osu.Game.Skinning protected ISkin GetRulesetTransformedSkin(ISkin skin) { - if (skin == null) + if (!(skin is LegacySkin)) return null; var rulesetTransformed = Ruleset.CreateLegacySkinProvider(skin, Beatmap); From 74ad6f9117f12c834a96a296c026824f002e3e17 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Jun 2021 17:24:30 +0300 Subject: [PATCH 057/457] Remove default skin from the ruleset skin sources That one doesn't need any changes to it, can be fetched from the `SkinManager` instead. --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 13664897ac..621e80ceb5 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -59,9 +59,6 @@ namespace osu.Game.Skinning // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. if (skinManager.CurrentSkin.Value is LegacySkin && skinManager.CurrentSkin.Value != skinManager.DefaultLegacySkin) SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultLegacySkin)); - - if (skinManager.CurrentSkin.Value != skinManager.DefaultSkin) - SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultSkin)); } protected ISkin GetRulesetTransformedSkin(ISkin skin) From 780388d174c63cb711d31377aaa521a4a3e29eb8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Jun 2021 03:48:25 +0300 Subject: [PATCH 058/457] Fix incorrect return value --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 621e80ceb5..f11acd981a 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Skinning protected ISkin GetRulesetTransformedSkin(ISkin skin) { if (!(skin is LegacySkin)) - return null; + return skin; var rulesetTransformed = Ruleset.CreateLegacySkinProvider(skin, Beatmap); if (rulesetTransformed != null) From 27da3dc75a408b44ddf4dc1a38f4f0a9e55b8a57 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sat, 19 Jun 2021 20:54:24 +0800 Subject: [PATCH 059/457] added supporter-only-filter content --- .../BeatmapListingFilterControl.cs | 10 +- osu.Game/Overlays/BeatmapListingOverlay.cs | 114 +++++++++++++++++- 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 1935a250b7..3436a1b3b2 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -24,6 +24,7 @@ namespace osu.Game.Overlays.BeatmapListing { /// /// Fired when a search finishes. Contains only new items in the case of pagination. + /// Null when non-supporter user used supporter-only filters /// public Action> SearchFinished; @@ -212,7 +213,14 @@ namespace osu.Game.Overlays.BeatmapListing lastResponse = response; getSetsRequest = null; - SearchFinished?.Invoke(sets); + if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) + { + SearchFinished?.Invoke(null); + } + else + { + SearchFinished?.Invoke(sets); + } }; api.Queue(getSetsRequest); diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 5e65cd9488..63b9d3d34a 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -15,7 +15,9 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.Containers; using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Resources.Localisation.Web; @@ -33,6 +35,7 @@ namespace osu.Game.Overlays private Container panelTarget; private FillFlowContainer foundContent; private NotFoundDrawable notFoundContent; + private SupporterRequiredDrawable supporterRequiredContent; private BeatmapListingFilterControl filterControl; public BeatmapListingOverlay() @@ -76,6 +79,7 @@ namespace osu.Game.Overlays { foundContent = new FillFlowContainer(), notFoundContent = new NotFoundDrawable(), + supporterRequiredContent = new SupporterRequiredDrawable(), } } }, @@ -117,6 +121,13 @@ namespace osu.Game.Overlays private void onSearchFinished(List beatmaps) { + // non-supporter user used supporter-only filters + if (beatmaps == null) + { + LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); + return; + } + var newPanels = beatmaps.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, @@ -170,7 +181,7 @@ namespace osu.Game.Overlays { var transform = lastContent.FadeOut(100, Easing.OutQuint); - if (lastContent == notFoundContent) + if (lastContent == notFoundContent || lastContent == supporterRequiredContent) { // not found display may be used multiple times, so don't expire/dispose it. transform.Schedule(() => panelTarget.Remove(lastContent)); @@ -240,6 +251,107 @@ namespace osu.Game.Overlays } } + public class SupporterRequiredDrawable : CompositeDrawable + { + public SupporterRequiredDrawable() + { + RelativeSizeAxes = Axes.X; + Height = 250; + Alpha = 0; + Margin = new MarginPadding { Top = 15 }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + AddInternal(new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Texture = textures.Get(@"Online/supporter-required"), + }, + createSupportRequiredText(), + } + }); + } + + private Drawable createSupportRequiredText() + { + LinkFlowContainer linkFlowContainer; + string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault( + BeatmapsStrings.ListingSearchFiltersRank.ToString(), + "{1}" + ).ToString().Split("{1}"); + + // var titleContainer = new Container + // { + // RelativeSizeAxes = Axes.X, + // Margin = new MarginPadding { Vertical = 5 }, + // Children = new Drawable[] + // { + // linkFlowContainer = new LinkFlowContainer + // { + // Anchor = Anchor.Centre, + // Origin = Anchor.Centre, + // } + // } + // }; + + linkFlowContainer = new LinkFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding + { + Bottom = 10, + } + }; + + linkFlowContainer.AddText( + text[0], + t => + { + t.Font = OsuFont.GetFont(size: 16); + t.Colour = Colour4.White; + } + ); + + linkFlowContainer.AddLink( + BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), + "https://osu.ppy.sh/store/products/supporter-tag", + t => + { + t.Font = OsuFont.GetFont(size: 16); + t.Colour = Colour4.AliceBlue; + } + ); + + linkFlowContainer.AddText( + text[1], + t => + { + t.Font = OsuFont.GetFont(size: 16); + t.Colour = Colour4.White; + } + ); + + return linkFlowContainer; + } + } + private const double time_between_fetches = 500; private double lastFetchDisplayedTime; From 42fdfbb9a1d2504da309c96ca23cdee4829819b2 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sun, 20 Jun 2021 17:17:07 +0800 Subject: [PATCH 060/457] added visual tests --- .../Online/TestSceneBeatmapListingOverlay.cs | 74 +++++++++++++++++++ osu.Game/Overlays/BeatmapListingOverlay.cs | 32 +++----- 2 files changed, 83 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 156d6b744e..86008ce173 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -14,6 +14,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osu.Game.Rulesets; +using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { @@ -58,6 +59,79 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); } + [Test] + public void TestSupporterOnlyFiltersPlaceholder() { + + AddStep("toggle non-supporter", () => + { + // non-supporter user + ((DummyAPIAccess)API).LocalUser.Value = new User + { + Username = API.LocalUser.Value.Username, + Id = API.LocalUser.Value.Id + 1, + IsSupporter = false, + }; + }); + AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); + + AddStep("toggle Random Rank Achieved filter", () => { + overlay.ChildrenOfType().Single().Ranks.Clear(); + Scoring.ScoreRank r = (Scoring.ScoreRank)(TestContext.CurrentContext.Random.NextShort() % 8); + overlay.ChildrenOfType().Single().Ranks.Add(r); + // overlay.ChildrenOfType().Single().Ranks. + }); + + AddUntilStep("supporter-placeholder show", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + + AddStep("Clear Rank Achieved filter", () => { + overlay.ChildrenOfType().Single().Ranks.Clear(); + }); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("toggle Random Played filter", () => { + SearchPlayed r = (SearchPlayed)(TestContext.CurrentContext.Random.NextShort() % 2 + 1); + overlay.ChildrenOfType().Single().Played.Value = r; + }); + + AddUntilStep("supporter-placeholder show", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + + AddStep("Clear Played filter", () => { + overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any; + }); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("toggle supporter", () => + { + // supporter user + ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true; + }); + + AddStep("toggle Random Rank Achieved filter", () => { + overlay.ChildrenOfType().Single().Ranks.Clear(); + Scoring.ScoreRank r = (Scoring.ScoreRank)(TestContext.CurrentContext.Random.NextShort() % 8); + overlay.ChildrenOfType().Single().Ranks.Add(r); + }); + + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Clear Rank Achieved filter", () => { + overlay.ChildrenOfType().Single().Ranks.Clear(); + }); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("toggle Random Played filter", () => { + SearchPlayed r = (SearchPlayed)(TestContext.CurrentContext.Random.NextShort() % 2 + 1); + overlay.ChildrenOfType().Single().Played.Value = r; + }); + + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Clear Played filter", () => { + overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any; + }); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + } + private void fetchFor(params BeatmapSetInfo[] beatmaps) { setsForResponse.Clear(); diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 63b9d3d34a..3dec6de03a 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -181,11 +181,16 @@ namespace osu.Game.Overlays { var transform = lastContent.FadeOut(100, Easing.OutQuint); - if (lastContent == notFoundContent || lastContent == supporterRequiredContent) + if (lastContent == notFoundContent) { // not found display may be used multiple times, so don't expire/dispose it. transform.Schedule(() => panelTarget.Remove(lastContent)); } + else if (lastContent == supporterRequiredContent) + { + // supporter required display may be used multiple times, so don't expire/dispose it. + transform.Schedule(() => panelTarget.Remove(supporterRequiredContent)); + } else { // Consider the case when the new content is smaller than the last content. @@ -256,9 +261,8 @@ namespace osu.Game.Overlays public SupporterRequiredDrawable() { RelativeSizeAxes = Axes.X; - Height = 250; + Height = 225; Alpha = 0; - Margin = new MarginPadding { Top = 15 }; } [BackgroundDependencyLoader] @@ -271,7 +275,6 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), Children = new Drawable[] { new Sprite @@ -290,24 +293,7 @@ namespace osu.Game.Overlays private Drawable createSupportRequiredText() { LinkFlowContainer linkFlowContainer; - string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault( - BeatmapsStrings.ListingSearchFiltersRank.ToString(), - "{1}" - ).ToString().Split("{1}"); - - // var titleContainer = new Container - // { - // RelativeSizeAxes = Axes.X, - // Margin = new MarginPadding { Vertical = 5 }, - // Children = new Drawable[] - // { - // linkFlowContainer = new LinkFlowContainer - // { - // Anchor = Anchor.Centre, - // Origin = Anchor.Centre, - // } - // } - // }; + string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(BeatmapsStrings.ListingSearchFiltersRank.ToString(), "{1}").ToString().Split("{1}"); linkFlowContainer = new LinkFlowContainer { @@ -335,7 +321,7 @@ namespace osu.Game.Overlays t => { t.Font = OsuFont.GetFont(size: 16); - t.Colour = Colour4.AliceBlue; + t.Colour = Colour4.FromHex("#A6C8D9"); } ); From e7aeba8d039cd8b3dfcaad98ed440d1cb4e9f620 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sun, 20 Jun 2021 18:28:43 +0800 Subject: [PATCH 061/457] added more visual tests --- .../Online/TestSceneBeatmapListingOverlay.cs | 153 +++++++++++------- .../BeatmapListingFilterControl.cs | 1 + 2 files changed, 99 insertions(+), 55 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 86008ce173..eebaea545a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -40,6 +40,16 @@ namespace osu.Game.Tests.Visual.Online return true; }; + + AddStep("initialize dummy", () => + { + // non-supporter user + ((DummyAPIAccess)API).LocalUser.Value = new User + { + Username = "TestBot", + Id = API.LocalUser.Value.Id + 1, + }; + }); } [Test] @@ -60,78 +70,95 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSupporterOnlyFiltersPlaceholder() { + public void TestSupporterOnlyFiltersPlaceholderNoBeatmaps() + { + AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); - AddStep("toggle non-supporter", () => - { - // non-supporter user - ((DummyAPIAccess)API).LocalUser.Value = new User - { - Username = API.LocalUser.Value.Username, - Id = API.LocalUser.Value.Id + 1, - IsSupporter = false, - }; - }); - AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); + // test non-supporter on Rank Achieved filter + toggleRandomRankFilter(); + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddStep("toggle Random Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - Scoring.ScoreRank r = (Scoring.ScoreRank)(TestContext.CurrentContext.Random.NextShort() % 8); - overlay.ChildrenOfType().Single().Ranks.Add(r); - // overlay.ChildrenOfType().Single().Ranks. - }); - - AddUntilStep("supporter-placeholder show", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - - AddStep("Clear Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - }); + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("toggle Random Played filter", () => { - SearchPlayed r = (SearchPlayed)(TestContext.CurrentContext.Random.NextShort() % 2 + 1); - overlay.ChildrenOfType().Single().Played.Value = r; - }); + // test non-supporter on Played filter + toggleRandomSupporterOnlyPlayedFilter(); + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("supporter-placeholder show", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - - AddStep("Clear Played filter", () => { - overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any; - }); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("toggle supporter", () => - { - // supporter user - ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true; - }); - - AddStep("toggle Random Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - Scoring.ScoreRank r = (Scoring.ScoreRank)(TestContext.CurrentContext.Random.NextShort() % 8); - overlay.ChildrenOfType().Single().Ranks.Add(r); - }); + AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); + // test supporter on Rank Achieved filter + toggleRandomRankFilter(); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("Clear Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - }); + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("toggle Random Played filter", () => { - SearchPlayed r = (SearchPlayed)(TestContext.CurrentContext.Random.NextShort() % 2 + 1); - overlay.ChildrenOfType().Single().Played.Value = r; - }); - + // test supporter on Played filter + toggleRandomSupporterOnlyPlayedFilter(); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("Clear Played filter", () => { - overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any; - }); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); } + [Test] + public void TestSupporterOnlyFiltersPlaceholderOneBeatmap() + { + AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); + AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); + + // test non-supporter on Rank Achieved filter + toggleRandomRankFilter(); + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + // test non-supporter on Played filter + toggleRandomSupporterOnlyPlayedFilter(); + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); + + // test supporter on Rank Achieved filter + toggleRandomRankFilter(); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + // test supporter on Played filter + toggleRandomSupporterOnlyPlayedFilter(); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + } + + private void fetchFor(params BeatmapSetInfo[] beatmaps) { setsForResponse.Clear(); @@ -141,6 +168,22 @@ namespace osu.Game.Tests.Visual.Online overlay.ChildrenOfType().Single().Query.TriggerChange(); } + private void toggleRandomRankFilter() + { + short r = TestContext.CurrentContext.Random.NextShort(); + AddStep("toggle Random Rank Achieved filter", () => + { + overlay.ChildrenOfType().Single().Ranks.Clear(); + overlay.ChildrenOfType().Single().Ranks.Add((Scoring.ScoreRank)(r % 8)); + }); + } + + private void toggleRandomSupporterOnlyPlayedFilter() + { + short r = TestContext.CurrentContext.Random.NextShort(); + AddStep("toggle Random Played filter", () => overlay.ChildrenOfType().Single().Played.Value = (SearchPlayed)(r % 2 + 1)); + } + private class TestAPIBeatmapSet : APIBeatmapSet { private readonly BeatmapSetInfo beatmapSet; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 3436a1b3b2..7b7f742b73 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -213,6 +213,7 @@ namespace osu.Game.Overlays.BeatmapListing lastResponse = response; getSetsRequest = null; + // check if an non-supporter user used supporter-only filters if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) { SearchFinished?.Invoke(null); From 996503eb2d350aeae8252e9504541c82300da234 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sun, 20 Jun 2021 21:23:54 +0800 Subject: [PATCH 062/457] fixed filter text display, added visual tests --- .../Online/TestSceneBeatmapListingOverlay.cs | 106 ++++++++++++------ .../BeatmapListingFilterControl.cs | 8 +- osu.Game/Overlays/BeatmapListingOverlay.cs | 45 ++++---- 3 files changed, 103 insertions(+), 56 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index eebaea545a..9128f72d6e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -72,45 +72,56 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestSupporterOnlyFiltersPlaceholderNoBeatmaps() { + AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); // test non-supporter on Rank Achieved filter toggleRandomRankFilter(); - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(true, false); AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); // test non-supporter on Played filter toggleRandomSupporterOnlyPlayedFilter(); - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(true, false); AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); + + // test non-supporter on both Rank Achieved and Played filter + toggleRandomRankFilter(); + toggleRandomSupporterOnlyPlayedFilter(); + expectedPlaceholderShown(true, false); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + expectedPlaceholderShown(false, true); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); // test supporter on Rank Achieved filter toggleRandomRankFilter(); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); // test supporter on Played filter toggleRandomSupporterOnlyPlayedFilter(); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); + + // test supporter on both Rank Achieved and Played filter + toggleRandomRankFilter(); + toggleRandomSupporterOnlyPlayedFilter(); + expectedPlaceholderShown(false, true); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + expectedPlaceholderShown(false, true); } [Test] @@ -121,41 +132,51 @@ namespace osu.Game.Tests.Visual.Online // test non-supporter on Rank Achieved filter toggleRandomRankFilter(); - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(true, false); AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); // test non-supporter on Played filter toggleRandomSupporterOnlyPlayedFilter(); - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(true, false); AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); + + // test non-supporter on both Rank Achieved and Played filter + toggleRandomRankFilter(); + toggleRandomSupporterOnlyPlayedFilter(); + expectedPlaceholderShown(true, false); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + expectedPlaceholderShown(false, false); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); // test supporter on Rank Achieved filter toggleRandomRankFilter(); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); // test supporter on Played filter toggleRandomSupporterOnlyPlayedFilter(); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); + + // test supporter on both Rank Achieved and Played filter + toggleRandomRankFilter(); + toggleRandomSupporterOnlyPlayedFilter(); + expectedPlaceholderShown(false, false); + + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + expectedPlaceholderShown(false, false); } @@ -184,6 +205,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("toggle Random Played filter", () => overlay.ChildrenOfType().Single().Played.Value = (SearchPlayed)(r % 2 + 1)); } + private void expectedPlaceholderShown(bool supporterRequiredShown, bool notFoundShown) + { + if (supporterRequiredShown) + { + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } + else + { + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + } + + if (notFoundShown) + { + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } + else + { + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + } + } + private class TestAPIBeatmapSet : APIBeatmapSet { private readonly BeatmapSetInfo beatmapSet; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 7b7f742b73..6e83dc0bf4 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -24,9 +24,9 @@ namespace osu.Game.Overlays.BeatmapListing { /// /// Fired when a search finishes. Contains only new items in the case of pagination. - /// Null when non-supporter user used supporter-only filters + /// Fired with BeatmapListingSearchControl when non-supporter user used supporter-only filters. /// - public Action> SearchFinished; + public Action, BeatmapListingSearchControl> SearchFinished; /// /// Fired when search criteria change. @@ -216,11 +216,11 @@ namespace osu.Game.Overlays.BeatmapListing // check if an non-supporter user used supporter-only filters if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) { - SearchFinished?.Invoke(null); + SearchFinished?.Invoke(sets, searchControl); } else { - SearchFinished?.Invoke(sets); + SearchFinished?.Invoke(sets, null); } }; diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 3dec6de03a..90f1e6932d 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -119,11 +119,17 @@ namespace osu.Game.Overlays private Task panelLoadDelegate; - private void onSearchFinished(List beatmaps) + private void onSearchFinished(List beatmaps, BeatmapListingSearchControl searchControl) { // non-supporter user used supporter-only filters - if (beatmaps == null) + if (searchControl != null) { + // compose filter string + List filtersStrs = new List(); + if (searchControl.Ranks.Any()) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); + if (searchControl.Played.Value != SearchPlayed.Any) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); + supporterRequiredContent.UpdateSupportRequiredText(string.Join(" and ", filtersStrs)); + LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); return; } @@ -258,11 +264,24 @@ namespace osu.Game.Overlays public class SupporterRequiredDrawable : CompositeDrawable { + private LinkFlowContainer linkFlowContainer; + public SupporterRequiredDrawable() { RelativeSizeAxes = Axes.X; Height = 225; Alpha = 0; + + linkFlowContainer = linkFlowContainer = new LinkFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding + { + Bottom = 10, + } + }; } [BackgroundDependencyLoader] @@ -285,27 +304,15 @@ namespace osu.Game.Overlays FillMode = FillMode.Fit, Texture = textures.Get(@"Online/supporter-required"), }, - createSupportRequiredText(), + linkFlowContainer, } }); } - private Drawable createSupportRequiredText() - { - LinkFlowContainer linkFlowContainer; - string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(BeatmapsStrings.ListingSearchFiltersRank.ToString(), "{1}").ToString().Split("{1}"); - - linkFlowContainer = new LinkFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding - { - Bottom = 10, - } - }; + public void UpdateSupportRequiredText(string filtersStr) { + string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(filtersStr, "{1}").ToString().Split("{1}"); + linkFlowContainer.Clear(); linkFlowContainer.AddText( text[0], t => @@ -333,8 +340,6 @@ namespace osu.Game.Overlays t.Colour = Colour4.White; } ); - - return linkFlowContainer; } } From d0a8b748238dfa03b9174db624c34bac0259ad2a Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sun, 20 Jun 2021 21:28:57 +0800 Subject: [PATCH 063/457] fixed filter text order --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 90f1e6932d..ceb033380c 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -126,8 +126,8 @@ namespace osu.Game.Overlays { // compose filter string List filtersStrs = new List(); - if (searchControl.Ranks.Any()) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); if (searchControl.Played.Value != SearchPlayed.Any) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); + if (searchControl.Ranks.Any()) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); supporterRequiredContent.UpdateSupportRequiredText(string.Join(" and ", filtersStrs)); LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); From 5cfd0e32236a5a63eaf4132e6fe9dea22569bf2c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 04:16:58 +0300 Subject: [PATCH 064/457] Remove implicit `LegacySkin` check and refactor anything using it --- .../Skinning/RulesetSkinProvidingContainer.cs | 31 +++++++++++++------ osu.Game/Tests/Visual/TestPlayer.cs | 2 +- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index f11acd981a..bbaeee98d8 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Skinning Ruleset = ruleset; Beatmap = beatmap; - InternalChild = new BeatmapSkinProvidingContainer(GetRulesetTransformedSkin(beatmapSkin)) + InternalChild = new BeatmapSkinProvidingContainer(beatmapSkin is LegacySkin ? GetLegacyRulesetTransformedSkin(beatmapSkin) : beatmapSkin) { Child = Content = new Container { @@ -54,23 +54,34 @@ namespace osu.Game.Skinning { SkinSources.Clear(); - SkinSources.Add(GetRulesetTransformedSkin(skinManager.CurrentSkin.Value)); + // TODO: we also want to insert a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. - // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. - if (skinManager.CurrentSkin.Value is LegacySkin && skinManager.CurrentSkin.Value != skinManager.DefaultLegacySkin) - SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultLegacySkin)); + switch (skinManager.CurrentSkin.Value) + { + case LegacySkin currentLegacySkin: + SkinSources.Add(GetLegacyRulesetTransformedSkin(currentLegacySkin)); + + if (currentLegacySkin != skinManager.DefaultLegacySkin) + SkinSources.Add(GetLegacyRulesetTransformedSkin(skinManager.DefaultLegacySkin)); + + break; + + default: + SkinSources.Add(skinManager.CurrentSkin.Value); + break; + } } - protected ISkin GetRulesetTransformedSkin(ISkin skin) + protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) { - if (!(skin is LegacySkin)) - return skin; + if (legacySkin == null) + return null; - var rulesetTransformed = Ruleset.CreateLegacySkinProvider(skin, Beatmap); + var rulesetTransformed = Ruleset.CreateLegacySkinProvider(legacySkin, Beatmap); if (rulesetTransformed != null) return rulesetTransformed; - return skin; + return legacySkin; } } } diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index e1431b0658..2be5d8ac9f 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual base.UpdateSkins(); if (skin != null) - SkinSources.Insert(0, GetRulesetTransformedSkin(skin)); + SkinSources.Insert(0, skin is LegacySkin ? GetLegacyRulesetTransformedSkin(skin) : skin); } } } From 11b78ad849d333f8cd0ad79267e7ed4506603daf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 04:27:09 +0300 Subject: [PATCH 065/457] Make `TestPlayer` skin assigning logic not flaky --- .../Beatmaps/LegacyBeatmapSkinColourTest.cs | 2 +- osu.Game/Tests/Visual/PlayerTestScene.cs | 2 +- osu.Game/Tests/Visual/TestPlayer.cs | 20 ++++++++++++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs index 1feb3eebbf..347b611579 100644 --- a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Beatmaps ExposedPlayer player = CreateTestPlayer(); - player.Skin = new TestSkin(userHasCustomColours); + player.SetSkin(new TestSkin(userHasCustomColours)); LoadScreen(player); diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index e42a043eec..f5fad895e2 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual } Player = CreatePlayer(ruleset); - Player.Skin = GetPlayerSkin(); + Player.SetSkin(GetPlayerSkin()); LoadScreen(Player); } diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 2be5d8ac9f..19fd7068b9 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -1,7 +1,9 @@ // 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.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -22,8 +24,6 @@ namespace osu.Game.Tests.Visual /// public class TestPlayer : Player { - public ISkin Skin { get; set; } - protected override bool PauseOnFocusLost { get; } public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; @@ -81,8 +81,22 @@ namespace osu.Game.Tests.Visual ScoreProcessor.NewJudgement += r => Results.Add(r); } + public ISkin Skin { get; private set; } + + private TestSkinProvidingContainer rulesetSkinProvider; + + internal void SetSkin(ISkin skin) + { + Debug.Assert(rulesetSkinProvider == null); + + if (Skin != null) + throw new InvalidOperationException("A skin has already been set."); + + Skin = skin; + } + protected override RulesetSkinProvidingContainer CreateRulesetSkinProvider(Ruleset ruleset, IBeatmap beatmap, ISkin beatmapSkin) - => new TestSkinProvidingContainer(Skin, ruleset, beatmap, beatmapSkin); + => rulesetSkinProvider = new TestSkinProvidingContainer(Skin, ruleset, beatmap, beatmapSkin); private class TestSkinProvidingContainer : RulesetSkinProvidingContainer { From 0707642b76d94b81f3ef061bc6f207f7edf85764 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 14:25:20 +0800 Subject: [PATCH 066/457] fixed SupporterRequiredDrawable --- osu.Game/Overlays/BeatmapListingOverlay.cs | 89 +++++++++------------- 1 file changed, 34 insertions(+), 55 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index ceb033380c..5ddad1c9fc 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -121,25 +122,20 @@ namespace osu.Game.Overlays private void onSearchFinished(List beatmaps, BeatmapListingSearchControl searchControl) { - // non-supporter user used supporter-only filters - if (searchControl != null) - { - // compose filter string - List filtersStrs = new List(); - if (searchControl.Played.Value != SearchPlayed.Any) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); - if (searchControl.Ranks.Any()) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); - supporterRequiredContent.UpdateSupportRequiredText(string.Join(" and ", filtersStrs)); - - LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); - return; - } - var newPanels = beatmaps.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }); + // non-supporter user used supporter-only filters + if (searchControl != null) + { + supporterRequiredContent.UpdateText(searchControl.Played.Value != SearchPlayed.Any, searchControl.Ranks.Any()); + LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); + return; + } + if (filterControl.CurrentPage == 0) { //No matches case @@ -262,26 +258,16 @@ namespace osu.Game.Overlays } } + // using string literals as there's no proper processing for LocalizeStrings yet public class SupporterRequiredDrawable : CompositeDrawable { - private LinkFlowContainer linkFlowContainer; + private OsuSpriteText supporterRequiredText; public SupporterRequiredDrawable() { RelativeSizeAxes = Axes.X; Height = 225; Alpha = 0; - - linkFlowContainer = linkFlowContainer = new LinkFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding - { - Bottom = 10, - } - }; } [BackgroundDependencyLoader] @@ -304,42 +290,35 @@ namespace osu.Game.Overlays FillMode = FillMode.Fit, Texture = textures.Get(@"Online/supporter-required"), }, - linkFlowContainer, + supporterRequiredText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Bottom = 10 }, + }, + createSupporterTagLink(), } }); } - public void UpdateSupportRequiredText(string filtersStr) { - string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(filtersStr, "{1}").ToString().Split("{1}"); + public void UpdateText(bool playedFilter, bool rankFilter) { + List filters = new List(); + if (playedFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); + if (rankFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); + supporterRequiredText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); + } - linkFlowContainer.Clear(); - linkFlowContainer.AddText( - text[0], - t => - { - t.Font = OsuFont.GetFont(size: 16); - t.Colour = Colour4.White; - } - ); + public Drawable createSupporterTagLink() { + LinkFlowContainer supporterTagLink = new LinkFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Bottom = 10 }, + }; - linkFlowContainer.AddLink( - BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), - "https://osu.ppy.sh/store/products/supporter-tag", - t => - { - t.Font = OsuFont.GetFont(size: 16); - t.Colour = Colour4.FromHex("#A6C8D9"); - } - ); - - linkFlowContainer.AddText( - text[1], - t => - { - t.Font = OsuFont.GetFont(size: 16); - t.Colour = Colour4.White; - } - ); + supporterTagLink.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), Online.Chat.LinkAction.External, "https://osu.ppy.sh/store/products/supporter-tag"); + return supporterTagLink; } } From 33674563041102d02722fd98366eab6d3c48aa92 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 14:30:54 +0800 Subject: [PATCH 067/457] fixed SupporterRequiredDrawable style --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 5ddad1c9fc..42a7253276 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -294,6 +294,8 @@ namespace osu.Game.Overlays { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 16), + Colour = Colour4.White, Margin = new MarginPadding { Bottom = 10 }, }, createSupporterTagLink(), From b42aedeb8171352bb56ed5334b0a347f43865335 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 14:43:54 +0800 Subject: [PATCH 068/457] fixed code style --- osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 9128f72d6e..2146ea333a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -179,7 +179,6 @@ namespace osu.Game.Tests.Visual.Online expectedPlaceholderShown(false, false); } - private void fetchFor(params BeatmapSetInfo[] beatmaps) { setsForResponse.Clear(); From 22cc1e14527c14bb2aa16052cdd5bb3ce27072dc Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 15:31:47 +0800 Subject: [PATCH 069/457] fixed code style base on code analysis --- osu.Game/Overlays/BeatmapListingOverlay.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 42a7253276..407b737db5 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -280,7 +279,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Children = new Drawable[] + Children = new[] { new Sprite { @@ -303,14 +302,16 @@ namespace osu.Game.Overlays }); } - public void UpdateText(bool playedFilter, bool rankFilter) { + public void UpdateText(bool playedFilter, bool rankFilter) + { List filters = new List(); if (playedFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); if (rankFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); supporterRequiredText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); } - public Drawable createSupporterTagLink() { + private Drawable createSupporterTagLink() + { LinkFlowContainer supporterTagLink = new LinkFlowContainer { Anchor = Anchor.Centre, From b162da5ee0265726f99b8c847b7b31512cae0e88 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 15:47:47 +0800 Subject: [PATCH 070/457] minor code change --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 407b737db5..578e70e630 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -320,7 +320,7 @@ namespace osu.Game.Overlays Margin = new MarginPadding { Bottom = 10 }, }; - supporterTagLink.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), Online.Chat.LinkAction.External, "https://osu.ppy.sh/store/products/supporter-tag"); + supporterTagLink.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), "https://osu.ppy.sh/store/products/supporter-tag"); return supporterTagLink; } } From 36e459e97e7b516f2c95a481c1561488226289a5 Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Mon, 21 Jun 2021 13:42:15 -0700 Subject: [PATCH 071/457] Use margin instead of padding --- osu.Game/Overlays/OverlayStreamItem.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/OverlayStreamItem.cs b/osu.Game/Overlays/OverlayStreamItem.cs index 7f8559e7de..cd1391a3d8 100644 --- a/osu.Game/Overlays/OverlayStreamItem.cs +++ b/osu.Game/Overlays/OverlayStreamItem.cs @@ -39,9 +39,9 @@ namespace osu.Game.Overlays protected OverlayStreamItem(T value) : base(value) { - Height = 60; - Width = 100; - Padding = new MarginPadding(5); + Height = 50; + Width = 90; + Margin = new MarginPadding(5); } [BackgroundDependencyLoader] From ebe0d43790a7d793dd6db2cb2ae8e1ce57d6d234 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 02:51:00 +0300 Subject: [PATCH 072/457] Add ability to disallow falling back to parent skins --- osu.Game/Skinning/SkinProvidingContainer.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 315571e79b..4435d924c2 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -38,6 +38,11 @@ namespace osu.Game.Skinning [CanBeNull] private ISkinSource fallbackSource; + /// + /// Whether falling back to parent s is allowed in this container. + /// + protected virtual bool AllowFallingBackToParent => true; + protected virtual bool AllowDrawableLookup(ISkinComponent component) => true; protected virtual bool AllowTextureLookup(string componentName) => true; @@ -180,9 +185,12 @@ namespace osu.Game.Skinning { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - fallbackSource = dependencies.Get(); - if (fallbackSource != null) - fallbackSource.SourceChanged += OnSourceChanged; + if (AllowFallingBackToParent) + { + fallbackSource = dependencies.Get(); + if (fallbackSource != null) + fallbackSource.SourceChanged += OnSourceChanged; + } dependencies.CacheAs(this); From d53a43cf3c65dbc1ede229e1dffa56f32f6a87da Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 02:52:37 +0300 Subject: [PATCH 073/457] Isolate `RulesetSkinProvidingContainer` from falling back to parent skin sources For simplicity of lookup order, and which sources are used for the lookup. --- .../Skinning/RulesetSkinProvidingContainer.cs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index bbaeee98d8..54d366b2f4 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -7,18 +7,26 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.UI; namespace osu.Game.Skinning { /// - /// A type of that provides access to the beatmap skin and user skin, - /// with each legacy skin source transformed with the ruleset's legacy skin transformer. + /// A type of specialized for and other gameplay-related components. + /// Providing access to the skin sources and the beatmap skin each surrounded with the ruleset legacy skin transformer. + /// While also limiting lookups from falling back to any parent s out of this container. /// public class RulesetSkinProvidingContainer : SkinProvidingContainer { protected readonly Ruleset Ruleset; protected readonly IBeatmap Beatmap; + /// + /// This container already re-exposes all skin sources in a ruleset-usable form. + /// Therefore disallow falling back to any parent any further. + /// + protected override bool AllowFallingBackToParent => false; + protected override Container Content { get; } public RulesetSkinProvidingContainer(Ruleset ruleset, IBeatmap beatmap, [CanBeNull] ISkin beatmapSkin) @@ -42,12 +50,7 @@ namespace osu.Game.Skinning private void load() { UpdateSkins(); - } - - protected override void OnSourceChanged() - { - UpdateSkins(); - base.OnSourceChanged(); + skinManager.SourceChanged += UpdateSkins; } protected virtual void UpdateSkins() @@ -83,5 +86,13 @@ namespace osu.Game.Skinning return legacySkin; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (skinManager != null) + skinManager.SourceChanged -= UpdateSkins; + } } } From 97dbc7f20ecc19ee0d00516a51618212b28bc404 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 02:54:34 +0300 Subject: [PATCH 074/457] Add back `SkinManager.DefaultSkin` to the ruleset skin lookup sources --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 54d366b2f4..8113597dee 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -73,6 +73,8 @@ namespace osu.Game.Skinning SkinSources.Add(skinManager.CurrentSkin.Value); break; } + + SkinSources.Add(skinManager.DefaultSkin); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) From 9e5bb146d3aaa2936c07f2299a58ce36d56d82a4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 03:06:39 +0300 Subject: [PATCH 075/457] Add xmldoc to `SkinManager` The `` part comes from `BeatmapManager`, which I believe works correctly here as well, as this does handle the "storage and retrieval" of skins. --- osu.Game/Skinning/SkinManager.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 7acc52809f..1f10177a9e 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -30,6 +30,13 @@ using osu.Game.IO.Archives; namespace osu.Game.Skinning { + /// + /// Handles the storage and retrieval of s. + /// + /// + /// This is also exposed and cached as on a game-wide level for general components across the game. + /// Lookups from gameplay components are instead covered by , and are never hit here. + /// [ExcludeFromDynamicCompile] public class SkinManager : ArchiveModelManager, ISkinSource, IStorageResourceProvider { From 627c857da8d0293625e9600c2e5176c333c0f0ee Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 03:44:32 +0300 Subject: [PATCH 076/457] Propagate `SourceChanged` events from `SkinManager` down in the ruleset skin container --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 8113597dee..4b3c3881c2 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -50,7 +50,13 @@ namespace osu.Game.Skinning private void load() { UpdateSkins(); - skinManager.SourceChanged += UpdateSkins; + skinManager.SourceChanged += OnSourceChanged; + } + + protected override void OnSourceChanged() + { + UpdateSkins(); + base.OnSourceChanged(); } protected virtual void UpdateSkins() From caa90bccc6c92ecc399900ad4774f308002c1260 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 03:45:43 +0300 Subject: [PATCH 077/457] Fix default skin potentially added twice in `RulesetSkinProvidingContainer` --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 4b3c3881c2..88cf70fa18 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -80,7 +80,8 @@ namespace osu.Game.Skinning break; } - SkinSources.Add(skinManager.DefaultSkin); + if (skinManager.CurrentSkin.Value != skinManager.DefaultSkin) + SkinSources.Add(skinManager.DefaultSkin); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) From ec040ff3fc80e3f0374245564f16279caf547b3f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 05:05:41 +0300 Subject: [PATCH 078/457] Fix leak due to not properly unbinding `SourceChanged` event on disposal --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 88cf70fa18..b07d3f5199 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Skinning base.Dispose(isDisposing); if (skinManager != null) - skinManager.SourceChanged -= UpdateSkins; + skinManager.SourceChanged -= OnSourceChanged; } } } From 00b4cf1829cd00af1a07c567a25fa9dbda7e0e5a Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 21 Jun 2021 20:20:43 -0700 Subject: [PATCH 079/457] Handle sub screen `OnExiting` logic on main screen --- osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index e418d36d40..ceee002c6e 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -240,13 +240,15 @@ namespace osu.Game.Screens.OnlinePlay public override bool OnExiting(IScreen next) { + if (screenStack.CurrentScreen?.OnExiting(next) == true) + return true; + RoomManager.PartRoom(); waves.Hide(); this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut(); - screenStack.CurrentScreen?.OnExiting(next); base.OnExiting(next); return false; } From 9bcd1e69224d4a7943ba8203765c59b50c5b6ed3 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 21 Jun 2021 20:22:18 -0700 Subject: [PATCH 080/457] Move confirm dialog logic to `OnExiting` --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index f9b3549f3c..d7025c2550 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -305,6 +305,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return true; } + return base.OnBackButton(); + } + + public override bool OnExiting(IScreen next) + { + if (client.Room == null) + { + // room has not been created yet; exit immediately. + return base.OnExiting(next); + } + if (!exitConfirmed && dialogOverlay != null) { dialogOverlay.Push(new ConfirmDialog("Are you sure you want to leave this multiplayer match?", () => @@ -316,7 +327,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return true; } - return base.OnBackButton(); + return base.OnExiting(next); } private ModSettingChangeTracker modSettingChangeTracker; From db860980622bfbc29db9c66f70373dd4e24132da Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 21 Jun 2021 20:23:11 -0700 Subject: [PATCH 081/457] Fix dialog not closing after spamming OS window close --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index d7025c2550..4b8c4422ec 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -318,11 +318,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!exitConfirmed && dialogOverlay != null) { - dialogOverlay.Push(new ConfirmDialog("Are you sure you want to leave this multiplayer match?", () => + if (dialogOverlay.CurrentDialog is ConfirmDialog confirmDialog) + confirmDialog.PerformOkAction(); + else { - exitConfirmed = true; - this.Exit(); - })); + dialogOverlay.Push(new ConfirmDialog("Are you sure you want to leave this multiplayer match?", () => + { + exitConfirmed = true; + this.Exit(); + })); + } return true; } From 2cdbada87e2bfe8e8bafca51c37242b379243b54 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 21 Jun 2021 20:24:22 -0700 Subject: [PATCH 082/457] Fix screen breadcrumb control updating on click --- .../Graphics/UserInterface/ScreenBreadcrumbControl.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs index e85525b2f8..d7bd7d7e01 100644 --- a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs @@ -3,6 +3,7 @@ using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; namespace osu.Game.Graphics.UserInterface @@ -19,8 +20,13 @@ namespace osu.Game.Graphics.UserInterface if (stack.CurrentScreen != null) onPushed(null, stack.CurrentScreen); + } - Current.ValueChanged += current => current.NewValue.MakeCurrent(); + protected override void SelectTab(TabItem tab) + { + // override base method to prevent current item from being changed on click. + // depend on screen push/exit to change current item instead. + tab.Value.MakeCurrent(); } private void onPushed(IScreen lastScreen, IScreen newScreen) From f89c154e1810b54b14f48522c1c5c2a6b5bcb546 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 22 Jun 2021 12:24:25 +0700 Subject: [PATCH 083/457] change `GetFontSizeByLevel` to return actual font size --- .../Containers/Markdown/OsuMarkdownHeading.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs index 40eb4cad15..a07216c32e 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs @@ -28,27 +28,25 @@ namespace osu.Game.Graphics.Containers.Markdown // Reference for this font size // https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/bem/osu-md.less#L9 // https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/variables.less#L161 - const float base_font_size = 14; - switch (level) { case 1: - return 30 / base_font_size; + return 30; case 2: - return 26 / base_font_size; + return 26; case 3: - return 20 / base_font_size; + return 20; case 4: - return 18 / base_font_size; + return 18; case 5: - return 16 / base_font_size; + return 16; default: - return 1; + return 14; } } From 5c3129f1a2e83a58e2c19a6018fc02df9544530c Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 22 Jun 2021 12:24:51 +0700 Subject: [PATCH 084/457] add font size in `HeadingTextFlowContainer` --- .../Graphics/Containers/Markdown/OsuMarkdownHeading.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs index a07216c32e..a3a86df678 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs @@ -20,7 +20,8 @@ namespace osu.Game.Graphics.Containers.Markdown public override MarkdownTextFlowContainer CreateTextFlow() => new HeadingTextFlowContainer { - Weight = GetFontWeightByLevel(level), + FontSize = GetFontSizeByLevel(level), + FontWeight = GetFontWeightByLevel(level), }; protected override float GetFontSizeByLevel(int level) @@ -65,9 +66,11 @@ namespace osu.Game.Graphics.Containers.Markdown private class HeadingTextFlowContainer : OsuMarkdownTextFlowContainer { - public FontWeight Weight { get; set; } + public float FontSize; + public FontWeight FontWeight; - protected override SpriteText CreateSpriteText() => base.CreateSpriteText().With(t => t.Font = t.Font.With(weight: Weight)); + protected override SpriteText CreateSpriteText() + => base.CreateSpriteText().With(t => t.Font = t.Font.With(size: FontSize, weight: FontWeight)); } } } From 0d17fb425973e8935220d06a2f40ff3223b23b86 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Tue, 22 Jun 2021 13:53:21 +0800 Subject: [PATCH 085/457] fixed code --- .../Online/TestSceneBeatmapListingOverlay.cs | 194 +++++++++--------- .../BeatmapListingFilterControl.cs | 41 +++- osu.Game/Overlays/BeatmapListingOverlay.cs | 70 +++---- 3 files changed, 170 insertions(+), 135 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 2146ea333a..cd382c2bb2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -24,6 +24,8 @@ namespace osu.Game.Tests.Visual.Online private BeatmapListingOverlay overlay; + private BeatmapListingSearchControl searchControl => overlay.ChildrenOfType().Single(); + [BackgroundDependencyLoader] private void load() { @@ -70,113 +72,123 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSupporterOnlyFiltersPlaceholderNoBeatmaps() + public void TestNonSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() { AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); // test non-supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, true); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + notFoundPlaceholderShown(); // test non-supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); // test non-supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); - - AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); - - // test supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(false, true); - - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, true); - - // test supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, true); - - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); - - // test supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, true); - - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); } [Test] - public void TestSupporterOnlyFiltersPlaceholderOneBeatmap() + public void TestSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() + { + AddStep("fetch for 0 beatmaps", () => fetchFor()); + AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); + + // test supporter on Rank Achieved filter + toggleRankFilter(Scoring.ScoreRank.XH); + notFoundPlaceholderShown(); + + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + notFoundPlaceholderShown(); + + // test supporter on Played filter + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + notFoundPlaceholderShown(); + + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); + + // test supporter on both Rank Achieved and Played filter + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + notFoundPlaceholderShown(); + + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); + } + + [Test] + public void TestNonSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() { AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); // test non-supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, false); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + noPlaceholderShown(); // test non-supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, false); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + noPlaceholderShown(); // test non-supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, false); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + noPlaceholderShown(); + } + [Test] + public void TestSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() + { + AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); // test supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(false, false); + toggleRankFilter(Scoring.ScoreRank.XH); + noPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, false); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + noPlaceholderShown(); // test supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, false); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + noPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, false); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + noPlaceholderShown(); // test supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, false); + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + noPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, false); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + noPlaceholderShown(); } private void fetchFor(params BeatmapSetInfo[] beatmaps) @@ -185,44 +197,36 @@ namespace osu.Game.Tests.Visual.Online setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b))); // trigger arbitrary change for fetching. - overlay.ChildrenOfType().Single().Query.TriggerChange(); + searchControl.Query.TriggerChange(); } - private void toggleRandomRankFilter() + private void toggleRankFilter(Scoring.ScoreRank rank) { - short r = TestContext.CurrentContext.Random.NextShort(); - AddStep("toggle Random Rank Achieved filter", () => + AddStep("toggle Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - overlay.ChildrenOfType().Single().Ranks.Add((Scoring.ScoreRank)(r % 8)); + searchControl.Ranks.Clear(); + searchControl.Ranks.Add(rank); }); } - private void toggleRandomSupporterOnlyPlayedFilter() + private void toggleSupporterOnlyPlayedFilter(SearchPlayed played) { - short r = TestContext.CurrentContext.Random.NextShort(); - AddStep("toggle Random Played filter", () => overlay.ChildrenOfType().Single().Played.Value = (SearchPlayed)(r % 2 + 1)); + AddStep("toggle Played filter", () => searchControl.Played.Value = played); } - private void expectedPlaceholderShown(bool supporterRequiredShown, bool notFoundShown) + private void supporterRequiredPlaceholderShown() { - if (supporterRequiredShown) - { - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - } - else - { - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - } + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } - if (notFoundShown) - { - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - } - else - { - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - } + private void notFoundPlaceholderShown() + { + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } + + private void noPlaceholderShown() + { + AddUntilStep("no placeholder shown", () => !overlay.ChildrenOfType().Any() && !overlay.ChildrenOfType().Any()); } private class TestAPIBeatmapSet : APIBeatmapSet diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 6e83dc0bf4..f49d913bb2 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -10,11 +10,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; +using osu.Game.Resources.Localisation.Web; using osuTK; using osuTK.Graphics; @@ -26,7 +28,7 @@ namespace osu.Game.Overlays.BeatmapListing /// Fired when a search finishes. Contains only new items in the case of pagination. /// Fired with BeatmapListingSearchControl when non-supporter user used supporter-only filters. /// - public Action, BeatmapListingSearchControl> SearchFinished; + public Action SearchFinished; /// /// Fired when search criteria change. @@ -216,11 +218,19 @@ namespace osu.Game.Overlays.BeatmapListing // check if an non-supporter user used supporter-only filters if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) { - SearchFinished?.Invoke(sets, searchControl); + List filters = new List(); + + if (searchControl.Played.Value != SearchPlayed.Any) + filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed); + + if (searchControl.Ranks.Any()) + filters.Add(BeatmapsStrings.ListingSearchFiltersRank); + + SearchFinished?.Invoke(SearchResult.SupporterOnlyFilter(filters)); } else { - SearchFinished?.Invoke(sets, null); + SearchFinished?.Invoke(SearchResult.ResultsReturned(sets)); } }; @@ -246,5 +256,30 @@ namespace osu.Game.Overlays.BeatmapListing base.Dispose(isDisposing); } + + public enum SearchResultType + { + ResultsReturned, + SupporterOnlyFilter + } + + public struct SearchResult + { + public SearchResultType Type { get; private set; } + public List Results { get; private set; } + public List Filters { get; private set; } + + public static SearchResult ResultsReturned(List results) => new SearchResult + { + Type = SearchResultType.ResultsReturned, + Results = results + }; + + public static SearchResult SupporterOnlyFilter(List filters) => new SearchResult + { + Type = SearchResultType.SupporterOnlyFilter, + Filters = filters + }; + } } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 578e70e630..63800e6585 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Localisation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -119,28 +120,28 @@ namespace osu.Game.Overlays private Task panelLoadDelegate; - private void onSearchFinished(List beatmaps, BeatmapListingSearchControl searchControl) + private void onSearchFinished(BeatmapListingFilterControl.SearchResult searchResult) { - var newPanels = beatmaps.Select(b => new GridBeatmapPanel(b) + // non-supporter user used supporter-only filters + if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilter) + { + supporterRequiredContent.UpdateText(searchResult.Filters); + addContentToPlaceholder(supporterRequiredContent); + return; + } + + var newPanels = searchResult.Results.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }); - // non-supporter user used supporter-only filters - if (searchControl != null) - { - supporterRequiredContent.UpdateText(searchControl.Played.Value != SearchPlayed.Any, searchControl.Ranks.Any()); - LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); - return; - } - if (filterControl.CurrentPage == 0) { //No matches case if (!newPanels.Any()) { - LoadComponentAsync(notFoundContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); + addContentToPlaceholder(notFoundContent); return; } @@ -182,16 +183,11 @@ namespace osu.Game.Overlays { var transform = lastContent.FadeOut(100, Easing.OutQuint); - if (lastContent == notFoundContent) + if (lastContent == notFoundContent || lastContent == supporterRequiredContent) { - // not found display may be used multiple times, so don't expire/dispose it. + // the placeholder may be used multiple times, so don't expire/dispose it. transform.Schedule(() => panelTarget.Remove(lastContent)); } - else if (lastContent == supporterRequiredContent) - { - // supporter required display may be used multiple times, so don't expire/dispose it. - transform.Schedule(() => panelTarget.Remove(supporterRequiredContent)); - } else { // Consider the case when the new content is smaller than the last content. @@ -260,7 +256,7 @@ namespace osu.Game.Overlays // using string literals as there's no proper processing for LocalizeStrings yet public class SupporterRequiredDrawable : CompositeDrawable { - private OsuSpriteText supporterRequiredText; + private OsuSpriteText filtersText; public SupporterRequiredDrawable() { @@ -289,30 +285,20 @@ namespace osu.Game.Overlays FillMode = FillMode.Fit, Texture = textures.Get(@"Online/supporter-required"), }, - supporterRequiredText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 16), - Colour = Colour4.White, - Margin = new MarginPadding { Bottom = 10 }, - }, - createSupporterTagLink(), + createSupporterText(), } }); } - public void UpdateText(bool playedFilter, bool rankFilter) + public void UpdateText(List filters) { - List filters = new List(); - if (playedFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); - if (rankFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); - supporterRequiredText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); + // use string literals for now + filtersText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); } - private Drawable createSupporterTagLink() + private Drawable createSupporterText() { - LinkFlowContainer supporterTagLink = new LinkFlowContainer + LinkFlowContainer supporterRequiredText = new LinkFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -320,8 +306,18 @@ namespace osu.Game.Overlays Margin = new MarginPadding { Bottom = 10 }, }; - supporterTagLink.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), "https://osu.ppy.sh/store/products/supporter-tag"); - return supporterTagLink; + filtersText = (OsuSpriteText)supporterRequiredText.AddText( + "_", + t => + { + t.Font = OsuFont.GetFont(size: 16); + t.Colour = Colour4.White; + } + ).First(); + + supporterRequiredText.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), @"/store/products/supporter-tag"); + + return supporterRequiredText; } } From 3745101f326b2d677f2c5ff124f01a43269c518e Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 10 Jun 2021 15:31:20 +0800 Subject: [PATCH 086/457] Extract seed setting control to IHasSeed --- osu.Game/Rulesets/Mods/IHasSeed.cs | 94 +++++++++++++++++++++++++++++ osu.Game/Rulesets/Mods/ModRandom.cs | 82 +------------------------ 2 files changed, 96 insertions(+), 80 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/IHasSeed.cs diff --git a/osu.Game/Rulesets/Mods/IHasSeed.cs b/osu.Game/Rulesets/Mods/IHasSeed.cs new file mode 100644 index 0000000000..b6852d960b --- /dev/null +++ b/osu.Game/Rulesets/Mods/IHasSeed.cs @@ -0,0 +1,94 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Rulesets.Mods +{ + public interface IHasSeed + { + public Bindable Seed { get; } + } + + public class SeedSettingsControl : SettingsItem + { + protected override Drawable CreateControl() => new SeedControl + { + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 5 } + }; + + private sealed class SeedControl : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current; + set + { + current.Current = value; + seedNumberBox.Text = value.Value.ToString(); + } + } + + private readonly OsuNumberBox seedNumberBox; + + public SeedControl() + { + AutoSizeAxes = Axes.Y; + + InternalChildren = new[] + { + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 2), + new Dimension(GridSizeMode.Relative, 0.25f) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + seedNumberBox = new OsuNumberBox + { + RelativeSizeAxes = Axes.X, + CommitOnFocusLost = true + } + } + } + } + }; + + seedNumberBox.Current.BindValueChanged(e => + { + int? value = null; + + if (int.TryParse(e.NewValue, out var intVal)) + value = intVal; + + current.Value = value; + }); + } + + protected override void Update() + { + if (current.Value == null) + seedNumberBox.Text = current.Current.Value.ToString(); + } + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index e0c3008ae8..c6040a48aa 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -8,11 +8,10 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; using osu.Game.Graphics; -using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods { - public abstract class ModRandom : Mod + public abstract class ModRandom : Mod, IHasSeed { public override string Name => "Random"; public override string Acronym => "RD"; @@ -20,88 +19,11 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.Dice; public override double ScoreMultiplier => 1; - [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(ModRandomSettingsControl))] + [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SeedSettingsControl))] public Bindable Seed { get; } = new Bindable { Default = null, Value = null }; - - private class ModRandomSettingsControl : SettingsItem - { - protected override Drawable CreateControl() => new SeedControl - { - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 5 } - }; - - private sealed class SeedControl : CompositeDrawable, IHasCurrentValue - { - private readonly BindableWithCurrent current = new BindableWithCurrent(); - - public Bindable Current - { - get => current; - set - { - current.Current = value; - seedNumberBox.Text = value.Value.ToString(); - } - } - - private readonly SettingsNumberBox.NumberBox seedNumberBox; - - public SeedControl() - { - AutoSizeAxes = Axes.Y; - - InternalChildren = new[] - { - new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 2), - new Dimension(GridSizeMode.Relative, 0.25f) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] - { - seedNumberBox = new SettingsNumberBox.NumberBox - { - RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true - } - } - } - } - }; - - seedNumberBox.Current.BindValueChanged(e => - { - int? value = null; - - if (int.TryParse(e.NewValue, out var intVal)) - value = intVal; - - current.Value = value; - }); - } - - protected override void Update() - { - if (current.Value == null) - seedNumberBox.Text = current.Current.Value.ToString(); - } - } - } } } From fc224c53f4ee203ae3ad6b9f7d556c24b8e892f2 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 22 Jun 2021 14:49:37 +0800 Subject: [PATCH 087/457] Remove extra usings --- osu.Game/Rulesets/Mods/ModRandom.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index c6040a48aa..61297c162d 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -2,10 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; using osu.Game.Graphics; From 0ad189e357f93d023dbaec929713ad40e867df36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 16:19:55 +0900 Subject: [PATCH 088/457] Expose skin sources via `ISkinSource` and revert to consuming based on hierarchy --- .../TestSceneCursorTrail.cs | 4 +++ .../TestSceneSkinFallbacks.cs | 3 ++ .../Gameplay/TestSceneSkinnableDrawable.cs | 3 ++ .../Gameplay/TestSceneSkinnableSound.cs | 2 ++ osu.Game/Skinning/ISkinSource.cs | 6 ++++ .../Skinning/RulesetSkinProvidingContainer.cs | 35 ++++++++----------- osu.Game/Skinning/SkinManager.cs | 26 ++++++++++---- osu.Game/Skinning/SkinProvidingContainer.cs | 15 ++++++++ .../Beatmaps/LegacyBeatmapSkinColourTest.cs | 1 + 9 files changed, 67 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index 46274e779b..fe962d3cb8 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; @@ -114,6 +116,8 @@ namespace osu.Game.Rulesets.Osu.Tests public ISkin FindProvider(Func lookupFunction) => null; + public IEnumerable AllSources => Enumerable.Empty(); + public event Action SourceChanged { add { } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 2b45818aa9..5ef27ab5ea 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -146,6 +147,8 @@ namespace osu.Game.Rulesets.Osu.Tests public ISkin FindProvider(Func lookupFunction) => null; + public IEnumerable AllSources => Enumerable.Empty(); + public event Action SourceChanged; private bool enabled = true; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 77966e925a..3317d8f80a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using NUnit.Framework; @@ -330,6 +331,8 @@ namespace osu.Game.Tests.Visual.Gameplay public ISkin FindProvider(Func lookupFunction) => throw new NotImplementedException(); + public IEnumerable AllSources => Enumerable.Empty(); + public event Action SourceChanged { add { } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 55ee01e0d5..59edb527eb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -147,6 +148,7 @@ namespace osu.Game.Tests.Visual.Gameplay public ISample GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo); public IBindable GetConfig(TLookup lookup) => source?.GetConfig(lookup); public ISkin FindProvider(Func lookupFunction) => source?.FindProvider(lookupFunction); + public IEnumerable AllSources => source.AllSources; public void TriggerSourceChanged() { diff --git a/osu.Game/Skinning/ISkinSource.cs b/osu.Game/Skinning/ISkinSource.cs index c7ebe91d64..ba3e2bf6ad 100644 --- a/osu.Game/Skinning/ISkinSource.cs +++ b/osu.Game/Skinning/ISkinSource.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using JetBrains.Annotations; namespace osu.Game.Skinning @@ -20,5 +21,10 @@ namespace osu.Game.Skinning /// The skin to be used for subsequent lookups, or null if none is available. [CanBeNull] ISkin FindProvider(Func lookupFunction); + + /// + /// Retrieve all sources available for lookup, with highest priority source first. + /// + IEnumerable AllSources { get; } } } diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index b07d3f5199..21a858977b 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -26,7 +26,6 @@ namespace osu.Game.Skinning /// Therefore disallow falling back to any parent any further. /// protected override bool AllowFallingBackToParent => false; - protected override Container Content { get; } public RulesetSkinProvidingContainer(Ruleset ruleset, IBeatmap beatmap, [CanBeNull] ISkin beatmapSkin) @@ -44,13 +43,13 @@ namespace osu.Game.Skinning } [Resolved] - private SkinManager skinManager { get; set; } + private ISkinSource skinSource { get; set; } [BackgroundDependencyLoader] private void load() { UpdateSkins(); - skinManager.SourceChanged += OnSourceChanged; + skinSource.SourceChanged += OnSourceChanged; } protected override void OnSourceChanged() @@ -63,25 +62,19 @@ namespace osu.Game.Skinning { SkinSources.Clear(); - // TODO: we also want to insert a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. - - switch (skinManager.CurrentSkin.Value) + foreach (var skin in skinSource.AllSources) { - case LegacySkin currentLegacySkin: - SkinSources.Add(GetLegacyRulesetTransformedSkin(currentLegacySkin)); + switch (skin) + { + case LegacySkin legacySkin: + SkinSources.Add(GetLegacyRulesetTransformedSkin(legacySkin)); + break; - if (currentLegacySkin != skinManager.DefaultLegacySkin) - SkinSources.Add(GetLegacyRulesetTransformedSkin(skinManager.DefaultLegacySkin)); - - break; - - default: - SkinSources.Add(skinManager.CurrentSkin.Value); - break; + default: + SkinSources.Add(skin); + break; + } } - - if (skinManager.CurrentSkin.Value != skinManager.DefaultSkin) - SkinSources.Add(skinManager.DefaultSkin); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) @@ -100,8 +93,8 @@ namespace osu.Game.Skinning { base.Dispose(isDisposing); - if (skinManager != null) - skinManager.SourceChanged -= OnSourceChanged; + if (skinSource != null) + skinSource.SourceChanged -= OnSourceChanged; } } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 1f10177a9e..6bd4888eb5 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -248,17 +248,29 @@ namespace osu.Game.Skinning return null; } + public IEnumerable AllSources + { + get + { + yield return CurrentSkin.Value; + + if (CurrentSkin.Value is LegacySkin) + yield return DefaultLegacySkin; + + yield return DefaultSkin; + } + } + private T lookupWithFallback(Func lookupFunction) where T : class { - if (lookupFunction(CurrentSkin.Value) is T skinSourced) - return skinSourced; + foreach (var source in AllSources) + { + if (lookupFunction(source) is T skinSourced) + return skinSourced; + } - if (CurrentSkin.Value is LegacySkin && lookupFunction(DefaultLegacySkin) is T legacySourced) - return legacySourced; - - // Finally fall back to the (non-legacy) default. - return lookupFunction(DefaultSkin); + return null; } #region IResourceStorageProvider diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 4435d924c2..c83c299723 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -131,6 +131,21 @@ namespace osu.Game.Skinning return fallbackSource?.FindProvider(lookupFunction); } + public IEnumerable AllSources + { + get + { + foreach (var skin in SkinSources) + yield return skin; + + if (fallbackSource != null) + { + foreach (var skin in fallbackSource.AllSources) + yield return skin; + } + } + } + public Drawable GetDrawableComponent(ISkinComponent component) { foreach (var skin in SkinSources) diff --git a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs index 347b611579..bb4768982a 100644 --- a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs @@ -146,6 +146,7 @@ namespace osu.Game.Tests.Beatmaps } public ISkin FindProvider(Func lookupFunction) => null; + public IEnumerable AllSources => Enumerable.Empty(); } } } From 14bdcef26b7da34ae0a1e1376b9c8e859ce42224 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 16:20:09 +0900 Subject: [PATCH 089/457] Add missing newline --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 21a858977b..f1cc3df3de 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -26,6 +26,7 @@ namespace osu.Game.Skinning /// Therefore disallow falling back to any parent any further. /// protected override bool AllowFallingBackToParent => false; + protected override Container Content { get; } public RulesetSkinProvidingContainer(Ruleset ruleset, IBeatmap beatmap, [CanBeNull] ISkin beatmapSkin) From b12adc6073b7d9c493a97bc9b2e8c5172fc46d88 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 10:48:03 +0300 Subject: [PATCH 090/457] Remove all test skinning changes in favour of the `ISkinSource.AllSources` path --- .../TestSceneLegacyBeatmapSkin.cs | 7 +++- .../TestSceneSkinFallbacks.cs | 24 ++++++++++- osu.Game/Screens/Play/Player.cs | 4 +- .../Tests/Beatmaps/HitObjectSampleTest.cs | 42 +++++++++++++++++-- .../Beatmaps/LegacyBeatmapSkinColourTest.cs | 32 ++++++++++---- .../Tests/Visual/LegacySkinPlayerTestScene.cs | 17 +++++++- osu.Game/Tests/Visual/PlayerTestScene.cs | 8 ---- osu.Game/Tests/Visual/TestPlayer.cs | 42 ------------------- 8 files changed, 108 insertions(+), 68 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs index 0077ff9e3c..bc3daca16f 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs @@ -101,10 +101,15 @@ namespace osu.Game.Rulesets.Catch.Tests AddAssert("is custom hyper dash fruit colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashFruitColour == TestSkin.HYPER_DASH_FRUIT_COLOUR); } - protected override ExposedPlayer CreateTestPlayer() => new CatchExposedPlayer(); + protected override ExposedPlayer CreateTestPlayer(bool userHasCustomColours) => new CatchExposedPlayer(userHasCustomColours); private class CatchExposedPlayer : ExposedPlayer { + public CatchExposedPlayer(bool userHasCustomColours) + : base(userHasCustomColours) + { + } + public Color4 UsableHyperDashColour => GameplayClockContainer.ChildrenOfType() .First() diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 5ef27ab5ea..334d27e0a9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -22,6 +22,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osu.Game.Storyboards; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { @@ -99,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Resolved] private AudioManager audio { get; set; } - protected override ISkin GetPlayerSkin() => 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); @@ -116,6 +117,27 @@ namespace osu.Game.Rulesets.Osu.Tests protected override ISkin GetSkin() => skin; } + public class SkinProvidingPlayer : TestPlayer + { + private readonly TestSource userSkin; + + public SkinProvidingPlayer(TestSource userSkin) + { + this.userSkin = userSkin; + } + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.CacheAs(userSkin); + + return dependencies; + } + } + public class TestSource : ISkinSource { private readonly string identifier; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index efc5fcfbe5..58f60d14cf 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -228,7 +228,7 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(GameplayBeatmap); - var rulesetSkinProvider = CreateRulesetSkinProvider(GameplayRuleset, playableBeatmap, Beatmap.Value.Skin); + var rulesetSkinProvider = new RulesetSkinProvidingContainer(GameplayRuleset, playableBeatmap, Beatmap.Value.Skin); // load the skinning hierarchy first. // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. @@ -309,8 +309,6 @@ namespace osu.Game.Screens.Play protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart); - protected virtual RulesetSkinProvidingContainer CreateRulesetSkinProvider(Ruleset ruleset, IBeatmap beatmap, ISkin beatmapSkin) => new RulesetSkinProvidingContainer(ruleset, beatmap, beatmapSkin); - private Drawable createUnderlayComponents() => DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index 7af0397726..7ee6c519b7 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -47,11 +47,15 @@ namespace osu.Game.Tests.Beatmaps private readonly TestResourceStore userSkinResourceStore = new TestResourceStore(); private readonly TestResourceStore beatmapSkinResourceStore = new TestResourceStore(); + private SkinSourceDependencyContainer dependencies; private IBeatmap currentTestBeatmap; protected sealed override bool HasCustomSteps => true; protected override bool Autoplay => true; + protected sealed override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + => new DependencyContainer(dependencies = new SkinSourceDependencyContainer(base.CreateChildDependencies(parent))); + protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestBeatmap; protected sealed override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) @@ -59,8 +63,6 @@ namespace osu.Game.Tests.Beatmaps protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false); - protected override ISkin GetPlayerSkin() => Skin; - protected void CreateTestWithBeatmap(string filename) { CreateTest(() => @@ -107,7 +109,8 @@ namespace osu.Game.Tests.Beatmaps } }; - Skin = new LegacySkin(userSkinInfo, this); + // Need to refresh the cached skin source to refresh the skin resource store. + dependencies.SkinSource = new SkinProvidingContainer(Skin = new LegacySkin(userSkinInfo, this)); }); } @@ -129,6 +132,39 @@ namespace osu.Game.Tests.Beatmaps #endregion + private class SkinSourceDependencyContainer : IReadOnlyDependencyContainer + { + public ISkinSource SkinSource; + + private readonly IReadOnlyDependencyContainer fallback; + + public SkinSourceDependencyContainer(IReadOnlyDependencyContainer fallback) + { + this.fallback = fallback; + } + + public object Get(Type type) + { + if (type == typeof(ISkinSource)) + return SkinSource; + + return fallback.Get(type); + } + + public object Get(Type type, CacheInfo info) + { + if (type == typeof(ISkinSource)) + return SkinSource; + + return fallback.Get(type, info); + } + + public void Inject(T instance) where T : class + { + // Never used directly + } + } + private class TestResourceStore : IResourceStore { public readonly List PerformedLookups = new List(); diff --git a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs index bb4768982a..86c75c297a 100644 --- a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs @@ -4,11 +4,13 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Screens.Play; using osu.Game.Skinning; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -47,24 +49,36 @@ namespace osu.Game.Tests.Beatmaps protected virtual ExposedPlayer LoadBeatmap(bool userHasCustomColours) { + ExposedPlayer player; + Beatmap.Value = testBeatmap; - ExposedPlayer player = CreateTestPlayer(); - - player.SetSkin(new TestSkin(userHasCustomColours)); - - LoadScreen(player); + LoadScreen(player = CreateTestPlayer(userHasCustomColours)); return player; } - protected virtual ExposedPlayer CreateTestPlayer() => new ExposedPlayer(); + protected virtual ExposedPlayer CreateTestPlayer(bool userHasCustomColours) => new ExposedPlayer(userHasCustomColours); - protected class ExposedPlayer : TestPlayer + protected class ExposedPlayer : Player { - public ExposedPlayer() - : base(false, false) + protected readonly bool UserHasCustomColours; + + public ExposedPlayer(bool userHasCustomColours) + : base(new PlayerConfiguration + { + AllowPause = false, + ShowResults = false, + }) { + UserHasCustomColours = userHasCustomColours; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(new TestSkin(UserHasCustomColours)); + return dependencies; } public IReadOnlyList UsableComboColours => diff --git a/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs index f4f351d46c..d74be70df8 100644 --- a/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs +++ b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; +using osu.Game.Rulesets; using osu.Game.Skinning; namespace osu.Game.Tests.Visual @@ -15,12 +16,15 @@ namespace osu.Game.Tests.Visual { protected LegacySkin LegacySkin { get; private set; } - protected override ISkin GetPlayerSkin() => LegacySkin; + private ISkinSource legacySkinSource; + + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(legacySkinSource); [BackgroundDependencyLoader] private void load(SkinManager skins) { LegacySkin = new DefaultLegacySkin(skins); + legacySkinSource = new SkinProvidingContainer(LegacySkin); } [SetUpSteps] @@ -47,5 +51,16 @@ namespace osu.Game.Tests.Visual AddUntilStep("wait for components to load", () => this.ChildrenOfType().All(t => t.ComponentsLoaded)); } + + public class SkinProvidingPlayer : TestPlayer + { + [Cached(typeof(ISkinSource))] + private readonly ISkinSource skinSource; + + public SkinProvidingPlayer(ISkinSource skinSource) + { + this.skinSource = skinSource; + } + } } } diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index f5fad895e2..088e997de9 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -10,7 +10,6 @@ using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Skinning; namespace osu.Game.Tests.Visual { @@ -79,8 +78,6 @@ namespace osu.Game.Tests.Visual } Player = CreatePlayer(ruleset); - Player.SetSkin(GetPlayerSkin()); - LoadScreen(Player); } @@ -96,11 +93,6 @@ namespace osu.Game.Tests.Visual [NotNull] protected abstract Ruleset CreatePlayerRuleset(); - /// - /// Creates an to be put inside the 's ruleset skin providing container. - /// - protected virtual ISkin GetPlayerSkin() => null; - protected sealed override Ruleset CreateRuleset() => CreatePlayerRuleset(); 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 19fd7068b9..09da4db952 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -1,21 +1,15 @@ // 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.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; 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; -using osu.Game.Skinning; namespace osu.Game.Tests.Visual { @@ -80,41 +74,5 @@ namespace osu.Game.Tests.Visual { ScoreProcessor.NewJudgement += r => Results.Add(r); } - - public ISkin Skin { get; private set; } - - private TestSkinProvidingContainer rulesetSkinProvider; - - internal void SetSkin(ISkin skin) - { - Debug.Assert(rulesetSkinProvider == null); - - if (Skin != null) - throw new InvalidOperationException("A skin has already been set."); - - Skin = skin; - } - - protected override RulesetSkinProvidingContainer CreateRulesetSkinProvider(Ruleset ruleset, IBeatmap beatmap, ISkin beatmapSkin) - => rulesetSkinProvider = new TestSkinProvidingContainer(Skin, ruleset, beatmap, beatmapSkin); - - private class TestSkinProvidingContainer : RulesetSkinProvidingContainer - { - private readonly ISkin skin; - - public TestSkinProvidingContainer(ISkin skin, Ruleset ruleset, IBeatmap beatmap, [CanBeNull] ISkin beatmapSkin) - : base(ruleset, beatmap, beatmapSkin) - { - this.skin = skin; - } - - protected override void UpdateSkins() - { - base.UpdateSkins(); - - if (skin != null) - SkinSources.Insert(0, skin is LegacySkin ? GetLegacyRulesetTransformedSkin(skin) : skin); - } - } } } From d0cdc07b1167fefd67bc1db900b9edf302104343 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 10:49:21 +0300 Subject: [PATCH 091/457] Reuse `AllSources` when looking up on `FindProvider` --- osu.Game/Skinning/SkinManager.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 6bd4888eb5..e220cad42a 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -236,14 +236,11 @@ namespace osu.Game.Skinning public ISkin FindProvider(Func lookupFunction) { - if (lookupFunction(CurrentSkin.Value)) - return CurrentSkin.Value; - - if (CurrentSkin.Value is LegacySkin && lookupFunction(DefaultLegacySkin)) - return DefaultLegacySkin; - - if (lookupFunction(DefaultSkin)) - return DefaultSkin; + foreach (var source in AllSources) + { + if (lookupFunction(source)) + return source; + } return null; } From c1284940e1da334498ca471952b2f8a4c8663033 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 10:49:37 +0300 Subject: [PATCH 092/457] Fix potentially providing the same skin instance twice in `AllSources` --- osu.Game/Skinning/SkinManager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index e220cad42a..3234cca0ac 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -251,10 +251,11 @@ namespace osu.Game.Skinning { yield return CurrentSkin.Value; - if (CurrentSkin.Value is LegacySkin) + if (CurrentSkin.Value is LegacySkin && CurrentSkin.Value != DefaultLegacySkin) yield return DefaultLegacySkin; - yield return DefaultSkin; + if (CurrentSkin.Value != DefaultSkin) + yield return DefaultSkin; } } From a9c783025262cf069e39c92f02e6760cc6c3d8cb Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 10:05:03 +0900 Subject: [PATCH 093/457] Fix NRE when hit object blueprint is not implemented --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 3e97e15cca..3552305664 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -256,7 +256,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (drawable == null) return null; - return CreateHitObjectBlueprintFor(item).With(b => b.DrawableObject = drawable); + return CreateHitObjectBlueprintFor(item)?.With(b => b.DrawableObject = drawable); } public virtual HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) => null; From fbe44dac34f255e4ac297fceedcd4c55ca8e1afb Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 10:05:29 +0900 Subject: [PATCH 094/457] Add empty catch hit object composer --- .../Editor/TestSceneEditor.cs | 14 +++++++++++++ osu.Game.Rulesets.Catch/CatchRuleset.cs | 4 ++++ .../Edit/CatchHitObjectComposer.cs | 20 +++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/TestSceneEditor.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneEditor.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneEditor.cs new file mode 100644 index 0000000000..161c685043 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneEditor.cs @@ -0,0 +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 NUnit.Framework; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests.Editor +{ + [TestFixture] + public class TestSceneEditor : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new CatchRuleset(); + } +} diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 23ce444560..cdd9a24f6a 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -22,7 +22,9 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using System; using osu.Framework.Extensions.EnumExtensions; +using osu.Game.Rulesets.Catch.Edit; using osu.Game.Rulesets.Catch.Skinning.Legacy; +using osu.Game.Rulesets.Edit; using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch @@ -182,5 +184,7 @@ namespace osu.Game.Rulesets.Catch public int LegacyID => 2; public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); + + public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this); } } diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs new file mode 100644 index 0000000000..392d7f004f --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.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.Collections.Generic; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public class CatchHitObjectComposer : HitObjectComposer + { + public CatchHitObjectComposer(CatchRuleset ruleset) + : base(ruleset) + { + } + + protected override IReadOnlyList CompositionTools => System.Array.Empty(); + } +} From b8ccfe6ea7d4dfde1c743ae814090060148cf9cd Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 10:39:32 +0900 Subject: [PATCH 095/457] Add basic selection blueprint movement logic --- .../BananaShowerSelectionBlueprint.cs | 15 +++++++ .../Blueprints/CatchSelectionBlueprint.cs | 38 +++++++++++++++++ .../Blueprints/FruitSelectionBlueprint.cs | 15 +++++++ .../Edit/CatchBlueprintContainer.cs | 35 ++++++++++++++++ .../Edit/CatchHitObjectComposer.cs | 12 ++++++ .../Edit/CatchSelectionHandler.cs | 41 +++++++++++++++++++ 6 files changed, 156 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerSelectionBlueprint.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerSelectionBlueprint.cs new file mode 100644 index 0000000000..9132b1a9e8 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerSelectionBlueprint.cs @@ -0,0 +1,15 @@ +// 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.Catch.Objects; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints +{ + public class BananaShowerSelectionBlueprint : CatchSelectionBlueprint + { + public BananaShowerSelectionBlueprint(BananaShower hitObject) + : base(hitObject) + { + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs new file mode 100644 index 0000000000..3ca57590e2 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs @@ -0,0 +1,38 @@ +// 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.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints +{ + public abstract class CatchSelectionBlueprint : HitObjectSelectionBlueprint + where THitObject : CatchHitObject + { + [Resolved] + private Playfield playfield { get; set; } + + protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer; + + public override Vector2 ScreenSpaceSelectionPoint + { + get + { + float x = HitObject.OriginalX; + float y = HitObjectContainer.PositionAtTime(HitObject.StartTime); + return HitObjectContainer.ToScreenSpace(new Vector2(x, y + HitObjectContainer.DrawHeight)); + } + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => SelectionQuad.Contains(screenSpacePos); + + protected CatchSelectionBlueprint(THitObject hitObject) + : base(hitObject) + { + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs new file mode 100644 index 0000000000..f104dbfb63 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs @@ -0,0 +1,15 @@ +// 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.Catch.Objects; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints +{ + public class FruitSelectionBlueprint : CatchSelectionBlueprint + { + public FruitSelectionBlueprint(Fruit hitObject) + : base(hitObject) + { + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs new file mode 100644 index 0000000000..b189a43915 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs @@ -0,0 +1,35 @@ +// 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.Catch.Edit.Blueprints; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public class CatchBlueprintContainer : ComposeBlueprintContainer + { + public CatchBlueprintContainer(CatchHitObjectComposer composer) + : base(composer) + { + } + + protected override SelectionHandler CreateSelectionHandler() => new CatchSelectionHandler(); + + public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) + { + switch (hitObject) + { + case Fruit fruit: + return new FruitSelectionBlueprint(fruit); + + case BananaShower bananaShower: + return new BananaShowerSelectionBlueprint(bananaShower); + } + + return base.CreateHitObjectBlueprintFor(hitObject); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 392d7f004f..00a10b86f4 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; namespace osu.Game.Rulesets.Catch.Edit { @@ -16,5 +18,15 @@ namespace osu.Game.Rulesets.Catch.Edit } protected override IReadOnlyList CompositionTools => System.Array.Empty(); + + public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) + { + var result = base.SnapScreenSpacePositionToValidTime(screenSpacePosition); + // TODO: implement position snap + result.ScreenSpacePosition.X = screenSpacePosition.X; + return result; + } + + protected override ComposeBlueprintContainer CreateBlueprintContainer() => new CatchBlueprintContainer(this); } } diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs new file mode 100644 index 0000000000..1fe303dc39 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.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 osu.Framework.Allocation; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public class CatchSelectionHandler : EditorSelectionHandler + { + [Resolved] + private Playfield playfield { get; set; } + + protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer; + + public override bool HandleMovement(MoveSelectionEvent moveEvent) + { + var blueprint = moveEvent.Blueprint; + Vector2 originalPosition = HitObjectContainer.ToLocalSpace(blueprint.ScreenSpaceSelectionPoint); + Vector2 targetPosition = HitObjectContainer.ToLocalSpace(blueprint.ScreenSpaceSelectionPoint + moveEvent.ScreenSpaceDelta); + float deltaX = targetPosition.X - originalPosition.X; + + EditorBeatmap.PerformOnSelection(h => + { + if (!(h is CatchHitObject hitObject)) return; + + if (hitObject is BananaShower) return; + + // TODO: confine in bounds + hitObject.OriginalXBindable.Value += deltaX; + }); + + return true; + } + } +} From c28cd5dd75bfa68ff12762bed9985793f81b6e8c Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 11:39:04 +0900 Subject: [PATCH 096/457] Add basic juice stream selection blueprint --- .../JuiceStreamSelectionBlueprint.cs | 57 +++++++++++++++++++ .../Edit/CatchBlueprintContainer.cs | 3 + .../Edit/CatchSelectionHandler.cs | 4 ++ 3 files changed, 64 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs new file mode 100644 index 0000000000..0cbce144a3 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.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.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints +{ + public class JuiceStreamSelectionBlueprint : CatchSelectionBlueprint + { + public override Quad SelectionQuad => HitObjectContainer.ToScreenSpace(computeBoundingBox().Offset(new Vector2(0, HitObjectContainer.DrawHeight))); + + private float minNestedX; + private float maxNestedX; + + public JuiceStreamSelectionBlueprint(JuiceStream hitObject) + : base(hitObject) + { + } + + [BackgroundDependencyLoader] + private void load() + { + HitObject.DefaultsApplied += onDefaultsApplied; + calculateObjectBounds(); + } + + private void onDefaultsApplied(HitObject _) => calculateObjectBounds(); + + private void calculateObjectBounds() + { + minNestedX = HitObject.NestedHitObjects.OfType().Min(nested => nested.OriginalX) - HitObject.OriginalX; + maxNestedX = HitObject.NestedHitObjects.OfType().Max(nested => nested.OriginalX) - HitObject.OriginalX; + } + + private RectangleF computeBoundingBox() + { + float left = HitObject.OriginalX + minNestedX; + float right = HitObject.OriginalX + maxNestedX; + float top = HitObjectContainer.PositionAtTime(HitObject.EndTime); + float bottom = HitObjectContainer.PositionAtTime(HitObject.StartTime); + float objectRadius = CatchHitObject.OBJECT_RADIUS * HitObject.Scale; + return new RectangleF(left, top, right - left, bottom - top).Inflate(objectRadius); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + HitObject.DefaultsApplied -= onDefaultsApplied; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs index b189a43915..7f2782a474 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs @@ -25,6 +25,9 @@ namespace osu.Game.Rulesets.Catch.Edit case Fruit fruit: return new FruitSelectionBlueprint(fruit); + case JuiceStream juiceStream: + return new JuiceStreamSelectionBlueprint(juiceStream); + case BananaShower bananaShower: return new BananaShowerSelectionBlueprint(bananaShower); } diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index 1fe303dc39..a0cedcb807 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.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.Linq; using osu.Framework.Allocation; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Objects; @@ -33,6 +34,9 @@ namespace osu.Game.Rulesets.Catch.Edit // TODO: confine in bounds hitObject.OriginalXBindable.Value += deltaX; + + foreach (var nested in hitObject.NestedHitObjects.OfType()) + nested.OriginalXBindable.Value += deltaX; }); return true; From 0078d7dc18af34d5216f7acf98b2fb55aa6d6256 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 12:04:24 +0900 Subject: [PATCH 097/457] Add outline to selected fruit --- .../Blueprints/Components/FruitOutline.cs | 39 +++++++++++++++++++ .../Blueprints/FruitSelectionBlueprint.cs | 12 ++++++ 2 files changed, 51 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs new file mode 100644 index 0000000000..35c481d793 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Skinning.Default; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components +{ + public class FruitOutline : CompositeDrawable + { + public FruitOutline() + { + Anchor = Anchor.BottomLeft; + Origin = Anchor.Centre; + Size = new Vector2(2 * CatchHitObject.OBJECT_RADIUS); + // TODO: use skinned component? + InternalChild = new BorderPiece(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour osuColour) + { + Colour = osuColour.Yellow; + } + + public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject) + { + X = hitObject.EffectiveX; + Y = hitObjectContainer.PositionAtTime(hitObject.StartTime); + Scale = new Vector2(hitObject.Scale); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs index f104dbfb63..9665aac2fb 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs @@ -1,15 +1,27 @@ // 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.Catch.Edit.Blueprints.Components; using osu.Game.Rulesets.Catch.Objects; namespace osu.Game.Rulesets.Catch.Edit.Blueprints { public class FruitSelectionBlueprint : CatchSelectionBlueprint { + private readonly FruitOutline outline; + public FruitSelectionBlueprint(Fruit hitObject) : base(hitObject) { + InternalChild = outline = new FruitOutline(); + } + + protected override void Update() + { + base.Update(); + + if (IsSelected) + outline.UpdateFrom(HitObjectContainer, HitObject); } } } From 4d7a8777954d0b821688a9b037773740dfc761b1 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 12:10:16 +0900 Subject: [PATCH 098/457] Add basic fruit placement tool --- .../Blueprints/CatchPlacementBlueprint.cs | 27 ++++++++++ .../Blueprints/FruitPlacementBlueprint.cs | 50 +++++++++++++++++++ .../Edit/CatchHitObjectComposer.cs | 5 +- .../Edit/FruitCompositionTool.cs | 20 ++++++++ 4 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs new file mode 100644 index 0000000000..69054e2c81 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs @@ -0,0 +1,27 @@ +// 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.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints +{ + public class CatchPlacementBlueprint : PlacementBlueprint + where THitObject : CatchHitObject, new() + { + protected new THitObject HitObject => (THitObject)base.HitObject; + + protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer; + + [Resolved] + private Playfield playfield { get; set; } + + public CatchPlacementBlueprint() + : base(new THitObject()) + { + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs new file mode 100644 index 0000000000..0f28cf6786 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osuTK.Input; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints +{ + public class FruitPlacementBlueprint : CatchPlacementBlueprint + { + private readonly FruitOutline outline; + + public FruitPlacementBlueprint() + { + InternalChild = outline = new FruitOutline(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + BeginPlacement(); + } + + protected override void Update() + { + base.Update(); + + outline.UpdateFrom(HitObjectContainer, HitObject); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button != MouseButton.Left) return base.OnMouseDown(e); + + EndPlacement(true); + return true; + } + + public override void UpdateTimeAndPosition(SnapResult result) + { + base.UpdateTimeAndPosition(result); + + HitObject.X = ToLocalSpace(result.ScreenSpacePosition).X; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 00a10b86f4..2099be8864 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -17,7 +17,10 @@ namespace osu.Game.Rulesets.Catch.Edit { } - protected override IReadOnlyList CompositionTools => System.Array.Empty(); + protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] + { + new FruitCompositionTool(), + }; public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) { diff --git a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs new file mode 100644 index 0000000000..da716bbe1d --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.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 osu.Game.Rulesets.Catch.Edit.Blueprints; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public class FruitCompositionTool : HitObjectCompositionTool + { + public FruitCompositionTool() + : base(nameof(Fruit)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint(); + } +} From e8907b53a8678b52657ea75b46a2fcea017e0ecb Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 12:30:09 +0900 Subject: [PATCH 099/457] Add basic banana shower placement tool --- .../Edit/BananaShowerCompositionTool.cs | 20 ++++++ .../BananaShowerPlacementBlueprint.cs | 72 +++++++++++++++++++ .../Blueprints/Components/TimeSpanOutline.cs | 61 ++++++++++++++++ .../Edit/CatchHitObjectComposer.cs | 1 + 4 files changed, 154 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs diff --git a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs new file mode 100644 index 0000000000..be18f223eb --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.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 osu.Game.Rulesets.Catch.Edit.Blueprints; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public class BananaShowerCompositionTool : HitObjectCompositionTool + { + public BananaShowerCompositionTool() + : base(nameof(BananaShower)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs new file mode 100644 index 0000000000..843cca3c7b --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs @@ -0,0 +1,72 @@ +// 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.Input.Events; +using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osuTK.Input; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints +{ + public class BananaShowerPlacementBlueprint : CatchPlacementBlueprint + { + private readonly TimeSpanOutline outline; + + public BananaShowerPlacementBlueprint() + { + InternalChild = outline = new TimeSpanOutline(); + } + + protected override void Update() + { + base.Update(); + + outline.UpdateFrom(HitObjectContainer, HitObject); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + switch (PlacementActive) + { + case PlacementState.Waiting: + if (e.Button != MouseButton.Left) break; + + BeginPlacement(true); + return true; + + case PlacementState.Active: + if (e.Button != MouseButton.Right) break; + + if (HitObject.Duration < 0) + { + HitObject.StartTime = HitObject.EndTime; + HitObject.Duration = -HitObject.Duration; + } + + EndPlacement(HitObject.Duration > 0); + return true; + } + + return base.OnMouseDown(e); + } + + public override void UpdateTimeAndPosition(SnapResult result) + { + base.UpdateTimeAndPosition(result); + + if (!(result.Time is double time)) return; + + switch (PlacementActive) + { + case PlacementState.Waiting: + HitObject.StartTime = time; + break; + + case PlacementState.Active: + HitObject.EndTime = time; + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs new file mode 100644 index 0000000000..4e5164e965 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs @@ -0,0 +1,61 @@ +// 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; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components +{ + public class TimeSpanOutline : CompositeDrawable + { + private const float border_width = 4; + + private bool isEmpty = true; + + public TimeSpanOutline() + { + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + RelativeSizeAxes = Axes.X; + + Masking = true; + BorderThickness = border_width; + + // a box is needed to make edge visible + InternalChild = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Transparent + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour osuColour) + { + BorderColour = osuColour.Yellow; + } + + public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, BananaShower hitObject) + { + float startY = hitObjectContainer.PositionAtTime(hitObject.StartTime); + float endY = hitObjectContainer.PositionAtTime(hitObject.EndTime); + + Y = Math.Max(startY, endY); + float height = Math.Abs(startY - endY); + + bool wasEmpty = isEmpty; + isEmpty = height == 0; + if (wasEmpty != isEmpty) + this.FadeTo(isEmpty ? 0.5f : 1f, 150); + + Height = Math.Max(height, border_width); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 2099be8864..c11e5eb45b 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Catch.Edit protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { new FruitCompositionTool(), + new BananaShowerCompositionTool() }; public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) From 21331d3a13c80c4f8fc4c3b8aac4438fec1ea7ae Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 12:47:24 +0900 Subject: [PATCH 100/457] Disable caught object stacking in editor --- .../Edit/CatchEditorPlayfield.cs | 27 +++++++++++++++++++ .../Edit/CatchHitObjectComposer.cs | 6 +++++ .../Edit/DrawableCatchEditorRuleset.cs | 21 +++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs diff --git a/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs new file mode 100644 index 0000000000..d383eb9ba6 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs @@ -0,0 +1,27 @@ +// 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.Beatmaps; +using osu.Game.Rulesets.Catch.UI; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public class CatchEditorPlayfield : CatchPlayfield + { + // TODO fixme: the size of the catcher is not changed when circle size is changed in setup screen. + public CatchEditorPlayfield(BeatmapDifficulty difficulty) + : base(difficulty) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // TODO: honor "hit animation" setting? + CatcherArea.MovableCatcher.CatchFruitOnPlate = false; + + // TODO: disable hit lighting as well + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index c11e5eb45b..d9712bc8e9 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -2,9 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -17,6 +20,9 @@ namespace osu.Game.Rulesets.Catch.Edit { } + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) => + new DrawableCatchEditorRuleset(ruleset, beatmap, mods); + protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { new FruitCompositionTool(), diff --git a/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs b/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs new file mode 100644 index 0000000000..0344709d45 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.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.Game.Beatmaps; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public class DrawableCatchEditorRuleset : DrawableCatchRuleset + { + public DrawableCatchEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + : base(ruleset, beatmap, mods) + { + } + + protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.BeatmapInfo.BaseDifficulty); + } +} From c4fde635c6f44c18687475a0f76401d9d66a35e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 17:41:20 +0900 Subject: [PATCH 101/457] Ensure duplicate mods cannot be defined --- osu.Game.Tests/Mods/ModUtilsTest.cs | 8 ++++++++ osu.Game/Utils/ModUtils.cs | 23 ++++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 9f27289d7e..0fac4521d9 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -14,6 +14,14 @@ namespace osu.Game.Tests.Mods [TestFixture] public class ModUtilsTest { + [Test] + public void TestModIsNotCompatibleWithItself() + { + var mod = new Mock(); + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod.Object, mod.Object }, out var invalid), Is.False); + Assert.That(invalid, Is.EquivalentTo(new[] { mod.Object })); + } + [Test] public void TestModIsCompatibleByItself() { diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 98766cb844..7485950f47 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -51,14 +51,31 @@ namespace osu.Game.Utils /// Whether all s in the combination are compatible with each-other. public static bool CheckCompatibleSet(IEnumerable combination, [NotNullWhen(false)] out List? invalidMods) { - combination = FlattenMods(combination).ToArray(); + var mods = FlattenMods(combination).ToArray(); invalidMods = null; - foreach (var mod in combination) + // ensure there are no duplicate mod definitions. + for (int i = 0; i < mods.Length; i++) + { + var candidate = mods[i]; + + for (int j = i + 1; j < mods.Length; j++) + { + var m = mods[j]; + + if (candidate.Equals(m)) + { + invalidMods ??= new List(); + invalidMods.Add(m); + } + } + } + + foreach (var mod in mods) { foreach (var type in mod.IncompatibleMods) { - foreach (var invalid in combination.Where(m => type.IsInstanceOfType(m))) + foreach (var invalid in mods.Where(m => type.IsInstanceOfType(m))) { if (invalid == mod) continue; From 6e0801b8529e5e3a832dd90ea56c1bc9e8638ee2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 17:41:27 +0900 Subject: [PATCH 102/457] Fix incorrect existing test case --- osu.Game.Tests/Mods/ModUtilsTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 0fac4521d9..4c126f0a3b 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -155,7 +155,7 @@ namespace osu.Game.Tests.Mods // multi mod. new object[] { - new Mod[] { new MultiMod(new OsuModHalfTime()), new OsuModHalfTime() }, + new Mod[] { new MultiMod(new OsuModHalfTime()), new OsuModDaycore() }, new[] { typeof(MultiMod) } }, // valid pair. From b8126e3ca8440180aaa1cc1812a0763f27e599e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 17:59:24 +0900 Subject: [PATCH 103/457] 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 1dc99bb60a..3c4380e355 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3c52405f8e..f91620bd25 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 3689ce51f2..22c4340ba2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 31cbb36a647f6221506da9cce093a6f0ed850d4b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 12:02:21 +0300 Subject: [PATCH 104/457] Implement `FindProvider` and `AllSources` properly on all test `ISkinSource`s --- osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs | 4 ++-- osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs | 4 ++-- osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs | 4 ++-- osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs | 5 +++-- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index fe962d3cb8..06bdb562e4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -114,9 +114,9 @@ namespace osu.Game.Rulesets.Osu.Tests public IBindable GetConfig(TLookup lookup) => null; - public ISkin FindProvider(Func lookupFunction) => null; + public ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : null; - public IEnumerable AllSources => Enumerable.Empty(); + public IEnumerable AllSources => new[] { this }; public event Action SourceChanged { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 334d27e0a9..662cbaee68 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -167,9 +167,9 @@ namespace osu.Game.Rulesets.Osu.Tests public IBindable GetConfig(TLookup lookup) => null; - public ISkin FindProvider(Func lookupFunction) => null; + public ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : null; - public IEnumerable AllSources => Enumerable.Empty(); + public IEnumerable AllSources => new[] { this }; public event Action SourceChanged; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 3317d8f80a..f29fbbf52b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -331,7 +331,7 @@ namespace osu.Game.Tests.Visual.Gameplay public ISkin FindProvider(Func lookupFunction) => throw new NotImplementedException(); - public IEnumerable AllSources => Enumerable.Empty(); + public IEnumerable AllSources => throw new NotImplementedException(); public event Action SourceChanged { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 59edb527eb..51f8b90c8f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -147,8 +147,8 @@ namespace osu.Game.Tests.Visual.Gameplay public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => source?.GetTexture(componentName, wrapModeS, wrapModeT); public ISample GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo); public IBindable GetConfig(TLookup lookup) => source?.GetConfig(lookup); - public ISkin FindProvider(Func lookupFunction) => source?.FindProvider(lookupFunction); - public IEnumerable AllSources => source.AllSources; + public ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : source?.FindProvider(lookupFunction); + public IEnumerable AllSources => new[] { this }.Concat(source?.AllSources); public void TriggerSourceChanged() { diff --git a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs index 86c75c297a..e0c2965fa0 100644 --- a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs @@ -159,8 +159,9 @@ namespace osu.Game.Tests.Beatmaps remove { } } - public ISkin FindProvider(Func lookupFunction) => null; - public IEnumerable AllSources => Enumerable.Empty(); + public ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : null; + + public IEnumerable AllSources => new[] { this }; } } } From ece63b9ba1ee6cc8683255dab0abf7d5cc439ee0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 12:03:55 +0300 Subject: [PATCH 105/457] Remove unused using directive --- osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index 06bdb562e4..211b0e8145 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; From 71e2815e7ec670693bf94ad8885e10b13015f338 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 12:05:17 +0300 Subject: [PATCH 106/457] Update and improve code documentation Co-authored-by: Dean Herbert --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 5 ++--- osu.Game/Skinning/SkinManager.cs | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index f1cc3df3de..c48aeca99a 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -13,8 +13,7 @@ namespace osu.Game.Skinning { /// /// A type of specialized for and other gameplay-related components. - /// Providing access to the skin sources and the beatmap skin each surrounded with the ruleset legacy skin transformer. - /// While also limiting lookups from falling back to any parent s out of this container. + /// Providing access to parent skin sources and the beatmap skin each surrounded with the ruleset legacy skin transformer. /// public class RulesetSkinProvidingContainer : SkinProvidingContainer { @@ -22,7 +21,7 @@ namespace osu.Game.Skinning protected readonly IBeatmap Beatmap; /// - /// This container already re-exposes all skin sources in a ruleset-usable form. + /// This container already re-exposes all parent sources in a ruleset-usable form. /// Therefore disallow falling back to any parent any further. /// protected override bool AllowFallingBackToParent => false; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 3234cca0ac..4cde4cd2b8 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -34,8 +34,8 @@ namespace osu.Game.Skinning /// Handles the storage and retrieval of s. /// /// - /// This is also exposed and cached as on a game-wide level for general components across the game. - /// Lookups from gameplay components are instead covered by , and are never hit here. + /// This is also exposed and cached as to allow for any component to potentially have skinning support. + /// For gameplay components, see which adds extra legacy and toggle logic that may affect the lookup process. /// [ExcludeFromDynamicCompile] public class SkinManager : ArchiveModelManager, ISkinSource, IStorageResourceProvider From a4b66bec2e570af80da16610bc99b859fa4651bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 18:18:25 +0900 Subject: [PATCH 107/457] Ensure realm contexts are flushed when update thread changes native thread --- osu.Game/OsuGameBase.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3a08ef684f..fb083ea7d5 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -182,6 +182,13 @@ namespace osu.Game dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage)); dependencies.Cache(realmFactory = new RealmContextFactory(Storage)); + + Host.UpdateThreadChanging += () => + { + var blocking = realmFactory.BlockAllOperations(); + Schedule(() => blocking.Dispose()); + }; + AddInternal(realmFactory); dependencies.CacheAs(Storage); From 37f7486fb1f2be6c5c7a2cf355266ec811265f77 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 12:25:29 +0300 Subject: [PATCH 108/457] Fix potential null reference in LINQ method --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 51f8b90c8f..ccf13e1e8f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -148,7 +148,7 @@ namespace osu.Game.Tests.Visual.Gameplay public ISample GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo); public IBindable GetConfig(TLookup lookup) => source?.GetConfig(lookup); public ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : source?.FindProvider(lookupFunction); - public IEnumerable AllSources => new[] { this }.Concat(source?.AllSources); + public IEnumerable AllSources => new[] { this }.Concat(source?.AllSources ?? Enumerable.Empty()); public void TriggerSourceChanged() { From b9a9174168f62115acb2bcc78f9054d6a248c27e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 18:26:42 +0900 Subject: [PATCH 109/457] Remove live realm bindings for now --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 37 +++++++++------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 432c52c2e9..d2e07c7d15 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -159,28 +159,6 @@ namespace osu.Game.Overlays.Toolbar }; } - private RealmKeyBinding realmKeyBinding; - - protected override void LoadComplete() - { - base.LoadComplete(); - - if (Hotkey == null) return; - - realmKeyBinding = realmFactory.Context.All().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.ActionInt == (int)Hotkey.Value); - - if (realmKeyBinding != null) - { - realmKeyBinding.PropertyChanged += (sender, args) => - { - if (args.PropertyName == nameof(realmKeyBinding.KeyCombinationString)) - updateKeyBindingTooltip(); - }; - } - - updateKeyBindingTooltip(); - } - protected override bool OnMouseDown(MouseDownEvent e) => true; protected override bool OnClick(ClickEvent e) @@ -196,6 +174,7 @@ namespace osu.Game.Overlays.Toolbar HoverBackground.FadeIn(200); tooltipContainer.FadeIn(100); + return base.OnHover(e); } @@ -222,6 +201,20 @@ namespace osu.Game.Overlays.Toolbar private void updateKeyBindingTooltip() { + if (Hotkey == null) return; + + var realmKeyBinding = realmFactory.Context.All().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.ActionInt == (int)Hotkey.Value); + + // TODO: temporarily disabled to avoid crashes when querying after ExecutionState is changed. + // if (realmKeyBinding != null) + // { + // realmKeyBinding.PropertyChanged += (sender, args) => + // { + // if (args.PropertyName == nameof(realmKeyBinding.KeyCombinationString)) + // updateKeyBindingTooltip(); + // }; + // } + if (realmKeyBinding != null) { var keyBindingString = realmKeyBinding.KeyCombination.ReadableString(); From f03c2bab481df4a343171b34e9eb1303095cee47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 22:45:13 +0900 Subject: [PATCH 110/457] Update event name in line with framework changes --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index fb083ea7d5..43c81783fe 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -183,7 +183,7 @@ namespace osu.Game dependencies.Cache(realmFactory = new RealmContextFactory(Storage)); - Host.UpdateThreadChanging += () => + Host.UpdateThreadPausing += () => { var blocking = realmFactory.BlockAllOperations(); Schedule(() => blocking.Dispose()); From ee84364d7ca7eea57d37257c198694ec08d6fb80 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 20:38:24 +0300 Subject: [PATCH 111/457] Resolve conflict issues --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 4a4773fce4..e46ba6d857 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -51,7 +51,6 @@ namespace osu.Game.Skinning private AudioManager audio { get; set; } [Resolved] - private SkinManager skinManager { get; set; } private ISkinSource skinSource { get; set; } [BackgroundDependencyLoader] @@ -84,6 +83,8 @@ namespace osu.Game.Skinning break; } } + + SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) @@ -102,9 +103,6 @@ namespace osu.Game.Skinning { base.Dispose(isDisposing); - SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultSkin, Beatmap)); - - SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); if (skinSource != null) skinSource.SourceChanged -= OnSourceChanged; } From 9cb9ef5c563316df57f7a49400359463ead3f49b Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Tue, 22 Jun 2021 13:31:41 -0700 Subject: [PATCH 112/457] Refactor the menu's max height to be a property --- osu.Game/Overlays/Settings/SettingsEnumDropdown.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index 9987a0c607..fa9973fb08 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -14,13 +14,15 @@ namespace osu.Game.Overlays.Settings protected new class DropdownControl : OsuEnumDropdown { + protected virtual int MenuMaxHeight => 200; + public DropdownControl() { Margin = new MarginPadding { Top = 5 }; RelativeSizeAxes = Axes.X; } - protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 200); + protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = MenuMaxHeight); } } } From 59928a7d91d230e32fec56d395d65c485457cc52 Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Tue, 22 Jun 2021 13:32:45 -0700 Subject: [PATCH 113/457] Decrease the max height of dropdown menus in mod settings --- osu.Game/Configuration/SettingSourceAttribute.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 3e50613093..cd0652f401 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -12,6 +12,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Localisation; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; namespace osu.Game.Configuration @@ -149,7 +150,7 @@ namespace osu.Game.Configuration break; case IBindable bindable: - var dropdownType = typeof(SettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]); + var dropdownType = typeof(ModSettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]); var dropdown = (Drawable)Activator.CreateInstance(dropdownType); dropdownType.GetProperty(nameof(SettingsDropdown.LabelText))?.SetValue(dropdown, attr.Label); @@ -183,5 +184,16 @@ namespace osu.Game.Configuration => obj.GetSettingsSourceProperties() .OrderBy(attr => attr.Item1) .ToArray(); + + private class ModSettingsEnumDropdown : SettingsEnumDropdown + where T : struct, Enum + { + protected override OsuDropdown CreateDropdown() => new ModDropdownControl(); + + private class ModDropdownControl : DropdownControl + { + protected override int MenuMaxHeight => 100; + } + } } } From 1a7bfafc698f03a840d8e8381281f0aeb1b39df0 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 09:34:11 +0900 Subject: [PATCH 114/457] Add icon for composition tools --- osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs | 4 ++++ osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs index be18f223eb..31075db7d1 100644 --- a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs +++ b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs @@ -1,6 +1,8 @@ // 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.Game.Beatmaps; using osu.Game.Rulesets.Catch.Edit.Blueprints; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Catch.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners); + public override PlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs index da716bbe1d..f776fe39c1 100644 --- a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs +++ b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs @@ -1,6 +1,8 @@ // 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.Game.Beatmaps; using osu.Game.Rulesets.Catch.Edit.Blueprints; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Catch.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); + public override PlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint(); } } From e96814bb866cb631bf5a8111df88f99c686b199e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 09:37:30 +0900 Subject: [PATCH 115/457] Remove comment about using skin for blueprint As the current game-wise direction is not using skin elements in blueprints. The design of the blueprint could be improved somehow, though. --- .../Edit/Blueprints/Components/FruitOutline.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs index 35c481d793..8769acc382 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs @@ -19,7 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components Anchor = Anchor.BottomLeft; Origin = Anchor.Centre; Size = new Vector2(2 * CatchHitObject.OBJECT_RADIUS); - // TODO: use skinned component? InternalChild = new BorderPiece(); } From eec44574739ed2348b620b141b8b61a94568920d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 09:40:07 +0900 Subject: [PATCH 116/457] Add `[CanBeNull]` to methods returning null by default --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 2 ++ .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 8a4d381535..185f029d14 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -94,6 +95,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Creates a for a specific item. /// /// The item to create the overlay for. + [CanBeNull] protected virtual SelectionBlueprint CreateBlueprintFor(T item) => null; protected virtual DragBox CreateDragBox(Action performSelect) => new DragBox(performSelect); diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 3552305664..79b38861ee 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using Humanizer; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -259,6 +260,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return CreateHitObjectBlueprintFor(item)?.With(b => b.DrawableObject = drawable); } + [CanBeNull] public virtual HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) => null; private void hitObjectAdded(HitObject obj) From a9b8736f70b19173731153284b5a29347870ee34 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 10:18:44 +0900 Subject: [PATCH 117/457] Order field and properties consistently --- .../Edit/Blueprints/CatchSelectionBlueprint.cs | 10 +++++----- osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs index 3ca57590e2..298f9474b0 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs @@ -13,11 +13,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints public abstract class CatchSelectionBlueprint : HitObjectSelectionBlueprint where THitObject : CatchHitObject { - [Resolved] - private Playfield playfield { get; set; } - - protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer; - public override Vector2 ScreenSpaceSelectionPoint { get @@ -30,6 +25,11 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => SelectionQuad.Contains(screenSpacePos); + protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer; + + [Resolved] + private Playfield playfield { get; set; } + protected CatchSelectionBlueprint(THitObject hitObject) : base(hitObject) { diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index a0cedcb807..f3a4d72c87 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -14,11 +14,11 @@ namespace osu.Game.Rulesets.Catch.Edit { public class CatchSelectionHandler : EditorSelectionHandler { + protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer; + [Resolved] private Playfield playfield { get; set; } - protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer; - public override bool HandleMovement(MoveSelectionEvent moveEvent) { var blueprint = moveEvent.Blueprint; From 69c8865a043d51dc824b33997f59051b4b82cb72 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 10:19:09 +0900 Subject: [PATCH 118/457] Use more consistent method names --- .../Edit/Blueprints/JuiceStreamSelectionBlueprint.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index 0cbce144a3..d6b8c35a09 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints { public class JuiceStreamSelectionBlueprint : CatchSelectionBlueprint { - public override Quad SelectionQuad => HitObjectContainer.ToScreenSpace(computeBoundingBox().Offset(new Vector2(0, HitObjectContainer.DrawHeight))); + public override Quad SelectionQuad => HitObjectContainer.ToScreenSpace(getBoundingBox().Offset(new Vector2(0, HitObjectContainer.DrawHeight))); private float minNestedX; private float maxNestedX; @@ -26,18 +26,18 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints private void load() { HitObject.DefaultsApplied += onDefaultsApplied; - calculateObjectBounds(); + computeObjectBounds(); } - private void onDefaultsApplied(HitObject _) => calculateObjectBounds(); + private void onDefaultsApplied(HitObject _) => computeObjectBounds(); - private void calculateObjectBounds() + private void computeObjectBounds() { minNestedX = HitObject.NestedHitObjects.OfType().Min(nested => nested.OriginalX) - HitObject.OriginalX; maxNestedX = HitObject.NestedHitObjects.OfType().Max(nested => nested.OriginalX) - HitObject.OriginalX; } - private RectangleF computeBoundingBox() + private RectangleF getBoundingBox() { float left = HitObject.OriginalX + minNestedX; float right = HitObject.OriginalX + maxNestedX; From 5a5cb39c9f1d6d11f3eec86e03dbf85af8c694c7 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 10:19:39 +0900 Subject: [PATCH 119/457] Add some comments about logic --- .../Edit/Blueprints/BananaShowerPlacementBlueprint.cs | 1 + .../Edit/Blueprints/Components/TimeSpanOutline.cs | 5 ++--- osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs index 843cca3c7b..6dea8b0712 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints case PlacementState.Active: if (e.Button != MouseButton.Right) break; + // If the duration is negative, swap the start and the end time to make the duration positive. if (HitObject.Duration < 0) { HitObject.StartTime = HitObject.EndTime; diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs index 4e5164e965..f925783919 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs @@ -21,14 +21,13 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components public TimeSpanOutline() { - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; + Anchor = Origin = Anchor.BottomLeft; RelativeSizeAxes = Axes.X; Masking = true; BorderThickness = border_width; - // a box is needed to make edge visible + // A box is needed to make the border visible. InternalChild = new Box { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index f3a4d72c87..c1a491d1ce 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -35,6 +35,7 @@ namespace osu.Game.Rulesets.Catch.Edit // TODO: confine in bounds hitObject.OriginalXBindable.Value += deltaX; + // Move the nested hit objects to give an instant result before nested objects are recreated. foreach (var nested in hitObject.NestedHitObjects.OfType()) nested.OriginalXBindable.Value += deltaX; }); From 125e1434017835cb992cd9bcad8b6b580b818cd9 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 10:26:01 +0900 Subject: [PATCH 120/457] Fix banana shower placement outline initial opacity --- .../Edit/Blueprints/Components/TimeSpanOutline.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs index f925783919..65dfce0493 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs @@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components { private const float border_width = 4; + private const float opacity_when_empty = 0.5f; + private bool isEmpty = true; public TimeSpanOutline() @@ -26,6 +28,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components Masking = true; BorderThickness = border_width; + Alpha = opacity_when_empty; // A box is needed to make the border visible. InternalChild = new Box @@ -52,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components bool wasEmpty = isEmpty; isEmpty = height == 0; if (wasEmpty != isEmpty) - this.FadeTo(isEmpty ? 0.5f : 1f, 150); + this.FadeTo(isEmpty ? opacity_when_empty : 1f, 150); Height = Math.Max(height, border_width); } From 0b351c99228cad3d84274a15774415a0a1ddaf87 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 10:57:04 +0900 Subject: [PATCH 121/457] Fix "possible NRE" inspection --- .../Compose/Components/Timeline/TimelineBlueprintContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 6f04f36b83..a642768574 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -89,7 +90,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } else { - placementBlueprint = CreateBlueprintFor(obj.NewValue); + placementBlueprint = CreateBlueprintFor(obj.NewValue).AsNonNull(); placementBlueprint.Colour = Color4.MediumPurple; From 49000b9501d9a8818679aede641c4ed843683f88 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 22 Jun 2021 08:48:41 -0700 Subject: [PATCH 122/457] Add multiplayer leave navigation tests --- .../Multiplayer/TestSceneMultiplayer.cs | 49 +++++++++++++++++++ osu.Game/Tests/Visual/ScreenTestScene.cs | 8 ++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index c5a6723508..599dfb082b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -6,15 +6,20 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; +using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Tests.Resources; @@ -159,6 +164,50 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("play started", () => !multiplayerScreen.IsCurrentScreen()); } + [Test] + public void TestLeaveNavigation() + { + loadMultiplayer(); + + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + AllowedMods = { new OsuModHidden() } + } + } + }); + + AddStep("open mod overlay", () => this.ChildrenOfType().ElementAt(2).Click()); + + AddStep("invoke on back button", () => multiplayerScreen.OnBackButton()); + + AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); + + AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden); + + testLeave("lounge tab item", () => this.ChildrenOfType.BreadcrumbTabItem>().First().Click()); + + testLeave("back button", () => multiplayerScreen.OnBackButton()); + + // mimics home button and OS window close + testLeave("forced exit", () => multiplayerScreen.Exit()); + + void testLeave(string actionName, Action action) + { + AddStep($"leave via {actionName}", action); + + AddAssert("dialog overlay is visible", () => DialogOverlay.State.Value == Visibility.Visible); + + AddStep("close dialog overlay", () => InputManager.Key(Key.Escape)); + } + } + private void createRoom(Func room) { AddStep("open room", () => diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs index 33cc00e748..b30be05ac4 100644 --- a/osu.Game/Tests/Visual/ScreenTestScene.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Overlays; using osu.Game.Screens; namespace osu.Game.Tests.Visual @@ -19,12 +21,16 @@ namespace osu.Game.Tests.Visual protected override Container Content => content; + [Cached] + protected DialogOverlay DialogOverlay { get; private set; } + protected ScreenTestScene() { base.Content.AddRange(new Drawable[] { Stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, - content = new Container { RelativeSizeAxes = Axes.Both } + content = new Container { RelativeSizeAxes = Axes.Both }, + DialogOverlay = new DialogOverlay() }); } From dc428da06c70797d92563caa3b11e0a89088d74b Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 22 Jun 2021 19:30:52 -0700 Subject: [PATCH 123/457] Fix test regression --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 07162c3cd1..b6ae91844a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -55,7 +55,12 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestExitWithoutSave() { - AddStep("exit without save", () => Editor.Exit()); + AddStep("exit without save", () => + { + Editor.Exit(); + DialogOverlay.CurrentDialog.PerformOkAction(); + }); + AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen()); AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true); } From 6fd020d91d76c3017fed6e4f99d2228d5d6a391b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 12:00:38 +0900 Subject: [PATCH 124/457] Remove unnecessary public accessibility All interface members are implicitly public. --- osu.Game/Rulesets/Mods/IHasSeed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/IHasSeed.cs b/osu.Game/Rulesets/Mods/IHasSeed.cs index b6852d960b..8e317b08be 100644 --- a/osu.Game/Rulesets/Mods/IHasSeed.cs +++ b/osu.Game/Rulesets/Mods/IHasSeed.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods { public interface IHasSeed { - public Bindable Seed { get; } + Bindable Seed { get; } } public class SeedSettingsControl : SettingsItem From 54084f8a9c7009766380f8cc12f87aca866030f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 13:40:05 +0900 Subject: [PATCH 125/457] Move `SeedSettingsControl` to own file --- osu.Game/Rulesets/Mods/IHasSeed.cs | 82 ----------------- osu.Game/Rulesets/Mods/SeedSettingsControl.cs | 92 +++++++++++++++++++ 2 files changed, 92 insertions(+), 82 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/SeedSettingsControl.cs diff --git a/osu.Game/Rulesets/Mods/IHasSeed.cs b/osu.Game/Rulesets/Mods/IHasSeed.cs index 8e317b08be..001a9d214c 100644 --- a/osu.Game/Rulesets/Mods/IHasSeed.cs +++ b/osu.Game/Rulesets/Mods/IHasSeed.cs @@ -2,11 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods { @@ -14,81 +9,4 @@ namespace osu.Game.Rulesets.Mods { Bindable Seed { get; } } - - public class SeedSettingsControl : SettingsItem - { - protected override Drawable CreateControl() => new SeedControl - { - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 5 } - }; - - private sealed class SeedControl : CompositeDrawable, IHasCurrentValue - { - private readonly BindableWithCurrent current = new BindableWithCurrent(); - - public Bindable Current - { - get => current; - set - { - current.Current = value; - seedNumberBox.Text = value.Value.ToString(); - } - } - - private readonly OsuNumberBox seedNumberBox; - - public SeedControl() - { - AutoSizeAxes = Axes.Y; - - InternalChildren = new[] - { - new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 2), - new Dimension(GridSizeMode.Relative, 0.25f) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] - { - seedNumberBox = new OsuNumberBox - { - RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true - } - } - } - } - }; - - seedNumberBox.Current.BindValueChanged(e => - { - int? value = null; - - if (int.TryParse(e.NewValue, out var intVal)) - value = intVal; - - current.Value = value; - }); - } - - protected override void Update() - { - if (current.Value == null) - seedNumberBox.Text = current.Current.Value.ToString(); - } - } - } } diff --git a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs new file mode 100644 index 0000000000..5c57717d93 --- /dev/null +++ b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs @@ -0,0 +1,92 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// A settings control for use by mods which have a customisable seed value. + /// + public class SeedSettingsControl : SettingsItem + { + protected override Drawable CreateControl() => new SeedControl + { + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 5 } + }; + + private sealed class SeedControl : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current; + set + { + current.Current = value; + seedNumberBox.Text = value.Value.ToString(); + } + } + + private readonly OsuNumberBox seedNumberBox; + + public SeedControl() + { + AutoSizeAxes = Axes.Y; + + InternalChildren = new[] + { + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 2), + new Dimension(GridSizeMode.Relative, 0.25f) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + seedNumberBox = new OsuNumberBox + { + RelativeSizeAxes = Axes.X, + CommitOnFocusLost = true + } + } + } + } + }; + + seedNumberBox.Current.BindValueChanged(e => + { + int? value = null; + + if (int.TryParse(e.NewValue, out var intVal)) + value = intVal; + + current.Value = value; + }); + } + + protected override void Update() + { + if (current.Value == null) + seedNumberBox.Text = current.Current.Value.ToString(); + } + } + } +} From bae42a89089559b7beecec677664fd57aa8705e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 14:07:37 +0900 Subject: [PATCH 126/457] Add inline comment explaining why the height is set lower --- osu.Game/Configuration/SettingSourceAttribute.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index cd0652f401..cee5883d9a 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -192,6 +192,7 @@ namespace osu.Game.Configuration private class ModDropdownControl : DropdownControl { + // Set low enough to workaround nested scroll issues (see https://github.com/ppy/osu-framework/issues/4536). protected override int MenuMaxHeight => 100; } } From e1b2c63e0941a08606f95bb08d28712c73576582 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 14:08:24 +0900 Subject: [PATCH 127/457] Add `IApplicableToBeatmapProcessor` mod interface --- osu.Game/Beatmaps/WorkingBeatmap.cs | 3 +++ .../Mods/IApplicableToBeatmapProcessor.cs | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 osu.Game/Rulesets/Mods/IApplicableToBeatmapProcessor.cs diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 712a3dd33a..662d24cc83 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -133,6 +133,9 @@ namespace osu.Game.Beatmaps IBeatmapProcessor processor = rulesetInstance.CreateBeatmapProcessor(converted); + foreach (var mod in mods.OfType()) + mod.ApplyToBeatmapProcessor(processor); + processor?.PreProcess(); // Compute default values for hitobjects, including creating nested hitobjects in-case they're needed diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmapProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmapProcessor.cs new file mode 100644 index 0000000000..e23a5d8d99 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmapProcessor.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.Beatmaps; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// Interface for a that applies changes to a . + /// + public interface IApplicableToBeatmapProcessor : IApplicableMod + { + /// + /// Applies this to a . + /// + void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor); + } +} From a0fd7f72ac8968bfa6d7a430ab571bdf3e558582 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 14:11:25 +0900 Subject: [PATCH 128/457] Use IApplicableToBeatmapProcessor in CatchModHardRock --- osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs | 8 ++++---- osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs | 8 ++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index fac5d03833..3a5322ce82 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -8,7 +8,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.MathUtils; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Catch.Beatmaps @@ -17,6 +16,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps { public const int RNG_SEED = 1337; + public bool HardRockOffsets { get; set; } + public CatchBeatmapProcessor(IBeatmap beatmap) : base(beatmap) { @@ -43,11 +44,10 @@ namespace osu.Game.Rulesets.Catch.Beatmaps } } - public static void ApplyPositionOffsets(IBeatmap beatmap, params Mod[] mods) + public void ApplyPositionOffsets(IBeatmap beatmap) { var rng = new FastRandom(RNG_SEED); - bool shouldApplyHardRockOffset = mods.Any(m => m is ModHardRock); float? lastPosition = null; double lastStartTime = 0; @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps switch (obj) { case Fruit fruit: - if (shouldApplyHardRockOffset) + if (HardRockOffsets) applyHardRockOffset(fruit, ref lastPosition, ref lastStartTime, rng); break; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index 0dde6aa06e..68b6ce96a3 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -7,10 +7,14 @@ using osu.Game.Rulesets.Catch.Beatmaps; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModHardRock : ModHardRock, IApplicableToBeatmap + public class CatchModHardRock : ModHardRock, IApplicableToBeatmapProcessor { public override double ScoreMultiplier => 1.12; - public void ApplyToBeatmap(IBeatmap beatmap) => CatchBeatmapProcessor.ApplyPositionOffsets(beatmap, this); + public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor) + { + var catchProcessor = (CatchBeatmapProcessor)beatmapProcessor; + catchProcessor.HardRockOffsets = true; + } } } From ad60b9d5a08000018b5a21b1a6656ce43884fe40 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 14:20:57 +0900 Subject: [PATCH 129/457] Allow catch difficulty adjust to enable hard rock offsets --- .../Mods/CatchModDifficultyAdjust.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 5f1736450a..0eec47ed09 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -5,11 +5,12 @@ using System.Linq; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModDifficultyAdjust : ModDifficultyAdjust + public class CatchModDifficultyAdjust : ModDifficultyAdjust, IApplicableToBeatmapProcessor { [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] public BindableNumber CircleSize { get; } = new BindableFloatWithLimitExtension @@ -31,6 +32,9 @@ namespace osu.Game.Rulesets.Catch.Mods Value = 5, }; + [SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")] + public BindableBool HardRockOffsets { get; } = new BindableBool(); + protected override void ApplyLimits(bool extended) { base.ApplyLimits(extended); @@ -70,5 +74,11 @@ namespace osu.Game.Rulesets.Catch.Mods ApplySetting(CircleSize, cs => difficulty.CircleSize = cs); ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar); } + + public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor) + { + var catchProcessor = (CatchBeatmapProcessor)beatmapProcessor; + catchProcessor.HardRockOffsets = HardRockOffsets.Value; + } } } From c9a203cc11f12532054493629617e21daaba4b21 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 15:03:34 +0900 Subject: [PATCH 130/457] Move collections db in-place --- osu.Game/Collections/CollectionManager.cs | 41 ++++++++++++++++------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index b53cc659f7..b2f36c75f4 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -257,27 +257,42 @@ namespace osu.Game.Collections { Interlocked.Increment(ref lastSave); + // This is NOT thread-safe!! try { - // This is NOT thread-safe!! + var tempPath = Path.GetTempFileName(); - using (var sw = new SerializationWriter(storage.GetStream(database_name, FileAccess.Write))) + using (var ms = new MemoryStream()) { - sw.Write(database_version); - - var collectionsCopy = Collections.ToArray(); - sw.Write(collectionsCopy.Length); - - foreach (var c in collectionsCopy) + using (var sw = new SerializationWriter(ms, true)) { - sw.Write(c.Name.Value); + sw.Write(database_version); - var beatmapsCopy = c.Beatmaps.ToArray(); - sw.Write(beatmapsCopy.Length); + var collectionsCopy = Collections.ToArray(); + sw.Write(collectionsCopy.Length); - foreach (var b in beatmapsCopy) - sw.Write(b.MD5Hash); + foreach (var c in collectionsCopy) + { + sw.Write(c.Name.Value); + + var beatmapsCopy = c.Beatmaps.ToArray(); + sw.Write(beatmapsCopy.Length); + + foreach (var b in beatmapsCopy) + sw.Write(b.MD5Hash); + } } + + using (var fs = File.OpenWrite(tempPath)) + ms.WriteTo(fs); + + var storagePath = storage.GetFullPath(database_name); + var storageBackupPath = storage.GetFullPath($"{database_name}.bak"); + if (File.Exists(storageBackupPath)) + File.Delete(storageBackupPath); + if (File.Exists(storagePath)) + File.Move(storagePath, storageBackupPath); + File.Move(tempPath, storagePath); } if (saveFailures < 10) From e4d17bd75773c52b61ec26c610483aec2ba4b34d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 15:06:28 +0900 Subject: [PATCH 131/457] Remove commented code This will be reverted in a future change, no need to keep it around. --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index d2e07c7d15..4a33f9e296 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -205,16 +205,6 @@ namespace osu.Game.Overlays.Toolbar var realmKeyBinding = realmFactory.Context.All().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.ActionInt == (int)Hotkey.Value); - // TODO: temporarily disabled to avoid crashes when querying after ExecutionState is changed. - // if (realmKeyBinding != null) - // { - // realmKeyBinding.PropertyChanged += (sender, args) => - // { - // if (args.PropertyName == nameof(realmKeyBinding.KeyCombinationString)) - // updateKeyBindingTooltip(); - // }; - // } - if (realmKeyBinding != null) { var keyBindingString = realmKeyBinding.KeyCombination.ReadableString(); From 5828606c1332152b71c942ae2b06d7d98dc445ad Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 15:09:42 +0900 Subject: [PATCH 132/457] Prefer using the backup file if it exists --- osu.Game/Collections/CollectionManager.cs | 30 ++++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index b2f36c75f4..04ea7eaebf 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -35,6 +35,7 @@ namespace osu.Game.Collections private const int database_version = 30000000; private const string database_name = "collection.db"; + private const string database_backup_name = "collection.db.bak"; public readonly BindableList Collections = new BindableList(); @@ -56,11 +57,14 @@ namespace osu.Game.Collections { Collections.CollectionChanged += collectionsChanged; - if (storage.Exists(database_name)) + // If a backup file exists, it means the previous write operation didn't complete successfully. Always prefer the backup file in such a case. + string filename = storage.Exists(database_backup_name) ? database_backup_name : database_name; + + if (storage.Exists(filename)) { List beatmapCollections; - using (var stream = storage.GetStream(database_name)) + using (var stream = storage.GetStream(filename)) beatmapCollections = readCollections(stream); // intentionally fire-and-forget async. @@ -286,13 +290,21 @@ namespace osu.Game.Collections using (var fs = File.OpenWrite(tempPath)) ms.WriteTo(fs); - var storagePath = storage.GetFullPath(database_name); - var storageBackupPath = storage.GetFullPath($"{database_name}.bak"); - if (File.Exists(storageBackupPath)) - File.Delete(storageBackupPath); - if (File.Exists(storagePath)) - File.Move(storagePath, storageBackupPath); - File.Move(tempPath, storagePath); + var databasePath = storage.GetFullPath(database_name); + var databaseBackupPath = storage.GetFullPath(database_backup_name); + + // Back up the existing database, clearing any existing backup. + if (File.Exists(databaseBackupPath)) + File.Delete(databaseBackupPath); + if (File.Exists(databasePath)) + File.Move(databasePath, databaseBackupPath); + + // Move the new database in-place of the existing one. + File.Move(tempPath, databasePath); + + // If everything succeeded up to this point, remove the backup file. + if (File.Exists(databaseBackupPath)) + File.Delete(databaseBackupPath); } if (saveFailures < 10) From 0510282dcbe1a3ad581d01ed57d1bfcfea431c18 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 15:10:03 +0900 Subject: [PATCH 133/457] Add missing ctor --- osu.Game/IO/Legacy/SerializationWriter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/IO/Legacy/SerializationWriter.cs b/osu.Game/IO/Legacy/SerializationWriter.cs index bb8014fe54..9ebeaf616e 100644 --- a/osu.Game/IO/Legacy/SerializationWriter.cs +++ b/osu.Game/IO/Legacy/SerializationWriter.cs @@ -18,8 +18,8 @@ namespace osu.Game.IO.Legacy /// handle null strings and simplify use with ISerializable. public class SerializationWriter : BinaryWriter { - public SerializationWriter(Stream s) - : base(s, Encoding.UTF8) + public SerializationWriter(Stream s, bool leaveOpen = false) + : base(s, Encoding.UTF8, leaveOpen) { } From 0db06c727b47c43b0207af22054a26c774c8c5b9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 23 Jun 2021 09:41:45 +0300 Subject: [PATCH 134/457] Dispose resource stores on finalizer --- osu.Game/Skinning/RulesetResourcesSkin.cs | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game/Skinning/RulesetResourcesSkin.cs b/osu.Game/Skinning/RulesetResourcesSkin.cs index 1905a8a899..60b22a8e67 100644 --- a/osu.Game/Skinning/RulesetResourcesSkin.cs +++ b/osu.Game/Skinning/RulesetResourcesSkin.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; @@ -47,5 +48,34 @@ namespace osu.Game.Skinning } public IBindable GetConfig(TLookup lookup) => null; + + #region Disposal + + ~RulesetResourcesSkin() + { + // required to potentially clean up sample store from audio hierarchy. + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private bool isDisposed; + + protected virtual void Dispose(bool isDisposing) + { + if (isDisposed) + return; + + isDisposed = true; + + rulesetTextures?.Dispose(); + rulesetSamples?.Dispose(); + } + + #endregion } } From 2e6800f5862f1815727b967a444f951fdbc89c1f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 23 Jun 2021 09:52:00 +0300 Subject: [PATCH 135/457] Enable NRT in `RulesetResourcesSkin` --- osu.Game/Skinning/RulesetResourcesSkin.cs | 29 +++++++++++++---------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/osu.Game/Skinning/RulesetResourcesSkin.cs b/osu.Game/Skinning/RulesetResourcesSkin.cs index 60b22a8e67..5cf2eec338 100644 --- a/osu.Game/Skinning/RulesetResourcesSkin.cs +++ b/osu.Game/Skinning/RulesetResourcesSkin.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -20,26 +22,29 @@ namespace osu.Game.Skinning /// public class RulesetResourcesSkin : ISkin { - private readonly TextureStore rulesetTextures; - private readonly ISampleStore rulesetSamples; + private readonly TextureStore? textures; + private readonly ISampleStore? samples; public RulesetResourcesSkin(Ruleset ruleset, GameHost host, AudioManager audio) { - IResourceStore rulesetResources = ruleset.CreateResourceStore(); + IResourceStore? resources = ruleset.CreateResourceStore(); - rulesetTextures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(rulesetResources, @"Textures"))); - rulesetSamples = audio.GetSampleStore(new NamespacedResourceStore(rulesetResources, @"Samples")); + if (resources != null) + { + textures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(resources, @"Textures"))); + samples = audio.GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); + } } - public Drawable GetDrawableComponent(ISkinComponent component) => null; + public Drawable? GetDrawableComponent(ISkinComponent component) => null; - public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => rulesetTextures.Get(componentName, wrapModeS, wrapModeT); + public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => textures?.Get(componentName, wrapModeS, wrapModeT); - public ISample GetSample(ISampleInfo sampleInfo) + public ISample? GetSample(ISampleInfo sampleInfo) { foreach (var lookup in sampleInfo.LookupNames) { - ISample sample = rulesetSamples.Get(lookup); + ISample? sample = samples?.Get(lookup); if (sample != null) return sample; } @@ -47,7 +52,7 @@ namespace osu.Game.Skinning return null; } - public IBindable GetConfig(TLookup lookup) => null; + public IBindable? GetConfig(TLookup lookup) => null; #region Disposal @@ -72,8 +77,8 @@ namespace osu.Game.Skinning isDisposed = true; - rulesetTextures?.Dispose(); - rulesetSamples?.Dispose(); + textures?.Dispose(); + samples?.Dispose(); } #endregion From 53fa22988097e9f22d6b16f8de7ce512a1a1bbab Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 23 Jun 2021 10:09:25 +0300 Subject: [PATCH 136/457] Add ruleset resources skin before `SkinManager.DefaultSkin` --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index e46ba6d857..c117df4b09 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -50,6 +50,9 @@ namespace osu.Game.Skinning [Resolved] private AudioManager audio { get; set; } + [Resolved] + private SkinManager skinManager { get; set; } + [Resolved] private ISkinSource skinSource { get; set; } @@ -84,7 +87,7 @@ namespace osu.Game.Skinning } } - SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); + SkinSources.Insert(SkinSources.IndexOf(skinManager.DefaultSkin), new RulesetResourcesSkin(Ruleset, host, audio)); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) From e7981eae9bbca5e40afbe9103628cfe17f9af9ba Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 16:14:05 +0900 Subject: [PATCH 137/457] Safeguard load procedure a bit more --- osu.Game/Collections/CollectionManager.cs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 04ea7eaebf..e6e8bedcf4 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -55,16 +55,26 @@ namespace osu.Game.Collections [BackgroundDependencyLoader] private void load() { + if (storage.Exists(database_backup_name)) + { + // If a backup file exists, it means the previous write operation didn't run to completion. + // Always prefer the backup file in such a case as it's the most recent copy that is guaranteed to not be malformed. + // + // The database is saved 100ms after any change, and again when the game is closed, so there shouldn't be a large diff between the two files in the worst case. + if (storage.Exists(database_name)) + storage.Delete(database_name); + File.Copy(storage.GetFullPath(database_backup_name), storage.GetFullPath(database_name)); + } + + // Only accept changes after/if the above succeeds to prevent simultaneously accessing either file. + // Todo: This really should not be delayed like this, but is unlikely to cause issues because CollectionManager loads very early in execution. Collections.CollectionChanged += collectionsChanged; - // If a backup file exists, it means the previous write operation didn't complete successfully. Always prefer the backup file in such a case. - string filename = storage.Exists(database_backup_name) ? database_backup_name : database_name; - - if (storage.Exists(filename)) + if (storage.Exists(database_name)) { List beatmapCollections; - using (var stream = storage.GetStream(filename)) + using (var stream = storage.GetStream(database_name)) beatmapCollections = readCollections(stream); // intentionally fire-and-forget async. From 2f657b972ddba9fefb4ada925f36067c3b275e99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 16:29:18 +0900 Subject: [PATCH 138/457] Remove local configuration of rider data sources --- .idea/.idea.osu.Desktop/.idea/dataSources.xml | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .idea/.idea.osu.Desktop/.idea/dataSources.xml diff --git a/.idea/.idea.osu.Desktop/.idea/dataSources.xml b/.idea/.idea.osu.Desktop/.idea/dataSources.xml deleted file mode 100644 index 10f8c1c84d..0000000000 --- a/.idea/.idea.osu.Desktop/.idea/dataSources.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - sqlite.xerial - true - org.sqlite.JDBC - jdbc:sqlite:$USER_HOME$/.local/share/osu/client.db - - - - - - \ No newline at end of file From 28f4c56cd6531b64f6631076fa911d62a2867b43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 16:30:17 +0900 Subject: [PATCH 139/457] Fix minor typo in comment --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3a08ef684f..6195d8e1ea 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -258,7 +258,7 @@ namespace osu.Game dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, true)); - // this should likely be moved to ArchiveModelManager when another case appers where it is necessary + // this should likely be moved to ArchiveModelManager when another case appears where it is necessary // to have inter-dependent model managers. this could be obtained with an IHasForeign interface to // allow lookups to be done on the child (ScoreManager in this case) to perform the cascading delete. List getBeatmapScores(BeatmapSetInfo set) From d48446990616a5d10c81b35a4aa4470283dc5757 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 23 Jun 2021 10:44:21 +0300 Subject: [PATCH 140/457] Handle case where `SkinManager` sources aren't part of `AllSources` In tests. --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index c117df4b09..83e2d398f9 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -87,7 +87,11 @@ namespace osu.Game.Skinning } } - SkinSources.Insert(SkinSources.IndexOf(skinManager.DefaultSkin), new RulesetResourcesSkin(Ruleset, host, audio)); + var defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + if (defaultSkinIndex >= 0) + SkinSources.Insert(defaultSkinIndex, new RulesetResourcesSkin(Ruleset, host, audio)); + else + SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) From 7767e2e77fbc2919b3ec9cce8fcf1f99590dc1cf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 17:34:30 +0900 Subject: [PATCH 141/457] Add to tooltip --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 0eec47ed09..98ddcaa21d 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -49,12 +49,14 @@ namespace osu.Game.Rulesets.Catch.Mods { string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}"; string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}"; + string spicyPatterns = HardRockOffsets.IsDefault ? string.Empty : $"Spicy patterns"; return string.Join(", ", new[] { circleSize, base.SettingDescription, - approachRate + approachRate, + spicyPatterns, }.Where(s => !string.IsNullOrEmpty(s))); } } From ed0552a9e8cce45220498581eec036f9640fde3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 17:19:18 +0900 Subject: [PATCH 142/457] Add failing test for FK constraint conflict on reimporting modified beatmap with scores present --- .../Beatmaps/IO/ImportBeatmapTest.cs | 72 ++++++++++++++++++- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 16 ++--- 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 0d117f8755..66b5cbed0c 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -19,7 +19,9 @@ using osu.Game.Database; using osu.Game.IO; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Scoring; using osu.Game.Tests.Resources; +using osu.Game.Tests.Scores.IO; using osu.Game.Users; using SharpCompress.Archives; using SharpCompress.Archives.Zip; @@ -185,10 +187,58 @@ namespace osu.Game.Tests.Beatmaps.IO } } - private string hashFile(string filename) + [Test] + public async Task TestImportThenImportWithChangedHashedFile() { - using (var s = File.OpenRead(filename)) - return s.ComputeMD5Hash(); + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + { + try + { + var osu = LoadOsuIntoHost(host); + + var temp = TestResources.GetTestBeatmapForImport(); + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + var imported = await LoadOszIntoOsu(osu); + + await createScoreForBeatmap(osu, imported.Beatmaps.First()); + + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + // arbitrary write to hashed file + // this triggers the special BeatmapManager.PreImport deletion/replacement flow. + using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.osu").First()).AppendText()) + await sw.WriteLineAsync("// changed"); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(extractedFolder); + zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); + } + + var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp)); + + ensureLoaded(osu); + + // check the newly "imported" beatmap is not the original. + Assert.IsTrue(imported.ID != importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + } + finally + { + Directory.Delete(extractedFolder, true); + } + } + finally + { + host.Exit(); + } + } } [Test] @@ -895,6 +945,16 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending); } + private Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmap) + { + return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo + { + OnlineScoreID = 2, + Beatmap = beatmap, + BeatmapInfoID = beatmap.ID + }, new ImportScoreTest.TestArchiveReader()); + } + private void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false) { var manager = osu.Dependencies.Get(); @@ -904,6 +964,12 @@ namespace osu.Game.Tests.Beatmaps.IO : manager.GetAllUsableBeatmapSets().Count); } + private string hashFile(string filename) + { + using (var s = File.OpenRead(filename)) + return s.ComputeMD5Hash(); + } + private void checkBeatmapCount(OsuGameBase osu, int expected) { Assert.AreEqual(expected, osu.Dependencies.Get().QueryBeatmaps(_ => true).ToList().Count); diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 7522aca5dc..cd7d744f53 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Scores.IO OnlineScoreID = 12345, }; - var imported = await loadScoreIntoOsu(osu, toImport); + var imported = await LoadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Rank, imported.Rank); Assert.AreEqual(toImport.TotalScore, imported.TotalScore); @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Scores.IO Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, }; - var imported = await loadScoreIntoOsu(osu, toImport); + var imported = await LoadScoreIntoOsu(osu, toImport); Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock)); Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime)); @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Scores.IO } }; - var imported = await loadScoreIntoOsu(osu, toImport); + var imported = await LoadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]); Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]); @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Scores.IO } }; - var imported = await loadScoreIntoOsu(osu, toImport); + var imported = await LoadScoreIntoOsu(osu, toImport); var beatmapManager = osu.Dependencies.Get(); var scoreManager = osu.Dependencies.Get(); @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Scores.IO beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.Beatmap.ID))); Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true)); - var secondImport = await loadScoreIntoOsu(osu, imported); + var secondImport = await LoadScoreIntoOsu(osu, imported); Assert.That(secondImport, Is.Null); } finally @@ -163,7 +163,7 @@ namespace osu.Game.Tests.Scores.IO { var osu = LoadOsuIntoHost(host, true); - await loadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader()); + await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader()); var scoreManager = osu.Dependencies.Get(); @@ -177,7 +177,7 @@ namespace osu.Game.Tests.Scores.IO } } - private async Task loadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) + public static async Task LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) { var beatmapManager = osu.Dependencies.Get(); @@ -190,7 +190,7 @@ namespace osu.Game.Tests.Scores.IO return scoreManager.GetAllUsableScores().FirstOrDefault(); } - private class TestArchiveReader : ArchiveReader + internal class TestArchiveReader : ArchiveReader { public TestArchiveReader() : base("test_archive") From dcba7bf779fa2ab78a884cc17671eefa15a71082 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 17:19:32 +0900 Subject: [PATCH 143/457] Fix import flow potentially hitting foreign key constraint --- osu.Game/Beatmaps/BeatmapManager.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 00af06703d..e93d8c6eb5 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -181,8 +181,13 @@ namespace osu.Game.Beatmaps if (existingOnlineId != null) { Delete(existingOnlineId); - beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID); - LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been purged."); + + // in order to avoid a unique key constraint, immediately remove the online ID from the previous set. + existingOnlineId.OnlineBeatmapSetID = null; + foreach (var b in existingOnlineId.Beatmaps) + b.OnlineBeatmapID = null; + + LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been deleted."); } } } From f6180b7e6a8adfa6560da82b4fe6805d5577f61e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 17:37:24 +0900 Subject: [PATCH 144/457] Mark `static` methods as such --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 66b5cbed0c..3b355da359 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -945,7 +945,7 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending); } - private Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmap) + private static Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmap) { return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo { @@ -955,7 +955,7 @@ namespace osu.Game.Tests.Beatmaps.IO }, new ImportScoreTest.TestArchiveReader()); } - private void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false) + private static void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false) { var manager = osu.Dependencies.Get(); @@ -964,18 +964,18 @@ namespace osu.Game.Tests.Beatmaps.IO : manager.GetAllUsableBeatmapSets().Count); } - private string hashFile(string filename) + private static string hashFile(string filename) { using (var s = File.OpenRead(filename)) return s.ComputeMD5Hash(); } - private void checkBeatmapCount(OsuGameBase osu, int expected) + private static void checkBeatmapCount(OsuGameBase osu, int expected) { Assert.AreEqual(expected, osu.Dependencies.Get().QueryBeatmaps(_ => true).ToList().Count); } - private void checkSingleReferencedFileCount(OsuGameBase osu, int expected) + private static void checkSingleReferencedFileCount(OsuGameBase osu, int expected) { Assert.AreEqual(expected, osu.Dependencies.Get().QueryFiles(f => f.ReferenceCount == 1).Count()); } From 6215f2d42beb416dc3a645e9f45cca77624a8d15 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 17:40:11 +0900 Subject: [PATCH 145/457] Remove unnecessary string interpolation --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 98ddcaa21d..bd7a1df2e4 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.Mods { string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}"; string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}"; - string spicyPatterns = HardRockOffsets.IsDefault ? string.Empty : $"Spicy patterns"; + string spicyPatterns = HardRockOffsets.IsDefault ? string.Empty : "Spicy patterns"; return string.Join(", ", new[] { From d148656108320559df37f00998d9346c3e2f41e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 18:08:34 +0900 Subject: [PATCH 146/457] Update in line with framework event structural changes (and add unbind) --- osu.Game/OsuGameBase.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 43c81783fe..f81eaa08a5 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -183,11 +183,7 @@ namespace osu.Game dependencies.Cache(realmFactory = new RealmContextFactory(Storage)); - Host.UpdateThreadPausing += () => - { - var blocking = realmFactory.BlockAllOperations(); - Schedule(() => blocking.Dispose()); - }; + Host.UpdateThread.ThreadPausing += onUpdateThreadPausing; AddInternal(realmFactory); @@ -363,6 +359,12 @@ namespace osu.Game Ruleset.BindValueChanged(onRulesetChanged); } + private void onUpdateThreadPausing() + { + var blocking = realmFactory.BlockAllOperations(); + Schedule(() => blocking.Dispose()); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -496,6 +498,9 @@ namespace osu.Game LocalConfig?.Dispose(); contextFactory.FlushConnections(); + + if (Host != null) + Host.UpdateThread.ThreadPausing -= onUpdateThreadPausing; } } } From 1bbfbb0d8e117ac9f5dad5a4f34aa3e1726ada2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 19:30:08 +0900 Subject: [PATCH 147/457] Fix test that never should have worked This was only working by luck until now. It was "correctly" matching on null online ID (see logic at https://github.com/ppy/osu/blob/abc96057b2cf50e02e0ec939645f6421684495d4/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs#L199-L207). Now it works by actually matching on the online ID. --- .../TestScenePlaylistsRoomSubScreen.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index a08a91314b..c4da2ab48a 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -111,10 +111,27 @@ namespace osu.Game.Tests.Visual.Playlists public void TestBeatmapUpdatedOnReImport() { BeatmapSetInfo importedSet = null; + TestBeatmap beatmap = null; + + // this step is required to make sure the further imports actually get online IDs. + // all the playlist logic relies on online ID matching. + AddStep("remove all matching online IDs", () => + { + beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo); + + var existing = manager.QueryBeatmapSets(s => s.OnlineBeatmapSetID == beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID).ToList(); + + foreach (var s in existing) + { + s.OnlineBeatmapSetID = null; + foreach (var b in s.Beatmaps) + b.OnlineBeatmapID = null; + manager.Update(s); + } + }); AddStep("import altered beatmap", () => { - var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo); beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1; importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result; From 01a5d998a542c7d9e88d071525c7b1cbbbf46fd5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 20:29:02 +0900 Subject: [PATCH 148/457] 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 1dc99bb60a..d0aff7b15e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3c52405f8e..0418e58593 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 3689ce51f2..6e2e169149 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 8a2026e3053c9c8bbc7d46c25029fa3d7f7f0506 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 21:26:52 +0900 Subject: [PATCH 149/457] Schedule callback instead --- osu.Game/Collections/CollectionManager.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index e6e8bedcf4..fe04c70d62 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -55,6 +55,8 @@ namespace osu.Game.Collections [BackgroundDependencyLoader] private void load() { + Collections.CollectionChanged += collectionsChanged; + if (storage.Exists(database_backup_name)) { // If a backup file exists, it means the previous write operation didn't run to completion. @@ -66,10 +68,6 @@ namespace osu.Game.Collections File.Copy(storage.GetFullPath(database_backup_name), storage.GetFullPath(database_name)); } - // Only accept changes after/if the above succeeds to prevent simultaneously accessing either file. - // Todo: This really should not be delayed like this, but is unlikely to cause issues because CollectionManager loads very early in execution. - Collections.CollectionChanged += collectionsChanged; - if (storage.Exists(database_name)) { List beatmapCollections; @@ -82,7 +80,7 @@ namespace osu.Game.Collections } } - private void collectionsChanged(object sender, NotifyCollectionChangedEventArgs e) + private void collectionsChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() => { switch (e.Action) { @@ -106,7 +104,7 @@ namespace osu.Game.Collections } backgroundSave(); - } + }); /// /// Set an endpoint for notifications to be posted to. From 263370e1ff946475f394c74c3c9e6aebab2d064d Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 23 Jun 2021 16:42:14 +0200 Subject: [PATCH 150/457] Localize ruleset filter any filter button. --- .../BeatmapSearchRulesetFilterRow.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs index c2d0eea80c..e2c84c537c 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; @@ -22,14 +23,21 @@ namespace osu.Game.Overlays.BeatmapListing [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - AddItem(new RulesetInfo - { - Name = @"Any" - }); + AddTabItem(new RulesetFilterTabItemAny()); foreach (var r in rulesets.AvailableRulesets) AddItem(r); } } + + private class RulesetFilterTabItemAny : FilterTabItem + { + protected override LocalisableString LabelFor(RulesetInfo info) => BeatmapsStrings.ModeAny; + + public RulesetFilterTabItemAny() + : base(new RulesetInfo()) + { + } + } } } From d2d0f3bf9ba7e46922c03f95c33d1ca18eb33d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Jun 2021 17:55:40 +0200 Subject: [PATCH 151/457] Set more appropriate build time limits for GH Actions workflows --- .github/workflows/ci.yml | 1 + .github/workflows/report-nunit.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed3e99cb61..29cbdd2d37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,7 @@ jobs: - { prettyname: macOS, fullname: macos-latest } - { prettyname: Linux, fullname: ubuntu-latest } threadingMode: ['SingleThread', 'MultiThreaded'] + timeout-minutes: 60 steps: - name: Checkout uses: actions/checkout@v2 diff --git a/.github/workflows/report-nunit.yml b/.github/workflows/report-nunit.yml index 381d2d49c5..e0ccd50989 100644 --- a/.github/workflows/report-nunit.yml +++ b/.github/workflows/report-nunit.yml @@ -21,6 +21,7 @@ jobs: - { prettyname: macOS } - { prettyname: Linux } threadingMode: ['SingleThread', 'MultiThreaded'] + timeout-minutes: 5 steps: - name: Annotate CI run with test results uses: dorny/test-reporter@v1.4.2 From c8022126dce165d7cd0e159e74c5f9cc726ef0b2 Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Wed, 23 Jun 2021 13:39:12 -0700 Subject: [PATCH 152/457] Add logo sound case for transitioning to song select --- osu.Game/Screens/Menu/ButtonSystem.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index a836f7bf09..da0edd07db 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -274,6 +274,10 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.Play: buttonsPlay.First().Click(); return false; + + // no sound should be played if the logo is clicked on while transitioning to song select + case ButtonSystemState.EnteringMode: + return false; } } From 73590bfca1a4131581aa74c6d7b28a8378729221 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Thu, 24 Jun 2021 07:20:31 +0800 Subject: [PATCH 153/457] Return an empty array when the sender is from system. --- osu.Game/Overlays/Chat/ChatLine.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index f43420e35e..8666d20159 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -240,6 +240,9 @@ namespace osu.Game.Overlays.Chat { get { + if (sender.Id == User.SYSTEM_USER.Id) + return Array.Empty(); + List items = new List { new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action) From 564c72bf74d6e5053f627d539f0118a9c4ee84e8 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Thu, 24 Jun 2021 10:10:57 +0800 Subject: [PATCH 154/457] compare directly instead of comparing IDs --- osu.Game/Overlays/Chat/ChatLine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 8666d20159..987dc27802 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -240,7 +240,7 @@ namespace osu.Game.Overlays.Chat { get { - if (sender.Id == User.SYSTEM_USER.Id) + if (sender == User.SYSTEM_USER) return Array.Empty(); List items = new List From 812624a502cdff0cd276d53857f83516fc9d07da Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Thu, 24 Jun 2021 10:45:20 +0800 Subject: [PATCH 155/457] use `.Equals()` instead --- osu.Game/Overlays/Chat/ChatLine.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 987dc27802..01cfe9a55b 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -240,7 +240,7 @@ namespace osu.Game.Overlays.Chat { get { - if (sender == User.SYSTEM_USER) + if (sender.Equals(User.SYSTEM_USER)) return Array.Empty(); List items = new List @@ -248,7 +248,7 @@ namespace osu.Game.Overlays.Chat new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action) }; - if (sender.Id != api.LocalUser.Value.Id) + if (!sender.Equals(api.LocalUser.Value)) items.Add(new OsuMenuItem("Start Chat", MenuItemType.Standard, startChatAction)); return items.ToArray(); From cd6f17537531203eaa7f1782e15c73ba520f5e11 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Jun 2021 13:29:06 +0900 Subject: [PATCH 156/457] Ensure beatmap is reloaded before each playlist room test run --- .../Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index c4da2ab48a..6d7a254ab9 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -40,8 +40,6 @@ namespace osu.Game.Tests.Visual.Playlists Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait(); - ((DummyAPIAccess)API).HandleRequest = req => { switch (req) @@ -58,6 +56,7 @@ namespace osu.Game.Tests.Visual.Playlists [SetUpSteps] public void SetupSteps() { + AddStep("ensure has beatmap", () => manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait()); AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(Room))); AddUntilStep("wait for load", () => match.IsCurrentScreen()); } From 2268d7f8a586f3121c420e2f04ceaf9bcd2a05b1 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 13:19:42 +0800 Subject: [PATCH 157/457] Extract utility methods into helper class; Better xmldoc and naming --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 79 +--------------- osu.Game.Rulesets.Osu/Utils/VectorHelper.cs | 100 ++++++++++++++++++++ 2 files changed, 102 insertions(+), 77 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Utils/VectorHelper.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 97e3d82664..bcd5274e02 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Osu.Utils; using osuTK; namespace osu.Game.Rulesets.Osu.Mods @@ -23,15 +24,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Description => "It never gets boring!"; - // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. - // The closer the hit objects draw to the border, the sharper the turn - private const float playfield_edge_ratio = 0.375f; - - private static readonly float border_distance_x = OsuPlayfield.BASE_SIZE.X * playfield_edge_ratio; - private static readonly float border_distance_y = OsuPlayfield.BASE_SIZE.Y * playfield_edge_ratio; - - private static readonly Vector2 playfield_middle = OsuPlayfield.BASE_SIZE / 2; - private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; private Random rng; @@ -113,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Mods distanceToPrev * (float)Math.Sin(current.AngleRad) ); - posRelativeToPrev = getRotatedVector(previous.EndPositionRandomised, posRelativeToPrev); + posRelativeToPrev = VectorHelper.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev); current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); @@ -185,73 +177,6 @@ namespace osu.Game.Rulesets.Osu.Mods } } - /// - /// Determines the position of the current hit object relative to the previous one. - /// - /// The position of the current hit object relative to the previous one - private Vector2 getRotatedVector(Vector2 prevPosChanged, Vector2 posRelativeToPrev) - { - var relativeRotationDistance = 0f; - - if (prevPosChanged.X < playfield_middle.X) - { - relativeRotationDistance = Math.Max( - (border_distance_x - prevPosChanged.X) / border_distance_x, - relativeRotationDistance - ); - } - else - { - relativeRotationDistance = Math.Max( - (prevPosChanged.X - (OsuPlayfield.BASE_SIZE.X - border_distance_x)) / border_distance_x, - relativeRotationDistance - ); - } - - if (prevPosChanged.Y < playfield_middle.Y) - { - relativeRotationDistance = Math.Max( - (border_distance_y - prevPosChanged.Y) / border_distance_y, - relativeRotationDistance - ); - } - else - { - relativeRotationDistance = Math.Max( - (prevPosChanged.Y - (OsuPlayfield.BASE_SIZE.Y - border_distance_y)) / border_distance_y, - relativeRotationDistance - ); - } - - return rotateVectorTowardsVector(posRelativeToPrev, playfield_middle - prevPosChanged, relativeRotationDistance / 2); - } - - /// - /// Rotates vector "initial" towards vector "destinantion" - /// - /// Vector to rotate to "destination" - /// Vector "initial" should be rotated to - /// The angle the vector should be rotated relative to the difference between the angles of the the two vectors. - /// Resulting vector - private Vector2 rotateVectorTowardsVector(Vector2 initial, Vector2 destination, float relativeDistance) - { - var initialAngleRad = Math.Atan2(initial.Y, initial.X); - var destAngleRad = Math.Atan2(destination.Y, destination.X); - - var diff = destAngleRad - initialAngleRad; - - while (diff < -Math.PI) diff += 2 * Math.PI; - - while (diff > Math.PI) diff -= 2 * Math.PI; - - var finalAngleRad = initialAngleRad + relativeDistance * diff; - - return new Vector2( - initial.Length * (float)Math.Cos(finalAngleRad), - initial.Length * (float)Math.Sin(finalAngleRad) - ); - } - private class RandomObjectInfo { public float AngleRad { get; set; } diff --git a/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs b/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs new file mode 100644 index 0000000000..bc482a3dd7 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs @@ -0,0 +1,100 @@ +// 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.Game.Rulesets.Osu.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Utils +{ + public static class VectorHelper + { + // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. + // The closer the hit objects draw to the border, the sharper the turn + private const float playfield_edge_ratio = 0.375f; + + private static readonly float border_distance_x = OsuPlayfield.BASE_SIZE.X * playfield_edge_ratio; + private static readonly float border_distance_y = OsuPlayfield.BASE_SIZE.Y * playfield_edge_ratio; + + private static readonly Vector2 playfield_middle = OsuPlayfield.BASE_SIZE / 2; + + /// + /// Rotate a hit object away from the playfield edge, while keeping a constant distance + /// from the previous object. + /// + /// + /// The extent of rotation depends on the position of the hit object. Hit objects + /// closer to the playfield edge will be rotated to a larger extent. + /// + /// Position of the previous hit object. + /// Position of the hit object to be rotated, relative to the previous hit object. + /// + /// The extent of rotation. + /// 0 means the hit object is never rotated. + /// 1 means the hit object will be fully rotated towards playfield center when it is originally at playfield edge. + /// + /// The new position of the hit object, relative to the previous one. + public static Vector2 RotateAwayFromEdge(Vector2 prevObjectPos, Vector2 posRelativeToPrev, float rotationRatio = 0.5f) + { + var relativeRotationDistance = 0f; + + if (prevObjectPos.X < playfield_middle.X) + { + relativeRotationDistance = Math.Max( + (border_distance_x - prevObjectPos.X) / border_distance_x, + relativeRotationDistance + ); + } + else + { + relativeRotationDistance = Math.Max( + (prevObjectPos.X - (OsuPlayfield.BASE_SIZE.X - border_distance_x)) / border_distance_x, + relativeRotationDistance + ); + } + + if (prevObjectPos.Y < playfield_middle.Y) + { + relativeRotationDistance = Math.Max( + (border_distance_y - prevObjectPos.Y) / border_distance_y, + relativeRotationDistance + ); + } + else + { + relativeRotationDistance = Math.Max( + (prevObjectPos.Y - (OsuPlayfield.BASE_SIZE.Y - border_distance_y)) / border_distance_y, + relativeRotationDistance + ); + } + + return RotateVectorTowardsVector(posRelativeToPrev, playfield_middle - prevObjectPos, relativeRotationDistance * rotationRatio); + } + + /// + /// Rotates vector "initial" towards vector "destination". + /// + /// The vector to be rotated. + /// The vector that "initial" should be rotated towards. + /// How much "initial" should be rotated. 0 means no rotation. 1 means "initial" is fully rotated to equal "destination". + /// The rotated vector. + public static Vector2 RotateVectorTowardsVector(Vector2 initial, Vector2 destination, float rotationRatio) + { + var initialAngleRad = Math.Atan2(initial.Y, initial.X); + var destAngleRad = Math.Atan2(destination.Y, destination.X); + + var diff = destAngleRad - initialAngleRad; + + while (diff < -Math.PI) diff += 2 * Math.PI; + + while (diff > Math.PI) diff -= 2 * Math.PI; + + var finalAngleRad = initialAngleRad + rotationRatio * diff; + + return new Vector2( + initial.Length * (float)Math.Cos(finalAngleRad), + initial.Length * (float)Math.Sin(finalAngleRad) + ); + } + } +} From 153e204d200bf554b19236c5fc283982047e148c Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 13:22:10 +0800 Subject: [PATCH 158/457] Cap rotation ratio to 1 --- osu.Game.Rulesets.Osu/Utils/VectorHelper.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs b/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs index bc482a3dd7..e3fe3249e8 100644 --- a/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs +++ b/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs @@ -68,7 +68,11 @@ namespace osu.Game.Rulesets.Osu.Utils ); } - return RotateVectorTowardsVector(posRelativeToPrev, playfield_middle - prevObjectPos, relativeRotationDistance * rotationRatio); + return RotateVectorTowardsVector( + posRelativeToPrev, + playfield_middle - prevObjectPos, + Math.Min(1, relativeRotationDistance * rotationRatio) + ); } /// From 63ab40ec24c24d9221f942f12cd2d60a3365bd42 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Jun 2021 14:37:26 +0900 Subject: [PATCH 159/457] Fix potential deadlocking behaviour (and convert `ResetEvent` to `Semaphore`) --- osu.Game/Database/RealmContextFactory.cs | 71 ++++++++++++++---------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index ed5931dd2b..4d81f8676f 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -26,6 +26,11 @@ namespace osu.Game.Database /// private readonly object writeLock = new object(); + /// + /// Lock object which is held during sections. + /// + private readonly SemaphoreSlim blockingLock = new SemaphoreSlim(1); + private static readonly GlobalStatistic reads = GlobalStatistics.Get("Realm", "Get (Read)"); private static readonly GlobalStatistic writes = GlobalStatistics.Get("Realm", "Get (Write)"); private static readonly GlobalStatistic refreshes = GlobalStatistics.Get("Realm", "Dirty Refreshes"); @@ -33,8 +38,6 @@ namespace osu.Game.Database private static readonly GlobalStatistic pending_writes = GlobalStatistics.Get("Realm", "Pending writes"); private static readonly GlobalStatistic active_usages = GlobalStatistics.Get("Realm", "Active usages"); - private readonly ManualResetEventSlim blockingResetEvent = new ManualResetEventSlim(true); - private Realm context; public Realm Context @@ -64,7 +67,7 @@ namespace osu.Game.Database public RealmUsage GetForRead() { reads.Value++; - return new RealmUsage(this); + return new RealmUsage(createContext()); } public RealmWriteUsage GetForWrite() @@ -73,8 +76,13 @@ namespace osu.Game.Database pending_writes.Value++; Monitor.Enter(writeLock); + return new RealmWriteUsage(createContext(), writeComplete); + } - return new RealmWriteUsage(this); + private void writeComplete() + { + Monitor.Exit(writeLock); + pending_writes.Value--; } protected override void Update() @@ -87,15 +95,22 @@ namespace osu.Game.Database private Realm createContext() { - blockingResetEvent.Wait(); - - contexts_created.Value++; - - return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true)) + try { - SchemaVersion = schema_version, - MigrationCallback = onMigration, - }); + blockingLock.Wait(); + + contexts_created.Value++; + + return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true)) + { + SchemaVersion = schema_version, + MigrationCallback = onMigration, + }); + } + finally + { + blockingLock.Release(); + } } private void onMigration(Migration migration, ulong lastSchemaVersion) @@ -113,21 +128,23 @@ namespace osu.Game.Database { base.Dispose(isDisposing); - BlockAllOperations(); + // In the standard case, operations will already be blocked by the Update thread "pausing" from GameHost exit. + // This avoids waiting (forever) on an already entered semaphore. + if (context != null || active_usages.Value > 0) + BlockAllOperations(); + + blockingLock?.Dispose(); } public IDisposable BlockAllOperations() { - blockingResetEvent.Reset(); + blockingLock.Wait(); flushContexts(); - return new InvokeOnDisposal(this, r => endBlockingSection()); + return new InvokeOnDisposal(this, endBlockingSection); } - private void endBlockingSection() - { - blockingResetEvent.Set(); - } + private static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); private void flushContexts() { @@ -148,13 +165,10 @@ namespace osu.Game.Database { public readonly Realm Realm; - protected readonly RealmContextFactory Factory; - - internal RealmUsage(RealmContextFactory factory) + internal RealmUsage(Realm context) { active_usages.Value++; - Factory = factory; - Realm = factory.createContext(); + Realm = context; } /// @@ -172,11 +186,13 @@ namespace osu.Game.Database /// public class RealmWriteUsage : RealmUsage { + private readonly Action onWriteComplete; private readonly Transaction transaction; - internal RealmWriteUsage(RealmContextFactory factory) - : base(factory) + internal RealmWriteUsage(Realm context, Action onWriteComplete) + : base(context) { + this.onWriteComplete = onWriteComplete; transaction = Realm.BeginWrite(); } @@ -200,8 +216,7 @@ namespace osu.Game.Database base.Dispose(); - Monitor.Exit(Factory.writeLock); - pending_writes.Value--; + onWriteComplete(); } } } From 27735eeedba9d3947ff403d1db12924f789b13ed Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Thu, 24 Jun 2021 13:45:38 +0800 Subject: [PATCH 160/457] fixed code --- .../BeatmapListingFilterControl.cs | 23 ++++++++----- osu.Game/Overlays/BeatmapListingOverlay.cs | 34 +++++++------------ 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index f49d913bb2..b6a0846407 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -25,8 +25,9 @@ namespace osu.Game.Overlays.BeatmapListing public class BeatmapListingFilterControl : CompositeDrawable { /// - /// Fired when a search finishes. Contains only new items in the case of pagination. - /// Fired with BeatmapListingSearchControl when non-supporter user used supporter-only filters. + /// Fired when a search finishes. + /// SearchFinished.Type = ResultsReturned when results returned. Contains only new items in the case of pagination. + /// SearchFinished.Type = SupporterOnlyFilter when a non-supporter user applied supporter-only filters. /// public Action SearchFinished; @@ -216,7 +217,7 @@ namespace osu.Game.Overlays.BeatmapListing getSetsRequest = null; // check if an non-supporter user used supporter-only filters - if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) + if (!api.LocalUser.Value.IsSupporter) { List filters = new List(); @@ -226,12 +227,14 @@ namespace osu.Game.Overlays.BeatmapListing if (searchControl.Ranks.Any()) filters.Add(BeatmapsStrings.ListingSearchFiltersRank); - SearchFinished?.Invoke(SearchResult.SupporterOnlyFilter(filters)); - } - else - { - SearchFinished?.Invoke(SearchResult.ResultsReturned(sets)); + if (filters.Any()) + { + SearchFinished?.Invoke(SearchResult.SupporterOnlyFilter(filters)); + return; + } } + + SearchFinished?.Invoke(SearchResult.ResultsReturned(sets)); }; api.Queue(getSetsRequest); @@ -259,10 +262,14 @@ namespace osu.Game.Overlays.BeatmapListing public enum SearchResultType { + // returned with Results ResultsReturned, + // non-supporter user applied supporter-only filters SupporterOnlyFilter } + // Results only valid when Type == ResultsReturned + // Filters only valid when Type == SupporterOnlyFilter public struct SearchResult { public SearchResultType Type { get; private set; } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 63800e6585..c2ba3d5bc0 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -256,7 +256,7 @@ namespace osu.Game.Overlays // using string literals as there's no proper processing for LocalizeStrings yet public class SupporterRequiredDrawable : CompositeDrawable { - private OsuSpriteText filtersText; + private LinkFlowContainer supporterRequiredText; public SupporterRequiredDrawable() { @@ -275,7 +275,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Children = new[] + Children = new Drawable[] { new Sprite { @@ -285,39 +285,31 @@ namespace osu.Game.Overlays FillMode = FillMode.Fit, Texture = textures.Get(@"Online/supporter-required"), }, - createSupporterText(), + supporterRequiredText = new LinkFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Bottom = 10 }, + }, } }); } public void UpdateText(List filters) { - // use string literals for now - filtersText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); - } + supporterRequiredText.Clear(); - private Drawable createSupporterText() - { - LinkFlowContainer supporterRequiredText = new LinkFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Bottom = 10 }, - }; - - filtersText = (OsuSpriteText)supporterRequiredText.AddText( - "_", + supporterRequiredText.AddText( + BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(), t => { t.Font = OsuFont.GetFont(size: 16); t.Colour = Colour4.White; } - ).First(); + ); supporterRequiredText.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), @"/store/products/supporter-tag"); - - return supporterRequiredText; } } From 16d58935352391052c4ffce4fcf98b875ef0dbca Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 24 Jun 2021 15:02:45 +0900 Subject: [PATCH 161/457] Add `DroppedObjectContainer` class --- .../TestSceneCatcher.cs | 6 +++--- .../TestSceneCatcherArea.cs | 8 ++------ .../TestSceneHyperDashColouring.cs | 2 +- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 6 +----- osu.Game.Rulesets.Catch/UI/Catcher.cs | 4 ++-- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 2 +- .../UI/DroppedObjectContainer.cs | 17 +++++++++++++++++ 7 files changed, 27 insertions(+), 18 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 900691ecae..28b1aaf03a 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Tests [Resolved] private OsuConfigManager config { get; set; } - private Container droppedObjectContainer; + private DroppedObjectContainer droppedObjectContainer; private TestCatcher catcher; @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Tests }; var trailContainer = new Container(); - droppedObjectContainer = new Container(); + droppedObjectContainer = new DroppedObjectContainer(); catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty); Child = new Container @@ -293,7 +293,7 @@ namespace osu.Game.Rulesets.Catch.Tests { public IEnumerable CaughtObjects => this.ChildrenOfType(); - public TestCatcher(Container trailsTarget, Container droppedObjectTarget, BeatmapDifficulty difficulty) + public TestCatcher(Container trailsTarget, DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty) : base(trailsTarget, droppedObjectTarget, difficulty) { } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index 4af5098451..dca75eee14 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Threading; using osu.Framework.Utils; @@ -97,10 +96,7 @@ namespace osu.Game.Rulesets.Catch.Tests SetContents(_ => { - var droppedObjectContainer = new Container - { - RelativeSizeAxes = Axes.Both - }; + var droppedObjectContainer = new DroppedObjectContainer(); return new CatchInputManager(catchRuleset) { @@ -126,7 +122,7 @@ namespace osu.Game.Rulesets.Catch.Tests private class TestCatcherArea : CatcherArea { - public TestCatcherArea(Container droppedObjectContainer, BeatmapDifficulty beatmapDifficulty) + public TestCatcherArea(DroppedObjectContainer droppedObjectContainer, BeatmapDifficulty beatmapDifficulty) : base(droppedObjectContainer, beatmapDifficulty) { } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 683a776dcc..4af528ef22 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("create hyper-dashing catcher", () => { - Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container()) + Child = setupSkinHierarchy(catcherArea = new CatcherArea(new DroppedObjectContainer()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 644facdabc..21501398fc 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; @@ -35,10 +34,7 @@ namespace osu.Game.Rulesets.Catch.UI public CatchPlayfield(BeatmapDifficulty difficulty) { - var droppedObjectContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }; + var droppedObjectContainer = new DroppedObjectContainer(); CatcherArea = new CatcherArea(droppedObjectContainer, difficulty) { diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 1f01dbabb5..6b42e3ddd3 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Catch.UI /// /// Contains objects dropped from the plate. /// - private readonly Container droppedObjectTarget; + private readonly DroppedObjectContainer droppedObjectTarget; public CatcherAnimationState CurrentState { @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Catch.UI private readonly DrawablePool caughtBananaPool; private readonly DrawablePool caughtDropletPool; - public Catcher([NotNull] Container trailsTarget, [NotNull] Container droppedObjectTarget, BeatmapDifficulty difficulty = null) + public Catcher([NotNull] Container trailsTarget, [NotNull] DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty = null) { this.trailsTarget = trailsTarget; this.droppedObjectTarget = droppedObjectTarget; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index cdb15c2b4c..951a10fc01 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.UI /// private int currentDirection; - public CatcherArea(Container droppedObjectContainer, BeatmapDifficulty difficulty = null) + public CatcherArea(DroppedObjectContainer droppedObjectContainer, BeatmapDifficulty difficulty = null) { Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE); Children = new Drawable[] diff --git a/osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs b/osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs new file mode 100644 index 0000000000..b44b0caae4 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Catch.Objects.Drawables; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class DroppedObjectContainer : Container + { + public DroppedObjectContainer() + { + RelativeSizeAxes = Axes.Both; + } + } +} From c9ec4b9da46bb1692fa86a5650e8efe8bbedcb95 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 00:28:39 +0900 Subject: [PATCH 162/457] Remove RoomTestScene inheritance from simple test scenes --- .../Multiplayer/TestSceneLoungeRoomInfo.cs | 34 +++---- .../TestSceneMatchBeatmapDetailArea.cs | 23 +++-- .../Multiplayer/TestSceneMatchHeader.cs | 71 +++++++------ .../Multiplayer/TestSceneMatchLeaderboard.cs | 99 ++++++++++--------- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestSceneMultiplayerRoomManager.cs | 2 +- .../TestScenePlaylistsMatchSettingsOverlay.cs | 32 +++--- .../TestScenePlaylistsParticipantsList.cs | 17 ++-- osu.Game/Tests/Visual/TestRoomContainer.cs | 38 +++++++ 9 files changed, 190 insertions(+), 128 deletions(-) create mode 100644 osu.Game/Tests/Visual/TestRoomContainer.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs index 9f24347ae9..a8cbd8b7ef 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs @@ -10,28 +10,28 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneLoungeRoomInfo : RoomTestScene + public class TestSceneLoungeRoomInfo : OsuTestScene { + private TestRoomContainer roomContainer; + [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Child = new RoomInfo + Child = roomContainer = new TestRoomContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 500 + Child = new RoomInfo + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500 + } }; }); - public override void SetUpSteps() - { - // Todo: Temp - } - [Test] public void TestNonSelectedRoom() { - AddStep("set null room", () => Room.RoomID.Value = null); + AddStep("set null room", () => roomContainer.Room.RoomID.Value = null); } [Test] @@ -39,11 +39,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set open room", () => { - Room.RoomID.Value = 0; - Room.Name.Value = "Room 0"; - Room.Host.Value = new User { Username = "peppy", Id = 2 }; - Room.EndDate.Value = DateTimeOffset.Now.AddMonths(1); - Room.Status.Value = new RoomStatusOpen(); + roomContainer.Room.RoomID.Value = 0; + roomContainer.Room.Name.Value = "Room 0"; + roomContainer.Room.Host.Value = new User { Username = "peppy", Id = 2 }; + roomContainer.Room.EndDate.Value = DateTimeOffset.Now.AddMonths(1); + roomContainer.Room.Status.Value = new RoomStatusOpen(); }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index 9ad9f2c883..2a254b1573 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchBeatmapDetailArea : RoomTestScene + public class TestSceneMatchBeatmapDetailArea : OsuTestScene { [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -23,23 +23,28 @@ namespace osu.Game.Tests.Visual.Multiplayer [Resolved] private RulesetStore rulesetStore { get; set; } + private TestRoomContainer roomContainer; + [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Child = new MatchBeatmapDetailArea + Child = roomContainer = new TestRoomContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500), - CreateNewItem = createNewItem + Child = new MatchBeatmapDetailArea + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500), + CreateNewItem = createNewItem + } }; }); private void createNewItem() { - Room.Playlist.Add(new PlaylistItem + roomContainer.Room.Playlist.Add(new PlaylistItem { - ID = Room.Playlist.Count, + ID = roomContainer.Room.Playlist.Count, Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, RequiredMods = diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs index 7cdc6b1a7d..50e6426472 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs @@ -11,42 +11,51 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchHeader : RoomTestScene + public class TestSceneMatchHeader : OsuTestScene { - public TestSceneMatchHeader() - { - Child = new Header(); - } + private TestRoomContainer roomContainer; [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Room.Playlist.Add(new PlaylistItem + Child = roomContainer = new TestRoomContainer { - Beatmap = - { - Value = new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - Title = "Title", - Artist = "Artist", - AuthorString = "Author", - }, - Version = "Version", - Ruleset = new OsuRuleset().RulesetInfo - } - }, - RequiredMods = - { - new OsuModDoubleTime(), - new OsuModNoFail(), - new OsuModRelax(), - } - }); - - Room.Name.Value = "A very awesome room"; - Room.Host.Value = new User { Id = 2, Username = "peppy" }; + Child = new Header() + }; }); + + [Test] + public void TestBasicRoom() + { + AddStep("set basic room", () => + { + roomContainer.Room.Playlist.Add(new PlaylistItem + { + Beatmap = + { + Value = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Title = "Title", + Artist = "Artist", + AuthorString = "Author", + }, + Version = "Version", + Ruleset = new OsuRuleset().RulesetInfo + } + }, + RequiredMods = + { + new OsuModDoubleTime(), + new OsuModNoFail(), + new OsuModRelax(), + } + }); + + roomContainer.Room.Name.Value = "A very awesome room"; + roomContainer.Room.Host.Value = new User { Id = 2, Username = "peppy" }; + }); + } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index 64eaf0556b..566dc9fc00 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -2,72 +2,75 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using Newtonsoft.Json; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Users; using osuTK; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchLeaderboard : RoomTestScene + public class TestSceneMatchLeaderboard : OsuTestScene { - protected override bool UseOnlineAPI => true; - - public TestSceneMatchLeaderboard() - { - Add(new MatchLeaderboard - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(550f, 450f), - Scope = MatchLeaderboardScope.Overall, - }); - } - [BackgroundDependencyLoader] - private void load(IAPIProvider api) + private void load() { - var req = new GetRoomScoresRequest(); - req.Success += v => { }; - req.Failure += _ => { }; + ((DummyAPIAccess)API).HandleRequest = r => + { + switch (r) + { + case GetRoomLeaderboardRequest leaderboardRequest: + leaderboardRequest.TriggerSuccess(new APILeaderboard + { + Leaderboard = new List + { + new APIUserScoreAggregate + { + UserID = 2, + User = new User { Id = 2, Username = "peppy" }, + TotalScore = 995533, + RoomID = 3, + CompletedBeatmaps = 1, + TotalAttempts = 6, + Accuracy = 0.9851 + }, + new APIUserScoreAggregate + { + UserID = 1040328, + User = new User { Id = 1040328, Username = "smoogipoo" }, + TotalScore = 981100, + RoomID = 3, + CompletedBeatmaps = 1, + TotalAttempts = 9, + Accuracy = 0.937 + } + } + }); + return true; + } - api.Queue(req); + return false; + }; } [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Room.RoomID.Value = 3; + Child = new TestRoomContainer + { + Room = { RoomID = { Value = 3 } }, + Child = new MatchLeaderboard + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(550f, 450f), + Scope = MatchLeaderboardScope.Overall, + } + }; }); - - private class GetRoomScoresRequest : APIRequest> - { - protected override string Target => "rooms/3/leaderboard"; - } - - private class RoomScore - { - [JsonProperty("user")] - public User User { get; set; } - - [JsonProperty("accuracy")] - public double Accuracy { get; set; } - - [JsonProperty("total_score")] - public int TotalScore { get; set; } - - [JsonProperty("pp")] - public double PP { get; set; } - - [JsonProperty("attempts")] - public int TotalAttempts { get; set; } - - [JsonProperty("completed")] - public int CompletedAttempts { get; set; } - } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 5b059c06f5..2725ef5976 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -29,7 +29,7 @@ using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiplayerMatchSongSelect : RoomTestScene + public class TestSceneMultiplayerMatchSongSelect : ScreenTestScene { private BeatmapManager manager; private RulesetStore rulesets; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index c008771fd9..80e36916b1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -12,7 +12,7 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Multiplayer { [HeadlessTest] - public class TestSceneMultiplayerRoomManager : RoomTestScene + public class TestSceneMultiplayerRoomManager : OsuTestScene { private TestMultiplayerRoomContainer roomContainer; private TestMultiplayerRoomManager roomManager => roomContainer.RoomManager; diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 44a79b6598..3055a2c6c7 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -15,23 +15,25 @@ using osu.Game.Screens.OnlinePlay.Playlists; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsMatchSettingsOverlay : RoomTestScene + public class TestScenePlaylistsMatchSettingsOverlay : OsuTestScene { [Cached(Type = typeof(IRoomManager))] private TestRoomManager roomManager = new TestRoomManager(); + private TestRoomContainer roomContainer; private TestRoomSettings settings; [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - settings = new TestRoomSettings + Child = roomContainer = new TestRoomContainer { - RelativeSizeAxes = Axes.Both, - State = { Value = Visibility.Visible } + Child = settings = new TestRoomSettings + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible } + } }; - - Child = settings; }); [Test] @@ -39,19 +41,19 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("clear name and beatmap", () => { - Room.Name.Value = ""; - Room.Playlist.Clear(); + roomContainer.Room.Name.Value = ""; + roomContainer.Room.Playlist.Clear(); }); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set name", () => Room.Name.Value = "Room name"); + AddStep("set name", () => roomContainer.Room.Name.Value = "Room name"); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set beatmap", () => Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } })); + AddStep("set beatmap", () => roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } })); AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value); - AddStep("clear name", () => Room.Name.Value = ""); + AddStep("clear name", () => roomContainer.Room.Name.Value = ""); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); } @@ -67,7 +69,7 @@ namespace osu.Game.Tests.Visual.Playlists { settings.NameField.Current.Value = expected_name; settings.DurationField.Current.Value = expectedDuration; - Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); + roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); roomManager.CreateRequested = r => { @@ -88,8 +90,8 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("setup", () => { - Room.Name.Value = "Test Room"; - Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); + roomContainer.Room.Name.Value = "Test Room"; + roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); fail = true; roomManager.CreateRequested = _ => !fail; diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index 255f147ec9..083930698f 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -8,16 +8,21 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsParticipantsList : RoomTestScene + public class TestScenePlaylistsParticipantsList : OsuTestScene { + private TestRoomContainer roomContainer; + [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Room.RoomID.Value = 7; + Child = roomContainer = new TestRoomContainer + { + Room = { RoomID = { Value = 7 } } + }; for (int i = 0; i < 50; i++) { - Room.RecentParticipants.Add(new User + roomContainer.Room.RecentParticipants.Add(new User { Username = "peppy", Statistics = new UserStatistics { GlobalRank = 1234 }, @@ -31,7 +36,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - Child = new ParticipantsDisplay(Direction.Horizontal) + roomContainer.Child = new ParticipantsDisplay(Direction.Horizontal) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -45,7 +50,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - Child = new ParticipantsDisplay(Direction.Vertical) + roomContainer.Child = new ParticipantsDisplay(Direction.Vertical) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Tests/Visual/TestRoomContainer.cs b/osu.Game/Tests/Visual/TestRoomContainer.cs new file mode 100644 index 0000000000..c4af89cd51 --- /dev/null +++ b/osu.Game/Tests/Visual/TestRoomContainer.cs @@ -0,0 +1,38 @@ +// 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.Game.Online.Rooms; + +namespace osu.Game.Tests.Visual +{ + /// + /// Contains a that is resolvable by components in test scenes. + /// + public class TestRoomContainer : Container + { + /// + /// The cached . + /// + public readonly Room Room = new Room(); + + [Cached] + private readonly Bindable roomBindable; + + public TestRoomContainer() + { + RelativeSizeAxes = Axes.Both; + roomBindable = new Bindable(Room); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new CachedModelDependencyContainer(base.CreateChildDependencies(parent)); + dependencies.Model.Value = Room; + return dependencies; + } + } +} From a98b5618b84e873adffeed23fdfd2a15a58a907c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 24 Jun 2021 10:07:38 +0300 Subject: [PATCH 163/457] Convert `RulesetResourcesSkin` to plain `ResourcesSkin` and pass non-null resources --- ...lesetResourcesSkin.cs => ResourcesSkin.cs} | 30 ++++++++----------- .../Skinning/RulesetSkinProvidingContainer.cs | 18 +++++++---- 2 files changed, 25 insertions(+), 23 deletions(-) rename osu.Game/Skinning/{RulesetResourcesSkin.cs => ResourcesSkin.cs} (61%) diff --git a/osu.Game/Skinning/RulesetResourcesSkin.cs b/osu.Game/Skinning/ResourcesSkin.cs similarity index 61% rename from osu.Game/Skinning/RulesetResourcesSkin.cs rename to osu.Game/Skinning/ResourcesSkin.cs index 5cf2eec338..3d17d7cc3d 100644 --- a/osu.Game/Skinning/RulesetResourcesSkin.cs +++ b/osu.Game/Skinning/ResourcesSkin.cs @@ -13,38 +13,32 @@ using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Audio; -using osu.Game.Rulesets; namespace osu.Game.Skinning { /// - /// An providing the resources of the ruleset for accessibility during lookups. + /// An that uses an underlying with namespaces for resources retrieval. /// - public class RulesetResourcesSkin : ISkin + public class ResourcesSkin : ISkin { - private readonly TextureStore? textures; - private readonly ISampleStore? samples; + private readonly TextureStore textures; + private readonly ISampleStore samples; - public RulesetResourcesSkin(Ruleset ruleset, GameHost host, AudioManager audio) + public ResourcesSkin(IResourceStore resources, GameHost host, AudioManager audio) { - IResourceStore? resources = ruleset.CreateResourceStore(); - - if (resources != null) - { - textures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(resources, @"Textures"))); - samples = audio.GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); - } + textures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(resources, @"Textures"))); + samples = audio.GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); } public Drawable? GetDrawableComponent(ISkinComponent component) => null; - public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => textures?.Get(componentName, wrapModeS, wrapModeT); + public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => textures.Get(componentName, wrapModeS, wrapModeT); public ISample? GetSample(ISampleInfo sampleInfo) { foreach (var lookup in sampleInfo.LookupNames) { - ISample? sample = samples?.Get(lookup); + ISample? sample = samples.Get(lookup); if (sample != null) return sample; } @@ -56,7 +50,7 @@ namespace osu.Game.Skinning #region Disposal - ~RulesetResourcesSkin() + ~ResourcesSkin() { // required to potentially clean up sample store from audio hierarchy. Dispose(false); @@ -77,8 +71,8 @@ namespace osu.Game.Skinning isDisposed = true; - textures?.Dispose(); - samples?.Dispose(); + textures.Dispose(); + samples.Dispose(); } #endregion diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 83e2d398f9..54bf91523f 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -87,11 +88,18 @@ namespace osu.Game.Skinning } } - var defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); - if (defaultSkinIndex >= 0) - SkinSources.Insert(defaultSkinIndex, new RulesetResourcesSkin(Ruleset, host, audio)); - else - SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); + if (Ruleset.CreateResourceStore() is IResourceStore resources) + { + int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + + if (defaultSkinIndex >= 0) + SkinSources.Insert(defaultSkinIndex, new ResourcesSkin(resources, host, audio)); + else + { + // Tests may potentially override the SkinManager with another source that doesn't include it in AllSources. + SkinSources.Add(new ResourcesSkin(resources, host, audio)); + } + } } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) From ae09c23e4e262d3f6cc815ead57aec7f734a1f60 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 24 Jun 2021 15:56:53 +0900 Subject: [PATCH 164/457] Resolve `DroppedObjectContainer` via DI --- .../TestSceneCatcher.cs | 38 +++++++++++-------- .../TestSceneCatcherArea.cs | 13 ++++--- .../TestSceneHyperDashColouring.cs | 18 +++++++-- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 9 +++-- osu.Game.Rulesets.Catch/UI/Catcher.cs | 6 +-- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 4 +- 6 files changed, 54 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 28b1aaf03a..1ad45d2f13 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -6,8 +6,8 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Game.Rulesets.Catch.UI; using osu.Framework.Graphics; +using osu.Game.Rulesets.Catch.UI; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; @@ -31,10 +31,23 @@ namespace osu.Game.Rulesets.Catch.Tests [Resolved] private OsuConfigManager config { get; set; } - private DroppedObjectContainer droppedObjectContainer; + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + + private readonly Container trailContainer; private TestCatcher catcher; + public TestSceneCatcher() + { + Add(trailContainer = new Container + { + Anchor = Anchor.Centre, + Depth = -1 + }); + Add(droppedObjectContainer = new DroppedObjectContainer()); + } + [SetUp] public void SetUp() => Schedule(() => { @@ -43,20 +56,13 @@ namespace osu.Game.Rulesets.Catch.Tests CircleSize = 0, }; - var trailContainer = new Container(); - droppedObjectContainer = new DroppedObjectContainer(); - catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty); + if (catcher != null) + Remove(catcher); - Child = new Container + Add(catcher = new TestCatcher(trailContainer, difficulty) { - Anchor = Anchor.Centre, - Children = new Drawable[] - { - trailContainer, - droppedObjectContainer, - catcher - } - }; + Anchor = Anchor.Centre + }); }); [Test] @@ -293,8 +299,8 @@ namespace osu.Game.Rulesets.Catch.Tests { public IEnumerable CaughtObjects => this.ChildrenOfType(); - public TestCatcher(Container trailsTarget, DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty) - : base(trailsTarget, droppedObjectTarget, difficulty) + public TestCatcher(Container trailsTarget, BeatmapDifficulty difficulty) + : base(trailsTarget, difficulty) { } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index dca75eee14..877e115e2f 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -96,15 +96,12 @@ namespace osu.Game.Rulesets.Catch.Tests SetContents(_ => { - var droppedObjectContainer = new DroppedObjectContainer(); - return new CatchInputManager(catchRuleset) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - droppedObjectContainer, - new TestCatcherArea(droppedObjectContainer, beatmapDifficulty) + new TestCatcherArea(beatmapDifficulty) { Anchor = Anchor.Centre, Origin = Anchor.TopCentre, @@ -122,9 +119,13 @@ namespace osu.Game.Rulesets.Catch.Tests private class TestCatcherArea : CatcherArea { - public TestCatcherArea(DroppedObjectContainer droppedObjectContainer, BeatmapDifficulty beatmapDifficulty) - : base(droppedObjectContainer, beatmapDifficulty) + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + + public TestCatcherArea(BeatmapDifficulty beatmapDifficulty) + : base(beatmapDifficulty) { + AddInternal(droppedObjectContainer = new DroppedObjectContainer()); } public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1); diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 4af528ef22..7fa981d492 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -118,11 +118,10 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("create hyper-dashing catcher", () => { - Child = setupSkinHierarchy(catcherArea = new CatcherArea(new DroppedObjectContainer()) + Child = setupSkinHierarchy(catcherArea = new TestCatcherArea { Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(4f), + Origin = Anchor.Centre }, skin); }); @@ -206,5 +205,18 @@ namespace osu.Game.Rulesets.Catch.Tests { } } + + private class TestCatcherArea : CatcherArea + { + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + + public TestCatcherArea() + { + Scale = new Vector2(4f); + + AddInternal(droppedObjectContainer = new DroppedObjectContainer()); + } + } } } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 21501398fc..05cd29dff5 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -26,6 +26,9 @@ namespace osu.Game.Rulesets.Catch.UI /// public const float CENTER_X = WIDTH / 2; + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + internal readonly CatcherArea CatcherArea; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => @@ -34,9 +37,7 @@ namespace osu.Game.Rulesets.Catch.UI public CatchPlayfield(BeatmapDifficulty difficulty) { - var droppedObjectContainer = new DroppedObjectContainer(); - - CatcherArea = new CatcherArea(droppedObjectContainer, difficulty) + CatcherArea = new CatcherArea(difficulty) { Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, @@ -44,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.UI InternalChildren = new[] { - droppedObjectContainer, + droppedObjectContainer = new DroppedObjectContainer(), CatcherArea.MovableCatcher.CreateProxiedContent(), HitObjectContainer.CreateProxy(), // This ordering (`CatcherArea` before `HitObjectContainer`) is important to diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 6b42e3ddd3..dcab9459ee 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -79,7 +79,8 @@ namespace osu.Game.Rulesets.Catch.UI /// /// Contains objects dropped from the plate. /// - private readonly DroppedObjectContainer droppedObjectTarget; + [Resolved] + private DroppedObjectContainer droppedObjectTarget { get; set; } public CatcherAnimationState CurrentState { @@ -134,10 +135,9 @@ namespace osu.Game.Rulesets.Catch.UI private readonly DrawablePool caughtBananaPool; private readonly DrawablePool caughtDropletPool; - public Catcher([NotNull] Container trailsTarget, [NotNull] DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty = null) + public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null) { this.trailsTarget = trailsTarget; - this.droppedObjectTarget = droppedObjectTarget; Origin = Anchor.TopCentre; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 951a10fc01..fea314df8d 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.UI /// private int currentDirection; - public CatcherArea(DroppedObjectContainer droppedObjectContainer, BeatmapDifficulty difficulty = null) + public CatcherArea(BeatmapDifficulty difficulty = null) { Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE); Children = new Drawable[] @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.UI Margin = new MarginPadding { Bottom = 350f }, X = CatchPlayfield.CENTER_X }, - MovableCatcher = new Catcher(this, droppedObjectContainer, difficulty) { X = CatchPlayfield.CENTER_X }, + MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X }, }; } From c0c1b8d62014bb0c23dad2e3b924051af5467303 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 24 Jun 2021 16:12:43 +0900 Subject: [PATCH 165/457] Fix catcher hyper-dash afterimage is not always displayed --- osu.Game.Rulesets.Catch/UI/CatcherTrail.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs index 80522ab36b..c961d98dc5 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs @@ -37,6 +37,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override void FreeAfterUse() { ClearTransforms(); + Alpha = 1; base.FreeAfterUse(); } } From 4af119a407c649e364fe917fa63049f52b5cb4a1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 16:29:06 +0900 Subject: [PATCH 166/457] Re-namespace --- osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs | 1 + .../Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs | 1 + osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs | 1 + osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs | 1 + .../Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs | 1 + .../Visual/Playlists/TestScenePlaylistsParticipantsList.cs | 1 + osu.Game/Tests/Visual/{ => OnlinePlay}/TestRoomContainer.cs | 2 +- 7 files changed, 7 insertions(+), 1 deletion(-) rename osu.Game/Tests/Visual/{ => OnlinePlay}/TestRoomContainer.cs (96%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs index a8cbd8b7ef..73f7865208 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Screens.OnlinePlay.Lounge.Components; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index 2a254b1573..d114ae8abf 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual.OnlinePlay; using osuTK; namespace osu.Game.Tests.Visual.Multiplayer diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs index 50e6426472..25cdfd4a90 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs @@ -7,6 +7,7 @@ using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Match.Components; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index 566dc9fc00..467df97cb9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -9,6 +9,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Match.Components; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; using osuTK; diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 3055a2c6c7..94aed2ecfa 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Playlists; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Playlists { diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index 083930698f..02ddfe4e79 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; namespace osu.Game.Tests.Visual.Playlists diff --git a/osu.Game/Tests/Visual/TestRoomContainer.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs similarity index 96% rename from osu.Game/Tests/Visual/TestRoomContainer.cs rename to osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs index c4af89cd51..93dc836d23 100644 --- a/osu.Game/Tests/Visual/TestRoomContainer.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.Rooms; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.OnlinePlay { /// /// Contains a that is resolvable by components in test scenes. From 9287fae5f764610063e19976986d5b753e39f786 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 16:54:09 +0900 Subject: [PATCH 167/457] Simplify --- osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs index 93dc836d23..c2abcdfd08 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs @@ -19,19 +19,18 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// public readonly Room Room = new Room(); - [Cached] - private readonly Bindable roomBindable; - public TestRoomContainer() { RelativeSizeAxes = Axes.Both; - roomBindable = new Bindable(Room); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - var dependencies = new CachedModelDependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Model.Value = Room; + var dependencies = new DependencyContainer( + new CachedModelDependencyContainer(parent) { Model = { Value = Room } }); + + dependencies.Cache(new Bindable(Room)); + return dependencies; } } From a7b5c3bed124798ed1a9b46ff391bd4df63337cf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 17:01:28 +0900 Subject: [PATCH 168/457] Add OnlinePlaySubScreenTestScene --- .../TestScenePlaylistsSongSelect.cs | 3 +- .../TestScenePlaylistsRoomSubScreen.cs | 3 +- .../OnlinePlaySubScreenTestScene.cs | 64 +++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index d95a95ebe5..b958afd183 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -20,10 +20,11 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Playlists; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestScenePlaylistsSongSelect : RoomTestScene + public class TestScenePlaylistsSongSelect : OnlinePlaySubScreenTestScene { [Resolved] private BeatmapManager beatmapManager { get; set; } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index a08a91314b..73f9fcd8fd 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -19,12 +19,13 @@ using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; using osuTK.Input; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsRoomSubScreen : RoomTestScene + public class TestScenePlaylistsRoomSubScreen : OnlinePlaySubScreenTestScene { [Cached(typeof(IRoomManager))] private readonly TestRoomManager roomManager = new TestRoomManager(); diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs new file mode 100644 index 0000000000..27cb6496ce --- /dev/null +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Online.Rooms; +using osu.Game.Screens; +using osu.Game.Screens.OnlinePlay; + +namespace osu.Game.Tests.Visual.OnlinePlay +{ + public abstract class OnlinePlaySubScreenTestScene : ScreenTestScene + { + /// + /// The cached . + /// + protected Room Room { get; private set; } + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("create dependencies", () => LoadScreen(new DependenciesScreen(CreateScreenDependencies))); + } + + /// + /// Creates dependencies for any pushed via . + /// Invoked at the start of every test via . + /// + /// + /// This should be overridden to add any custom dependencies required by subclasses of . + /// + /// The parent dependency container. + /// The resultant dependency container. + protected virtual IReadOnlyDependencyContainer CreateScreenDependencies(IReadOnlyDependencyContainer parent) + { + Room = new Room(); + + var dependencies = new DependencyContainer( + new CachedModelDependencyContainer(parent) { Model = { Value = Room } }); + + dependencies.Cache(new Bindable(Room)); + + return dependencies; + } + + /// + /// A dummy screen used for injecting new dependencies into the hierarchy before any screen is pushed via . + /// + private class DependenciesScreen : OsuScreen + { + private readonly Func createDependenciesFunc; + + public DependenciesScreen(Func createDependenciesFunc) + { + this.createDependenciesFunc = createDependenciesFunc; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + => createDependenciesFunc(base.CreateChildDependencies(parent)); + } + } +} From 6922de12c692c7ae6835579d868b1a92100487f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Jun 2021 17:17:12 +0900 Subject: [PATCH 169/457] Add extra null safety in dispose call --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7cc34114bf..d0be03f8c1 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -499,7 +499,7 @@ namespace osu.Game contextFactory.FlushConnections(); - if (Host != null) + if (Host?.UpdateThread != null) Host.UpdateThread.ThreadPausing -= onUpdateThreadPausing; } } From 5115299e9aa3fc7119dfa12784c1bee4a034ddb9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 19:09:31 +0900 Subject: [PATCH 170/457] Merge RoomManagerTestScene into OnlinePlaySubScreenTestScene --- .../Multiplayer/RoomManagerTestScene.cs | 61 ------------ .../Visual/Multiplayer/TestRoomManager.cs | 35 ------- .../TestSceneLoungeRoomsContainer.cs | 17 ++-- .../TestScenePlaylistsSongSelect.cs | 24 ++--- .../TestScenePlaylistsLoungeSubScreen.cs | 6 +- .../TestScenePlaylistsRoomSubScreen.cs | 58 +++-------- .../OnlinePlaySubScreenTestScene.cs | 95 +++++++++++++++++-- .../Visual/OnlinePlay/TestRoomContainer.cs | 2 +- 8 files changed, 129 insertions(+), 169 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs delete mode 100644 osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs deleted file mode 100644 index c665a57452..0000000000 --- a/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Online.Rooms; -using osu.Game.Rulesets; -using osu.Game.Screens.OnlinePlay; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual.Multiplayer -{ - public abstract class RoomManagerTestScene : RoomTestScene - { - [Cached(Type = typeof(IRoomManager))] - protected TestRoomManager RoomManager { get; } = new TestRoomManager(); - - public override void SetUpSteps() - { - base.SetUpSteps(); - - AddStep("clear rooms", () => RoomManager.Rooms.Clear()); - } - - protected void AddRooms(int count, RulesetInfo ruleset = null) - { - AddStep("add rooms", () => - { - for (int i = 0; i < count; i++) - { - var room = new Room - { - RoomID = { Value = i }, - Name = { Value = $"Room {i}" }, - Host = { Value = new User { Username = "Host" } }, - EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, - Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal } - }; - - if (ruleset != null) - { - room.Playlist.Add(new PlaylistItem - { - Ruleset = { Value = ruleset }, - Beatmap = - { - Value = new BeatmapInfo - { - Metadata = new BeatmapMetadata() - } - } - }); - } - - RoomManager.Rooms.Add(room); - } - }); - } - } -} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs deleted file mode 100644 index 1785c99784..0000000000 --- a/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Bindables; -using osu.Game.Online.Rooms; -using osu.Game.Screens.OnlinePlay; - -namespace osu.Game.Tests.Visual.Multiplayer -{ - public class TestRoomManager : IRoomManager - { - public event Action RoomsUpdated - { - add { } - remove { } - } - - public readonly BindableList Rooms = new BindableList(); - - public IBindable InitialRoomsReceived { get; } = new Bindable(true); - - IBindableList IRoomManager.Rooms => Rooms; - - public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) => Rooms.Add(room); - - public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) - { - } - - public void PartRoom() - { - } - } -} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 5682fd5c3c..d63d52a463 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -10,12 +10,13 @@ using osu.Game.Online.Rooms; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Lounge.Components; +using osu.Game.Tests.Visual.OnlinePlay; using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneLoungeRoomsContainer : RoomManagerTestScene + public class TestSceneLoungeRoomsContainer : OnlinePlaySubScreenTestScene { private RoomsContainer container; @@ -34,7 +35,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestBasicListChanges() { - AddRooms(3); + AddStep("add rooms", () => RoomManager.AddRooms(3)); AddAssert("has 3 rooms", () => container.Rooms.Count == 3); AddStep("remove first room", () => RoomManager.Rooms.Remove(RoomManager.Rooms.FirstOrDefault())); @@ -51,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestKeyboardNavigation() { - AddRooms(3); + AddStep("add rooms", () => RoomManager.AddRooms(3)); AddAssert("no selection", () => checkRoomSelected(null)); @@ -72,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestClickDeselection() { - AddRooms(1); + AddStep("add room", () => RoomManager.AddRooms(1)); AddAssert("no selection", () => checkRoomSelected(null)); @@ -91,7 +92,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestStringFiltering() { - AddRooms(4); + AddStep("add rooms", () => RoomManager.AddRooms(4)); AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4); @@ -107,8 +108,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestRulesetFiltering() { - AddRooms(2, new OsuRuleset().RulesetInfo); - AddRooms(3, new CatchRuleset().RulesetInfo); + AddStep("add rooms", () => RoomManager.AddRooms(2, new OsuRuleset().RulesetInfo)); + AddStep("add rooms", () => RoomManager.AddRooms(3, new CatchRuleset().RulesetInfo)); AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5); @@ -121,7 +122,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3); } - private bool checkRoomSelected(Room room) => Room == room; + private bool checkRoomSelected(Room room) => SelectedRoom.Value == room; private void joinRequested(Room room) => room.Status.Value = new JoinedRoomStatus(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index b958afd183..4f10877f95 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -14,6 +14,7 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -86,6 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("reset", () => { + SelectedRoom.Value = new Room(); Ruleset.Value = new OsuRuleset().RulesetInfo; Beatmap.SetDefault(); SelectedMods.Value = Array.Empty(); @@ -99,14 +101,14 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestItemAddedIfEmptyOnStart() { AddStep("finalise selection", () => songSelect.FinaliseSelection()); - AddAssert("playlist has 1 item", () => Room.Playlist.Count == 1); + AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); } [Test] public void TestItemAddedWhenCreateNewItemClicked() { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddAssert("playlist has 1 item", () => Room.Playlist.Count == 1); + AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); } [Test] @@ -114,7 +116,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); AddStep("finalise selection", () => songSelect.FinaliseSelection()); - AddAssert("playlist has 1 item", () => Room.Playlist.Count == 1); + AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); } [Test] @@ -122,7 +124,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddAssert("playlist has 2 items", () => Room.Playlist.Count == 2); + AddAssert("playlist has 2 items", () => SelectedRoom.Value.Playlist.Count == 2); } [Test] @@ -132,13 +134,13 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); AddStep("rearrange", () => { - var item = Room.Playlist[0]; - Room.Playlist.RemoveAt(0); - Room.Playlist.Add(item); + var item = SelectedRoom.Value.Playlist[0]; + SelectedRoom.Value.Playlist.RemoveAt(0); + SelectedRoom.Value.Playlist.Add(item); }); AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddAssert("new item has id 2", () => Room.Playlist.Last().ID == 2); + AddAssert("new item has id 2", () => SelectedRoom.Value.Playlist.Last().ID == 2); } /// @@ -152,8 +154,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change mod rate", () => ((OsuModDoubleTime)SelectedMods.Value[0]).SpeedChange.Value = 2); AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddAssert("item 1 has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value)); - AddAssert("item 2 has rate 2", () => Precision.AlmostEquals(2, ((OsuModDoubleTime)Room.Playlist.Last().RequiredMods[0]).SpeedChange.Value)); + AddAssert("item 1 has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0]).SpeedChange.Value)); + AddAssert("item 2 has rate 2", () => Precision.AlmostEquals(2, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.Last().RequiredMods[0]).SpeedChange.Value)); } /// @@ -175,7 +177,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2); - AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value)); + AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0]).SpeedChange.Value)); } private class TestPlaylistsSongSelect : PlaylistsSongSelect diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index 618447eae2..62a6f40d65 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs @@ -10,11 +10,11 @@ using osu.Game.Graphics.Containers; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Playlists; -using osu.Game.Tests.Visual.Multiplayer; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsLoungeSubScreen : RoomManagerTestScene + public class TestScenePlaylistsLoungeSubScreen : OnlinePlaySubScreenTestScene { private LoungeSubScreen loungeScreen; @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestScrollSelectedIntoView() { - AddRooms(30); + AddStep("add rooms", () => RoomManager.AddRooms(30)); AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms.First())); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index 73f9fcd8fd..98efdd98ef 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -15,7 +15,6 @@ using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; @@ -27,9 +26,6 @@ namespace osu.Game.Tests.Visual.Playlists { public class TestScenePlaylistsRoomSubScreen : OnlinePlaySubScreenTestScene { - [Cached(typeof(IRoomManager))] - private readonly TestRoomManager roomManager = new TestRoomManager(); - private BeatmapManager manager; private RulesetStore rulesets; @@ -59,7 +55,8 @@ namespace osu.Game.Tests.Visual.Playlists [SetUpSteps] public void SetupSteps() { - AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(Room))); + AddStep("set room", () => SelectedRoom.Value = new Room()); + AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom.Value))); AddUntilStep("wait for load", () => match.IsCurrentScreen()); } @@ -68,12 +65,12 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("set room properties", () => { - Room.RoomID.Value = 1; - Room.Name.Value = "my awesome room"; - Room.Host.Value = new User { Id = 2, Username = "peppy" }; - Room.RecentParticipants.Add(Room.Host.Value); - Room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.RoomID.Value = 1; + SelectedRoom.Value.Name.Value = "my awesome room"; + SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; + SelectedRoom.Value.RecentParticipants.Add(SelectedRoom.Value.Host.Value); + SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo } @@ -89,9 +86,9 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("set room properties", () => { - Room.Name.Value = "my awesome room"; - Room.Host.Value = new User { Id = 2, Username = "peppy" }; - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Name.Value = "my awesome room"; + SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo } @@ -105,7 +102,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddAssert("first playlist item selected", () => match.SelectedItem.Value == Room.Playlist[0]); + AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]); } [Test] @@ -123,9 +120,9 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("load room", () => { - Room.Name.Value = "my awesome room"; - Room.Host.Value = new User { Id = 2, Username = "peppy" }; - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Name.Value = "my awesome room"; + SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = importedSet.Beatmaps[0] }, Ruleset = { Value = new OsuRuleset().RulesetInfo } @@ -156,30 +153,5 @@ namespace osu.Game.Tests.Visual.Playlists { } } - - private class TestRoomManager : IRoomManager - { - public event Action RoomsUpdated - { - add => throw new NotImplementedException(); - remove => throw new NotImplementedException(); - } - - public IBindable InitialRoomsReceived { get; } = new Bindable(true); - - public IBindableList Rooms { get; } = new BindableList(); - - public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) - { - room.RoomID.Value = 1; - onSuccess?.Invoke(room); - } - - public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room); - - public void PartRoom() - { - } - } } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs index 27cb6496ce..177f6635a7 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs @@ -2,20 +2,37 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Game.Beatmaps; using osu.Game.Online.Rooms; +using osu.Game.Rulesets; using osu.Game.Screens; using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Lounge.Components; +using osu.Game.Users; namespace osu.Game.Tests.Visual.OnlinePlay { + /// + /// A providing all the dependencies cached by for testing s. + /// public abstract class OnlinePlaySubScreenTestScene : ScreenTestScene { /// - /// The cached . + /// The cached . /// - protected Room Room { get; private set; } + protected Bindable SelectedRoom { get; private set; } + + /// + /// The cached + /// + protected TestRoomManager RoomManager { get; private set; } + + protected Bindable Filter { get; private set; } + + protected OngoingOperationTracker OngoingOperationTracker { get; private set; } public override void SetUpSteps() { @@ -35,16 +52,80 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// The resultant dependency container. protected virtual IReadOnlyDependencyContainer CreateScreenDependencies(IReadOnlyDependencyContainer parent) { - Room = new Room(); + SelectedRoom = new Bindable(); + RoomManager = new TestRoomManager(); + Filter = new Bindable(new FilterCriteria()); + OngoingOperationTracker = new OngoingOperationTracker(); - var dependencies = new DependencyContainer( - new CachedModelDependencyContainer(parent) { Model = { Value = Room } }); - - dependencies.Cache(new Bindable(Room)); + var dependencies = new DependencyContainer(new CachedModelDependencyContainer(parent) { Model = { BindTarget = SelectedRoom } }); + dependencies.CacheAs(SelectedRoom); + dependencies.CacheAs(RoomManager); + dependencies.CacheAs(Filter); + dependencies.CacheAs(OngoingOperationTracker); return dependencies; } + protected class TestRoomManager : IRoomManager + { + public event Action RoomsUpdated + { + add { } + remove { } + } + + public readonly BindableList Rooms = new BindableList(); + + public IBindable InitialRoomsReceived { get; } = new Bindable(true); + + IBindableList IRoomManager.Rooms => Rooms; + + public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) + { + room.RoomID.Value ??= Rooms.Select(r => r.RoomID.Value).Where(id => id != null).Select(id => id.Value).DefaultIfEmpty().Max() + 1; + Rooms.Add(room); + onSuccess?.Invoke(room); + } + + public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room); + + public void PartRoom() + { + } + + public void AddRooms(int count, RulesetInfo ruleset = null) + { + for (int i = 0; i < count; i++) + { + var room = new Room + { + RoomID = { Value = i }, + Name = { Value = $"Room {i}" }, + Host = { Value = new User { Username = "Host" } }, + EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, + Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal } + }; + + if (ruleset != null) + { + room.Playlist.Add(new PlaylistItem + { + Ruleset = { Value = ruleset }, + Beatmap = + { + Value = new BeatmapInfo + { + Metadata = new BeatmapMetadata() + } + } + }); + } + + CreateRoom(room); + } + } + } + /// /// A dummy screen used for injecting new dependencies into the hierarchy before any screen is pushed via . /// diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs index c2abcdfd08..7ff2e5133e 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs @@ -10,7 +10,7 @@ using osu.Game.Online.Rooms; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// Contains a that is resolvable by components in test scenes. + /// Contains a single that is resolvable by components in test scenes. /// public class TestRoomContainer : Container { From a21cf87b5fcee946600bbf22100397094383fbfe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 19:13:50 +0900 Subject: [PATCH 171/457] Split room manager and allow overrides --- .../TestSceneLoungeRoomsContainer.cs | 5 ++ .../TestScenePlaylistsLoungeSubScreen.cs | 9 +-- .../OnlinePlaySubScreenTestScene.cs | 70 +----------------- .../Visual/OnlinePlay/TestBasicRoomManager.cs | 74 +++++++++++++++++++ 4 files changed, 87 insertions(+), 71 deletions(-) create mode 100644 osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index d63d52a463..22e7acce44 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -9,6 +9,7 @@ using osu.Game.Graphics; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Tests.Visual.OnlinePlay; using osuTK.Graphics; @@ -18,6 +19,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneLoungeRoomsContainer : OnlinePlaySubScreenTestScene { + protected new TestBasicRoomManager RoomManager => (TestBasicRoomManager)base.RoomManager; + private RoomsContainer container; [BackgroundDependencyLoader] @@ -32,6 +35,8 @@ namespace osu.Game.Tests.Visual.Multiplayer }; } + protected override IRoomManager CreateRoomManager() => new TestBasicRoomManager(); + [Test] public void TestBasicListChanges() { diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index 62a6f40d65..34e66b9cb2 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs @@ -3,10 +3,10 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Graphics.Containers; +using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Playlists; @@ -16,12 +16,11 @@ namespace osu.Game.Tests.Visual.Playlists { public class TestScenePlaylistsLoungeSubScreen : OnlinePlaySubScreenTestScene { + protected new TestBasicRoomManager RoomManager => (TestBasicRoomManager)base.RoomManager; + private LoungeSubScreen loungeScreen; - [BackgroundDependencyLoader] - private void load() - { - } + protected override IRoomManager CreateRoomManager() => new TestBasicRoomManager(); public override void SetUpSteps() { diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs index 177f6635a7..c4a8658d93 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs @@ -2,16 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Beatmaps; using osu.Game.Online.Rooms; -using osu.Game.Rulesets; using osu.Game.Screens; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; -using osu.Game.Users; namespace osu.Game.Tests.Visual.OnlinePlay { @@ -28,7 +24,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// The cached /// - protected TestRoomManager RoomManager { get; private set; } + protected IRoomManager RoomManager { get; private set; } protected Bindable Filter { get; private set; } @@ -53,78 +49,20 @@ namespace osu.Game.Tests.Visual.OnlinePlay protected virtual IReadOnlyDependencyContainer CreateScreenDependencies(IReadOnlyDependencyContainer parent) { SelectedRoom = new Bindable(); - RoomManager = new TestRoomManager(); + RoomManager = CreateRoomManager(); Filter = new Bindable(new FilterCriteria()); OngoingOperationTracker = new OngoingOperationTracker(); var dependencies = new DependencyContainer(new CachedModelDependencyContainer(parent) { Model = { BindTarget = SelectedRoom } }); dependencies.CacheAs(SelectedRoom); - dependencies.CacheAs(RoomManager); + dependencies.CacheAs(RoomManager); dependencies.CacheAs(Filter); dependencies.CacheAs(OngoingOperationTracker); return dependencies; } - protected class TestRoomManager : IRoomManager - { - public event Action RoomsUpdated - { - add { } - remove { } - } - - public readonly BindableList Rooms = new BindableList(); - - public IBindable InitialRoomsReceived { get; } = new Bindable(true); - - IBindableList IRoomManager.Rooms => Rooms; - - public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) - { - room.RoomID.Value ??= Rooms.Select(r => r.RoomID.Value).Where(id => id != null).Select(id => id.Value).DefaultIfEmpty().Max() + 1; - Rooms.Add(room); - onSuccess?.Invoke(room); - } - - public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room); - - public void PartRoom() - { - } - - public void AddRooms(int count, RulesetInfo ruleset = null) - { - for (int i = 0; i < count; i++) - { - var room = new Room - { - RoomID = { Value = i }, - Name = { Value = $"Room {i}" }, - Host = { Value = new User { Username = "Host" } }, - EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, - Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal } - }; - - if (ruleset != null) - { - room.Playlist.Add(new PlaylistItem - { - Ruleset = { Value = ruleset }, - Beatmap = - { - Value = new BeatmapInfo - { - Metadata = new BeatmapMetadata() - } - } - }); - } - - CreateRoom(room); - } - } - } + protected virtual IRoomManager CreateRoomManager() => new TestBasicRoomManager(); /// /// A dummy screen used for injecting new dependencies into the hierarchy before any screen is pushed via . diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs new file mode 100644 index 0000000000..fd81072651 --- /dev/null +++ b/osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Online.Rooms; +using osu.Game.Rulesets; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.OnlinePlay +{ + public class TestBasicRoomManager : IRoomManager + { + public event Action RoomsUpdated + { + add { } + remove { } + } + + public readonly BindableList Rooms = new BindableList(); + + public IBindable InitialRoomsReceived { get; } = new Bindable(true); + + IBindableList IRoomManager.Rooms => Rooms; + + public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) + { + room.RoomID.Value ??= Rooms.Select(r => r.RoomID.Value).Where(id => id != null).Select(id => id.Value).DefaultIfEmpty().Max() + 1; + Rooms.Add(room); + onSuccess?.Invoke(room); + } + + public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room); + + public void PartRoom() + { + } + + public void AddRooms(int count, RulesetInfo ruleset = null) + { + for (int i = 0; i < count; i++) + { + var room = new Room + { + RoomID = { Value = i }, + Name = { Value = $"Room {i}" }, + Host = { Value = new User { Username = "Host" } }, + EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, + Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal } + }; + + if (ruleset != null) + { + room.Playlist.Add(new PlaylistItem + { + Ruleset = { Value = ruleset }, + Beatmap = + { + Value = new BeatmapInfo + { + Metadata = new BeatmapMetadata() + } + } + }); + } + + CreateRoom(room); + } + } + } +} From 9cf2c9ddaef526ec9cc3ed8f3b08e8247e1b1765 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 21:02:21 +0900 Subject: [PATCH 172/457] Fix incorrect dependency creation --- osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs index 7ff2e5133e..d75281e772 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer( - new CachedModelDependencyContainer(parent) { Model = { Value = Room } }); + new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) { Model = { Value = Room } }); dependencies.Cache(new Bindable(Room)); From 5820793d1119f82bfeb772a835cfdd220277a356 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 20:44:48 +0900 Subject: [PATCH 173/457] Make TestMultiplayerRoomContainer inherit TestRoomContainer --- .../Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs index 1abf4d8f5d..d44b55c3c1 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs @@ -8,10 +8,11 @@ using osu.Framework.Graphics.Containers; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestMultiplayerRoomContainer : Container + public class TestMultiplayerRoomContainer : TestRoomContainer { protected override Container Content => content; private readonly Container content; From 8fba7d2423774f35c5e1182c4dcfe22992f9dad9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 21:02:04 +0900 Subject: [PATCH 174/457] Remove MultiplayerTestScene inheritance from simple test scenes --- .../TestSceneFreeModSelectOverlay.cs | 11 +- .../TestSceneMultiSpectatorLeaderboard.cs | 112 +++++++++--------- 2 files changed, 66 insertions(+), 57 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 26a0301d8a..25de2d0df3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -7,14 +7,17 @@ using osu.Game.Screens.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneFreeModSelectOverlay : MultiplayerTestScene + public class TestSceneFreeModSelectOverlay : OsuTestScene { [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Child = new FreeModSelectOverlay + Child = new TestMultiplayerRoomContainer { - State = { Value = Visibility.Visible } + Child = new FreeModSelectOverlay + { + State = { Value = Visibility.Visible } + } }; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 5ad35be0ec..9ec85d360f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -1,15 +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 System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Framework.Threading; using osu.Framework.Timing; using osu.Game.Database; using osu.Game.Online.Spectator; @@ -21,61 +24,38 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene + public class TestSceneMultiSpectatorLeaderboard : OsuTestScene { - [Cached(typeof(SpectatorClient))] - private TestSpectatorClient spectatorClient = new TestSpectatorClient(); + private Dictionary clocks; - [Cached(typeof(UserLookupCache))] - private UserLookupCache lookupCache = new TestUserLookupCache(); - - protected override Container Content => content; - private readonly Container content; - - private readonly Dictionary clocks = new Dictionary - { - { PLAYER_1_ID, new ManualClock() }, - { PLAYER_2_ID, new ManualClock() } - }; - - public TestSceneMultiSpectatorLeaderboard() - { - base.Content.AddRange(new Drawable[] - { - spectatorClient, - lookupCache, - content = new Container { RelativeSizeAxes = Axes.Both } - }); - } + private MultiSpectatorLeaderboard leaderboard; + private TestContainer container; [SetUpSteps] - public new void SetUpSteps() + public void SetUpSteps() { - MultiSpectatorLeaderboard leaderboard = null; - AddStep("reset", () => { - Clear(); - - foreach (var (userId, clock) in clocks) + clocks = new Dictionary { - spectatorClient.EndPlay(userId); - clock.CurrentTime = 0; - } + { MultiplayerTestScene.PLAYER_1_ID, new ManualClock() }, + { MultiplayerTestScene.PLAYER_2_ID, new ManualClock() } + }; + + Child = container = new TestContainer(); + + foreach (var (userId, _) in clocks) + container.SpectatorClient.StartPlay(userId, 0); }); AddStep("create leaderboard", () => { - foreach (var (userId, _) in clocks) - spectatorClient.StartPlay(userId, 0); - Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); - var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); var scoreProcessor = new OsuScoreProcessor(); scoreProcessor.ApplyBeatmap(playable); - LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, Add); + container.LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, container.Add); }); AddUntilStep("wait for load", () => leaderboard.IsLoaded); @@ -96,42 +76,42 @@ namespace osu.Game.Tests.Visual.Multiplayer // For player 2, send frames in sets of 10. for (int i = 0; i < 100; i++) { - spectatorClient.SendFrames(PLAYER_1_ID, i, 1); + container.SpectatorClient.SendFrames(MultiplayerTestScene.PLAYER_1_ID, i, 1); if (i % 10 == 0) - spectatorClient.SendFrames(PLAYER_2_ID, i, 10); + container.SpectatorClient.SendFrames(MultiplayerTestScene.PLAYER_2_ID, i, 10); } }); - assertCombo(PLAYER_1_ID, 1); - assertCombo(PLAYER_2_ID, 10); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 1); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 10); // Advance to a point where only user player 1's frame changes. setTime(500); - assertCombo(PLAYER_1_ID, 5); - assertCombo(PLAYER_2_ID, 10); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 5); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 10); // Advance to a point where both user's frame changes. setTime(1100); - assertCombo(PLAYER_1_ID, 11); - assertCombo(PLAYER_2_ID, 20); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 11); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 20); // Advance user player 2 only to a point where its frame changes. - setTime(PLAYER_2_ID, 2100); - assertCombo(PLAYER_1_ID, 11); - assertCombo(PLAYER_2_ID, 30); + setTime(MultiplayerTestScene.PLAYER_2_ID, 2100); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 11); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 30); // Advance both users beyond their last frame setTime(101 * 100); - assertCombo(PLAYER_1_ID, 100); - assertCombo(PLAYER_2_ID, 100); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 100); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 100); } [Test] public void TestNoFrames() { - assertCombo(PLAYER_1_ID, 0); - assertCombo(PLAYER_2_ID, 0); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 0); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 0); } private void setTime(double time) => AddStep($"set time {time}", () => @@ -146,6 +126,32 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertCombo(int userId, int expectedCombo) => AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo); + private class TestContainer : TestMultiplayerRoomContainer + { + [Cached(typeof(SpectatorClient))] + public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); + + [Cached(typeof(UserLookupCache))] + public readonly UserLookupCache LookupCache = new TestUserLookupCache(); + + protected override Container Content => content; + private readonly Container content; + + public TestContainer() + { + AddRangeInternal(new Drawable[] + { + SpectatorClient, + LookupCache, + content = new Container { RelativeSizeAxes = Axes.Both } + }); + } + + public new Task LoadComponentAsync([NotNull] TLoadable component, Action onLoaded = null, CancellationToken cancellation = default, Scheduler scheduler = null) + where TLoadable : Drawable + => base.LoadComponentAsync(component, onLoaded, cancellation, scheduler); + } + private class TestUserLookupCache : UserLookupCache { protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) From 3fcda83713111f24fc1436adfdd588d2aa6cea3a Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 22:00:19 +0800 Subject: [PATCH 175/457] Rename `VectorHelper` to `VectorUtils` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 2 +- osu.Game.Rulesets.Osu/Utils/{VectorHelper.cs => VectorUtils.cs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Osu/Utils/{VectorHelper.cs => VectorUtils.cs} (99%) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index bcd5274e02..7c7e88cbba 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Mods distanceToPrev * (float)Math.Sin(current.AngleRad) ); - posRelativeToPrev = VectorHelper.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev); + posRelativeToPrev = VectorUtils.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev); current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); diff --git a/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs b/osu.Game.Rulesets.Osu/Utils/VectorUtils.cs similarity index 99% rename from osu.Game.Rulesets.Osu/Utils/VectorHelper.cs rename to osu.Game.Rulesets.Osu/Utils/VectorUtils.cs index e3fe3249e8..a829c89636 100644 --- a/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs +++ b/osu.Game.Rulesets.Osu/Utils/VectorUtils.cs @@ -7,7 +7,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Utils { - public static class VectorHelper + public static class VectorUtils { // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. // The closer the hit objects draw to the border, the sharper the turn From 4fe30ca9235d1fa1937879b2896cdddb7a3c21c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 24 Jun 2021 18:31:13 +0200 Subject: [PATCH 176/457] Remove Traditional Chinese (HK) language option Due to no actual existing translations it was falling back to Simplified Chinese instead, making the option actively wrong/confusing. --- osu.Game/Localisation/Language.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs index 3c66f31c58..dc1fac47a8 100644 --- a/osu.Game/Localisation/Language.cs +++ b/osu.Game/Localisation/Language.cs @@ -103,8 +103,11 @@ namespace osu.Game.Localisation [Description(@"简体中文")] zh, - [Description(@"繁體中文(香港)")] - zh_hk, + // Traditional Chinese (Hong Kong) is listed in web sources but has no associated localisations, + // and was wrongly falling back to Simplified Chinese. + // Can be revisited if localisations ever arrive. + // [Description(@"繁體中文(香港)")] + // zh_hk, [Description(@"繁體中文(台灣)")] zh_tw From 62566f2a4a0caf84a211e6cf7469f04abfa5900f Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Thu, 24 Jun 2021 14:29:47 -0700 Subject: [PATCH 177/457] Remove "Score Multiplier" text --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e31e307d4d..85dcade986 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -330,22 +330,12 @@ namespace osu.Game.Overlays.Mods Spacing = new Vector2(footer_button_spacing / 2, 0), Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - Children = new Drawable[] + Child = MultiplierLabel = new OsuSpriteText { - new OsuSpriteText - { - Text = @"Score Multiplier:", - Font = OsuFont.GetFont(size: 30), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - }, - MultiplierLabel = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Width = 70, // make width fixed so reflow doesn't occur when multiplier number changes. - }, + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Width = 70, // make width fixed so reflow doesn't occur when multiplier number changes. }, }, } From 34ace2553e5b8db36be5dd0447bac569eeb2d843 Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Thu, 24 Jun 2021 14:32:00 -0700 Subject: [PATCH 178/457] Revert "Refactor the menu's max height to be a property" This reverts commit 9cb9ef5c --- osu.Game/Overlays/Settings/SettingsEnumDropdown.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index fa9973fb08..9987a0c607 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -14,15 +14,13 @@ namespace osu.Game.Overlays.Settings protected new class DropdownControl : OsuEnumDropdown { - protected virtual int MenuMaxHeight => 200; - public DropdownControl() { Margin = new MarginPadding { Top = 5 }; RelativeSizeAxes = Axes.X; } - protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = MenuMaxHeight); + protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 200); } } } From 93edb25acefe5004a7fd4e032d8a7696ca02bbdd Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Thu, 24 Jun 2021 14:32:43 -0700 Subject: [PATCH 179/457] Override `CreateMenu` instead of using a property --- 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 cee5883d9a..55636495df 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -192,8 +192,8 @@ namespace osu.Game.Configuration private class ModDropdownControl : DropdownControl { - // Set low enough to workaround nested scroll issues (see https://github.com/ppy/osu-framework/issues/4536). - protected override int MenuMaxHeight => 100; + // Set menu's max height low enough to workaround nested scroll issues (see https://github.com/ppy/osu-framework/issues/4536). + protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 100); } } } From 26086ca1efc5107ab3c125a24a146db17940e1bb Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 25 Jun 2021 09:43:14 +0800 Subject: [PATCH 180/457] Rename `VectorUtils` to `OsuHitObjectGenerationUtils` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 2 +- .../Utils/{VectorUtils.cs => OsuHitObjectGenerationUtils.cs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Osu/Utils/{VectorUtils.cs => OsuHitObjectGenerationUtils.cs} (98%) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 7c7e88cbba..d1212096bf 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Mods distanceToPrev * (float)Math.Sin(current.AngleRad) ); - posRelativeToPrev = VectorUtils.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev); + posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev); current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); diff --git a/osu.Game.Rulesets.Osu/Utils/VectorUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs similarity index 98% rename from osu.Game.Rulesets.Osu/Utils/VectorUtils.cs rename to osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index a829c89636..2a60124757 100644 --- a/osu.Game.Rulesets.Osu/Utils/VectorUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -7,7 +7,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Utils { - public static class VectorUtils + public static class OsuHitObjectGenerationUtils { // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. // The closer the hit objects draw to the border, the sharper the turn From ec8810cc2b96e92108c5fd365366c0fe8e9d97be Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 25 Jun 2021 09:44:23 +0800 Subject: [PATCH 181/457] Use `MathF` instead of `(float)Math` --- .../Utils/OsuHitObjectGenerationUtils.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 2a60124757..06b964a647 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -84,20 +84,20 @@ namespace osu.Game.Rulesets.Osu.Utils /// The rotated vector. public static Vector2 RotateVectorTowardsVector(Vector2 initial, Vector2 destination, float rotationRatio) { - var initialAngleRad = Math.Atan2(initial.Y, initial.X); - var destAngleRad = Math.Atan2(destination.Y, destination.X); + var initialAngleRad = MathF.Atan2(initial.Y, initial.X); + var destAngleRad = MathF.Atan2(destination.Y, destination.X); var diff = destAngleRad - initialAngleRad; - while (diff < -Math.PI) diff += 2 * Math.PI; + while (diff < -MathF.PI) diff += 2 * MathF.PI; - while (diff > Math.PI) diff -= 2 * Math.PI; + while (diff > MathF.PI) diff -= 2 * MathF.PI; var finalAngleRad = initialAngleRad + rotationRatio * diff; return new Vector2( - initial.Length * (float)Math.Cos(finalAngleRad), - initial.Length * (float)Math.Sin(finalAngleRad) + initial.Length * MathF.Cos(finalAngleRad), + initial.Length * MathF.Sin(finalAngleRad) ); } } From 31c786e1c306e1b0a5161e644b2d12009cc91143 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 25 Jun 2021 09:51:34 +0800 Subject: [PATCH 182/457] Use `SettingsNumberBox.NumberBox` instead of `OsuNumberBox` --- osu.Game/Rulesets/Mods/SeedSettingsControl.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs index 5c57717d93..1280197532 100644 --- a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs @@ -5,7 +5,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods @@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Mods } } - private readonly OsuNumberBox seedNumberBox; + private readonly SettingsNumberBox.NumberBox seedNumberBox; public SeedControl() { @@ -61,7 +60,7 @@ namespace osu.Game.Rulesets.Mods { new Drawable[] { - seedNumberBox = new OsuNumberBox + seedNumberBox = new SettingsNumberBox.NumberBox { RelativeSizeAxes = Axes.X, CommitOnFocusLost = true From aa5d22d04a4ecf0c0bc315ae7ce3ad1fd7b50b32 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 13:02:19 +0900 Subject: [PATCH 183/457] Remove "test container", make everything go through OnlinePlayTestScene --- .../Multiplayer/TestSceneLoungeRoomInfo.cs | 32 +++--- .../TestSceneLoungeRoomsContainer.cs | 7 +- .../TestSceneMatchBeatmapDetailArea.cs | 25 ++--- .../Multiplayer/TestSceneMatchHeader.cs | 19 ++-- .../Multiplayer/TestSceneMatchLeaderboard.cs | 20 ++-- .../TestSceneMultiplayerMatchFooter.cs | 16 +-- .../TestScenePlaylistsSongSelect.cs | 2 +- .../TestScenePlaylistsLoungeSubScreen.cs | 7 +- .../TestScenePlaylistsMatchSettingsOverlay.cs | 49 +++++---- .../TestScenePlaylistsParticipantsList.cs | 18 ++-- .../TestScenePlaylistsRoomSubScreen.cs | 2 +- .../IMultiplayerRoomTestDependencies.cs | 22 ++++ .../MultiplayerRoomTestDependencies.cs | 23 ++++ .../MultiplayerSubScreenTestScene.cs | 17 +++ .../TestMultiplayerRoomContainer.cs | 3 +- ...RoomManager.cs => BasicTestRoomManager.cs} | 2 +- .../OnlinePlay/IRoomTestDependencies.cs | 38 +++++++ .../OnlinePlaySubScreenTestScene.cs | 83 --------------- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 100 ++++++++++++++++++ .../Visual/OnlinePlay/RoomTestDependencies.cs | 78 ++++++++++++++ .../Visual/OnlinePlay/TestRoomContainer.cs | 37 ------- 21 files changed, 370 insertions(+), 230 deletions(-) create mode 100644 osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs create mode 100644 osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs create mode 100644 osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs rename osu.Game/Tests/Visual/OnlinePlay/{TestBasicRoomManager.cs => BasicTestRoomManager.cs} (97%) create mode 100644 osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs delete mode 100644 osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs create mode 100644 osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs create mode 100644 osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs delete mode 100644 osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs index 73f7865208..471d0b6c98 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs @@ -4,6 +4,7 @@ using System; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Tests.Visual.OnlinePlay; @@ -11,28 +12,25 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneLoungeRoomInfo : OsuTestScene + public class TestSceneLoungeRoomInfo : OnlinePlayTestScene { - private TestRoomContainer roomContainer; - [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = roomContainer = new TestRoomContainer + SelectedRoom.Value = new Room(); + + Child = new RoomInfo { - Child = new RoomInfo - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 500 - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500 }; }); [Test] public void TestNonSelectedRoom() { - AddStep("set null room", () => roomContainer.Room.RoomID.Value = null); + AddStep("set null room", () => SelectedRoom.Value.RoomID.Value = null); } [Test] @@ -40,11 +38,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set open room", () => { - roomContainer.Room.RoomID.Value = 0; - roomContainer.Room.Name.Value = "Room 0"; - roomContainer.Room.Host.Value = new User { Username = "peppy", Id = 2 }; - roomContainer.Room.EndDate.Value = DateTimeOffset.Now.AddMonths(1); - roomContainer.Room.Status.Value = new RoomStatusOpen(); + SelectedRoom.Value.RoomID.Value = 0; + SelectedRoom.Value.Name.Value = "Room 0"; + SelectedRoom.Value.Host.Value = new User { Username = "peppy", Id = 2 }; + SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMonths(1); + SelectedRoom.Value.Status.Value = new RoomStatusOpen(); }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 22e7acce44..a78cff0650 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -9,7 +9,6 @@ using osu.Game.Graphics; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Tests.Visual.OnlinePlay; using osuTK.Graphics; @@ -17,9 +16,9 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneLoungeRoomsContainer : OnlinePlaySubScreenTestScene + public class TestSceneLoungeRoomsContainer : OnlinePlayTestScene { - protected new TestBasicRoomManager RoomManager => (TestBasicRoomManager)base.RoomManager; + protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager; private RoomsContainer container; @@ -35,8 +34,6 @@ namespace osu.Game.Tests.Visual.Multiplayer }; } - protected override IRoomManager CreateRoomManager() => new TestBasicRoomManager(); - [Test] public void TestBasicListChanges() { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index d114ae8abf..d66603a448 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -16,7 +16,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchBeatmapDetailArea : OsuTestScene + public class TestSceneMatchBeatmapDetailArea : OnlinePlayTestScene { [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -24,28 +24,25 @@ namespace osu.Game.Tests.Visual.Multiplayer [Resolved] private RulesetStore rulesetStore { get; set; } - private TestRoomContainer roomContainer; - [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = roomContainer = new TestRoomContainer + SelectedRoom.Value = new Room(); + + Child = new MatchBeatmapDetailArea { - Child = new MatchBeatmapDetailArea - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500), - CreateNewItem = createNewItem - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500), + CreateNewItem = createNewItem }; }); private void createNewItem() { - roomContainer.Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem { - ID = roomContainer.Room.Playlist.Count, + ID = SelectedRoom.Value.Playlist.Count, Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, RequiredMods = diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs index 25cdfd4a90..3557bd9127 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs @@ -12,17 +12,14 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchHeader : OsuTestScene + public class TestSceneMatchHeader : OnlinePlayTestScene { - private TestRoomContainer roomContainer; - [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = roomContainer = new TestRoomContainer - { - Child = new Header() - }; + SelectedRoom.Value = new Room(); + + Child = new Header(); }); [Test] @@ -30,7 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set basic room", () => { - roomContainer.Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { @@ -54,8 +51,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - roomContainer.Room.Name.Value = "A very awesome room"; - roomContainer.Room.Host.Value = new User { Id = 2, Username = "peppy" }; + SelectedRoom.Value.Name.Value = "A very awesome room"; + SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index 467df97cb9..a7a5f3af39 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchLeaderboard : OsuTestScene + public class TestSceneMatchLeaderboard : OnlinePlayTestScene { [BackgroundDependencyLoader] private void load() @@ -59,18 +59,16 @@ namespace osu.Game.Tests.Visual.Multiplayer } [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = new TestRoomContainer + SelectedRoom.Value = new Room { RoomID = { Value = 3 } }; + + Child = new MatchLeaderboard { - Room = { RoomID = { Value = 3 } }, - Child = new MatchLeaderboard - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(550f, 450f), - Scope = MatchLeaderboardScope.Overall, - } + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(550f, 450f), + Scope = MatchLeaderboardScope.Overall, }; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs index 6b03b53b4b..afe66d2686 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs @@ -1,27 +1,27 @@ // 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 NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiplayerMatchFooter : MultiplayerTestScene + public class TestSceneMultiplayerMatchFooter : OnlinePlayTestScene { - [Cached] - private readonly OnlinePlayBeatmapAvailabilityTracker availablilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); - - [BackgroundDependencyLoader] - private void load() + [SetUp] + public new void Setup() => Schedule(() => { + SelectedRoom.Value = new Room(); + Child = new MultiplayerMatchFooter { Anchor = Anchor.Centre, Origin = Anchor.Centre, Height = 50 }; - } + }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 4f10877f95..e4bf9b36ed 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -25,7 +25,7 @@ using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestScenePlaylistsSongSelect : OnlinePlaySubScreenTestScene + public class TestScenePlaylistsSongSelect : OnlinePlayTestScene { [Resolved] private BeatmapManager beatmapManager { get; set; } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index 34e66b9cb2..b16b61c5c7 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Graphics.Containers; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Playlists; @@ -14,14 +13,12 @@ using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsLoungeSubScreen : OnlinePlaySubScreenTestScene + public class TestScenePlaylistsLoungeSubScreen : OnlinePlayTestScene { - protected new TestBasicRoomManager RoomManager => (TestBasicRoomManager)base.RoomManager; + protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager; private LoungeSubScreen loungeScreen; - protected override IRoomManager CreateRoomManager() => new TestBasicRoomManager(); - public override void SetUpSteps() { base.SetUpSteps(); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 94aed2ecfa..b52b6a6a0e 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -3,7 +3,6 @@ using System; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,24 +15,23 @@ using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsMatchSettingsOverlay : OsuTestScene + public class TestScenePlaylistsMatchSettingsOverlay : OnlinePlayTestScene { - [Cached(Type = typeof(IRoomManager))] - private TestRoomManager roomManager = new TestRoomManager(); + protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; - private TestRoomContainer roomContainer; private TestRoomSettings settings; + protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = roomContainer = new TestRoomContainer + SelectedRoom.Value = new Room(); + + Child = settings = new TestRoomSettings { - Child = settings = new TestRoomSettings - { - RelativeSizeAxes = Axes.Both, - State = { Value = Visibility.Visible } - } + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible } }; }); @@ -42,19 +40,19 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("clear name and beatmap", () => { - roomContainer.Room.Name.Value = ""; - roomContainer.Room.Playlist.Clear(); + SelectedRoom.Value.Name.Value = ""; + SelectedRoom.Value.Playlist.Clear(); }); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set name", () => roomContainer.Room.Name.Value = "Room name"); + AddStep("set name", () => SelectedRoom.Value.Name.Value = "Room name"); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set beatmap", () => roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } })); + AddStep("set beatmap", () => SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } })); AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value); - AddStep("clear name", () => roomContainer.Room.Name.Value = ""); + AddStep("clear name", () => SelectedRoom.Value.Name.Value = ""); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); } @@ -70,9 +68,9 @@ namespace osu.Game.Tests.Visual.Playlists { settings.NameField.Current.Value = expected_name; settings.DurationField.Current.Value = expectedDuration; - roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); - roomManager.CreateRequested = r => + RoomManager.CreateRequested = r => { createdRoom = r; return true; @@ -91,11 +89,11 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("setup", () => { - roomContainer.Room.Name.Value = "Test Room"; - roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); + SelectedRoom.Value.Name.Value = "Test Room"; + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); fail = true; - roomManager.CreateRequested = _ => !fail; + RoomManager.CreateRequested = _ => !fail; }); AddAssert("error not displayed", () => !settings.ErrorText.IsPresent); @@ -122,7 +120,12 @@ namespace osu.Game.Tests.Visual.Playlists public OsuSpriteText ErrorText => ((MatchSettings)Settings).ErrorText; } - private class TestRoomManager : IRoomManager + private class TestDependencies : RoomTestDependencies + { + protected override IRoomManager CreateRoomManager() => new TestRoomManager(); + } + + protected class TestRoomManager : IRoomManager { public const string FAILED_TEXT = "failed"; diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index 02ddfe4e79..76a78c0a3c 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -3,27 +3,23 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsParticipantsList : OsuTestScene + public class TestScenePlaylistsParticipantsList : OnlinePlayTestScene { - private TestRoomContainer roomContainer; - [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = roomContainer = new TestRoomContainer - { - Room = { RoomID = { Value = 7 } } - }; + SelectedRoom.Value = new Room { RoomID = { Value = 7 } }; for (int i = 0; i < 50; i++) { - roomContainer.Room.RecentParticipants.Add(new User + SelectedRoom.Value.RecentParticipants.Add(new User { Username = "peppy", Statistics = new UserStatistics { GlobalRank = 1234 }, @@ -37,7 +33,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - roomContainer.Child = new ParticipantsDisplay(Direction.Horizontal) + Child = new ParticipantsDisplay(Direction.Horizontal) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -51,7 +47,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - roomContainer.Child = new ParticipantsDisplay(Direction.Vertical) + Child = new ParticipantsDisplay(Direction.Vertical) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index 98efdd98ef..a0096c823f 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -24,7 +24,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsRoomSubScreen : OnlinePlaySubScreenTestScene + public class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene { private BeatmapManager manager; private RulesetStore rulesets; diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs new file mode 100644 index 0000000000..0b53bdd04e --- /dev/null +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Online.Multiplayer; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Tests.Visual.OnlinePlay; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public interface IMultiplayerRoomTestDependencies : IRoomTestDependencies + { + /// + /// The cached . + /// + TestMultiplayerClient Client { get; } + + /// + /// The cached . + /// + new TestMultiplayerRoomManager RoomManager { get; } + } +} diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs new file mode 100644 index 0000000000..e3913dd291 --- /dev/null +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.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.Game.Online.Multiplayer; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Tests.Visual.OnlinePlay; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class MultiplayerRoomTestDependencies : RoomTestDependencies, IMultiplayerRoomTestDependencies + { + public TestMultiplayerClient Client { get; } + public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; + + public MultiplayerRoomTestDependencies() + { + Client = new TestMultiplayerClient(RoomManager); + CacheAs(Client); + } + + protected override IRoomManager CreateRoomManager() => new TestMultiplayerRoomManager(); + } +} diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs new file mode 100644 index 0000000000..5c405ee9d3 --- /dev/null +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Tests.Visual.OnlinePlay; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class MultiplayerSubScreenTestScene : OnlinePlayTestScene, IMultiplayerRoomTestDependencies + { + public TestMultiplayerClient Client => RoomDependencies.Client; + public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; + + protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; + + protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); + } +} diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs index d44b55c3c1..1abf4d8f5d 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs @@ -8,11 +8,10 @@ using osu.Framework.Graphics.Containers; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; -using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestMultiplayerRoomContainer : TestRoomContainer + public class TestMultiplayerRoomContainer : Container { protected override Container Content => content; private readonly Container content; diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs similarity index 97% rename from osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs rename to osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs index fd81072651..67beea9117 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs @@ -12,7 +12,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.OnlinePlay { - public class TestBasicRoomManager : IRoomManager + public class BasicTestRoomManager : IRoomManager { public event Action RoomsUpdated { diff --git a/osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs new file mode 100644 index 0000000000..848e2aa77f --- /dev/null +++ b/osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs @@ -0,0 +1,38 @@ +// 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.Online.Rooms; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Lounge.Components; + +namespace osu.Game.Tests.Visual.OnlinePlay +{ + public interface IRoomTestDependencies + { + /// + /// The cached . + /// + Bindable SelectedRoom { get; } + + /// + /// The cached + /// + IRoomManager RoomManager { get; } + + /// + /// The cached . + /// + Bindable Filter { get; } + + /// + /// The cached . + /// + OngoingOperationTracker OngoingOperationTracker { get; } + + /// + /// The cached . + /// + OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; } + } +} diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs deleted file mode 100644 index c4a8658d93..0000000000 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Game.Online.Rooms; -using osu.Game.Screens; -using osu.Game.Screens.OnlinePlay; -using osu.Game.Screens.OnlinePlay.Lounge.Components; - -namespace osu.Game.Tests.Visual.OnlinePlay -{ - /// - /// A providing all the dependencies cached by for testing s. - /// - public abstract class OnlinePlaySubScreenTestScene : ScreenTestScene - { - /// - /// The cached . - /// - protected Bindable SelectedRoom { get; private set; } - - /// - /// The cached - /// - protected IRoomManager RoomManager { get; private set; } - - protected Bindable Filter { get; private set; } - - protected OngoingOperationTracker OngoingOperationTracker { get; private set; } - - public override void SetUpSteps() - { - base.SetUpSteps(); - - AddStep("create dependencies", () => LoadScreen(new DependenciesScreen(CreateScreenDependencies))); - } - - /// - /// Creates dependencies for any pushed via . - /// Invoked at the start of every test via . - /// - /// - /// This should be overridden to add any custom dependencies required by subclasses of . - /// - /// The parent dependency container. - /// The resultant dependency container. - protected virtual IReadOnlyDependencyContainer CreateScreenDependencies(IReadOnlyDependencyContainer parent) - { - SelectedRoom = new Bindable(); - RoomManager = CreateRoomManager(); - Filter = new Bindable(new FilterCriteria()); - OngoingOperationTracker = new OngoingOperationTracker(); - - var dependencies = new DependencyContainer(new CachedModelDependencyContainer(parent) { Model = { BindTarget = SelectedRoom } }); - dependencies.CacheAs(SelectedRoom); - dependencies.CacheAs(RoomManager); - dependencies.CacheAs(Filter); - dependencies.CacheAs(OngoingOperationTracker); - - return dependencies; - } - - protected virtual IRoomManager CreateRoomManager() => new TestBasicRoomManager(); - - /// - /// A dummy screen used for injecting new dependencies into the hierarchy before any screen is pushed via . - /// - private class DependenciesScreen : OsuScreen - { - private readonly Func createDependenciesFunc; - - public DependenciesScreen(Func createDependenciesFunc) - { - this.createDependenciesFunc = createDependenciesFunc; - } - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - => createDependenciesFunc(base.CreateChildDependencies(parent)); - } - } -} diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs new file mode 100644 index 0000000000..5e46ea0544 --- /dev/null +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -0,0 +1,100 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Lounge.Components; + +namespace osu.Game.Tests.Visual.OnlinePlay +{ + /// + /// A providing all the dependencies cached by for testing s. + /// + public abstract class OnlinePlayTestScene : ScreenTestScene, IRoomTestDependencies + { + public Bindable SelectedRoom => RoomDependencies?.SelectedRoom; + public IRoomManager RoomManager => RoomDependencies?.RoomManager; + public Bindable Filter => RoomDependencies?.Filter; + public OngoingOperationTracker OngoingOperationTracker => RoomDependencies?.OngoingOperationTracker; + public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => RoomDependencies?.AvailabilityTracker; + + protected RoomTestDependencies RoomDependencies => delegatedDependencies?.RoomDependencies; + private DelegatedRoomDependencyContainer delegatedDependencies; + + protected override Container Content => content; + private readonly Container content; + private readonly Container drawableDependenciesContainer; + + protected OnlinePlayTestScene() + { + base.Content.AddRange(new Drawable[] + { + drawableDependenciesContainer = new Container { RelativeSizeAxes = Axes.Both }, + content = new Container { RelativeSizeAxes = Axes.Both }, + }); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + delegatedDependencies = new DelegatedRoomDependencyContainer(base.CreateChildDependencies(parent)); + return delegatedDependencies; + } + + [SetUp] + public void Setup() => Schedule(() => + { + // Reset the room dependencies to a fresh state. + drawableDependenciesContainer.Clear(); + delegatedDependencies.RoomDependencies = CreateRoomDependencies(); + drawableDependenciesContainer.AddRange(RoomDependencies.DrawableComponents); + }); + + /// + /// Creates the room dependencies. Called every . + /// + /// + /// Any custom dependencies required for online-play sub-classes should be added here. + /// + protected virtual RoomTestDependencies CreateRoomDependencies() => new RoomTestDependencies(); + + /// + /// A providing a mutable lookup source for room dependencies. + /// + private class DelegatedRoomDependencyContainer : IReadOnlyDependencyContainer + { + /// + /// The room's dependencies. + /// + public RoomTestDependencies RoomDependencies { get; set; } + + private readonly IReadOnlyDependencyContainer parent; + private readonly DependencyContainer injectableDependencies; + + /// + /// Creates a new . + /// + /// The fallback to use when cannot satisfy a dependency. + public DelegatedRoomDependencyContainer(IReadOnlyDependencyContainer parent) + { + this.parent = parent; + injectableDependencies = new DependencyContainer(this); + } + + public object Get(Type type) + => RoomDependencies?.Get(type) ?? parent.Get(type); + + public object Get(Type type, CacheInfo info) + => RoomDependencies?.Get(type, info) ?? parent.Get(type, info); + + public void Inject(T instance) + where T : class + => injectableDependencies.Inject(instance); + } + } +} diff --git a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs new file mode 100644 index 0000000000..9d22f9e286 --- /dev/null +++ b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Lounge.Components; + +namespace osu.Game.Tests.Visual.OnlinePlay +{ + /// + /// Contains dependencies for testing online-play rooms. + /// + public class RoomTestDependencies : IReadOnlyDependencyContainer, IRoomTestDependencies + { + public Bindable SelectedRoom { get; } + public IRoomManager RoomManager { get; } + public Bindable Filter { get; } + public OngoingOperationTracker OngoingOperationTracker { get; } + public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; } + + /// + /// All cached dependencies which are also components. + /// + public IReadOnlyList DrawableComponents => drawableComponents; + + private readonly List drawableComponents = new List(); + private readonly DependencyContainer dependencies; + + public RoomTestDependencies() + { + SelectedRoom = new Bindable(); + RoomManager = CreateRoomManager(); + Filter = new Bindable(new FilterCriteria()); + OngoingOperationTracker = new OngoingOperationTracker(); + AvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); + + dependencies = new DependencyContainer(new CachedModelDependencyContainer(null) { Model = { BindTarget = SelectedRoom } }); + + CacheAs(SelectedRoom); + CacheAs(RoomManager); + CacheAs(Filter); + CacheAs(OngoingOperationTracker); + CacheAs(AvailabilityTracker); + } + + public object Get(Type type) + => dependencies.Get(type); + + public object Get(Type type, CacheInfo info) + => dependencies.Get(type, info); + + public void Inject(T instance) + where T : class + => dependencies.Inject(instance); + + protected void Cache(object instance) + { + dependencies.Cache(instance); + if (instance is Drawable drawable) + drawableComponents.Add(drawable); + } + + protected void CacheAs(T instance) + where T : class + { + dependencies.CacheAs(instance); + if (instance is Drawable drawable) + drawableComponents.Add(drawable); + } + + protected virtual IRoomManager CreateRoomManager() => new BasicTestRoomManager(); + } +} diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs deleted file mode 100644 index d75281e772..0000000000 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs +++ /dev/null @@ -1,37 +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.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Online.Rooms; - -namespace osu.Game.Tests.Visual.OnlinePlay -{ - /// - /// Contains a single that is resolvable by components in test scenes. - /// - public class TestRoomContainer : Container - { - /// - /// The cached . - /// - public readonly Room Room = new Room(); - - public TestRoomContainer() - { - RelativeSizeAxes = Axes.Both; - } - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer( - new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) { Model = { Value = Room } }); - - dependencies.Cache(new Bindable(Room)); - - return dependencies; - } - } -} From 81a812e21690cc4231459765e9a4d8b859da3647 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 15:00:10 +0900 Subject: [PATCH 184/457] Rework MultiplayerTestScene to make use of OnlinePlayTestScene --- .../StatefulMultiplayerClientTest.cs | 8 +- .../TestSceneFreeModSelectOverlay.cs | 11 +- .../TestSceneLoungeRoomsContainer.cs | 11 +- .../TestSceneMultiSpectatorLeaderboard.cs | 85 +++++-------- .../TestSceneMultiSpectatorScreen.cs | 54 ++++---- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestSceneMultiplayerMatchSubScreen.cs | 10 +- .../TestSceneMultiplayerReadyButton.cs | 10 +- .../TestSceneMultiplayerRoomManager.cs | 120 +++++++----------- .../TestSceneMultiplayerSpectateButton.cs | 25 +--- .../MultiplayerSubScreenTestScene.cs | 17 --- .../Multiplayer/MultiplayerTestScene.cs | 55 +++----- .../TestMultiplayerRoomContainer.cs | 48 ------- .../Multiplayer/TestMultiplayerRoomManager.cs | 5 +- osu.Game/Tests/Visual/RoomTestScene.cs | 33 ----- 15 files changed, 159 insertions(+), 335 deletions(-) delete mode 100644 osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs delete mode 100644 osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs delete mode 100644 osu.Game/Tests/Visual/RoomTestScene.cs diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index adc1d6aede..0983b806e2 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Users; @@ -50,7 +51,10 @@ namespace osu.Game.Tests.NonVisual.Multiplayer AddStep("create room initially in gameplay", () => { - Room.RoomID.Value = null; + var newRoom = new Room(); + newRoom.CopyFrom(SelectedRoom.Value); + + newRoom.RoomID.Value = null; Client.RoomSetupAction = room => { room.State = MultiplayerRoomState.Playing; @@ -61,7 +65,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer }); }; - RoomManager.CreateRoom(Room); + RoomManager.CreateRoom(newRoom); }); AddUntilStep("wait for room join", () => Client.Room != null); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 25de2d0df3..26a0301d8a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -7,17 +7,14 @@ using osu.Game.Screens.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneFreeModSelectOverlay : OsuTestScene + public class TestSceneFreeModSelectOverlay : MultiplayerTestScene { [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = new TestMultiplayerRoomContainer + Child = new FreeModSelectOverlay { - Child = new FreeModSelectOverlay - { - State = { Value = Visibility.Visible } - } + State = { Value = Visibility.Visible } }; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index a78cff0650..75cc687ee8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -3,7 +3,6 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Online.Rooms; @@ -22,8 +21,8 @@ namespace osu.Game.Tests.Visual.Multiplayer private RoomsContainer container; - [BackgroundDependencyLoader] - private void load() + [SetUp] + public new void Setup() => Schedule(() => { Child = container = new RoomsContainer { @@ -32,7 +31,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Width = 0.5f, JoinRequested = joinRequested }; - } + }); [Test] public void TestBasicListChanges() @@ -113,14 +112,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add rooms", () => RoomManager.AddRooms(2, new OsuRuleset().RulesetInfo)); AddStep("add rooms", () => RoomManager.AddRooms(3, new CatchRuleset().RulesetInfo)); + // Todo: What even is this case...? + AddStep("set empty filter criteria", () => container.Filter(null)); AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5); AddStep("filter osu! rooms", () => container.Filter(new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo })); - AddUntilStep("2 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2); AddStep("filter catch rooms", () => container.Filter(new FilterCriteria { Ruleset = new CatchRuleset().RulesetInfo })); - AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 9ec85d360f..54594fbfc8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -1,51 +1,48 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -using JetBrains.Annotations; using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Framework.Threading; using osu.Framework.Timing; using osu.Game.Database; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play.HUD; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiSpectatorLeaderboard : OsuTestScene + public class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene { - private Dictionary clocks; + public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; + protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; + + private Dictionary clocks; private MultiSpectatorLeaderboard leaderboard; - private TestContainer container; [SetUpSteps] - public void SetUpSteps() + public new void SetUpSteps() { AddStep("reset", () => { + Clear(); + clocks = new Dictionary { - { MultiplayerTestScene.PLAYER_1_ID, new ManualClock() }, - { MultiplayerTestScene.PLAYER_2_ID, new ManualClock() } + { PLAYER_1_ID, new ManualClock() }, + { PLAYER_2_ID, new ManualClock() } }; - Child = container = new TestContainer(); - foreach (var (userId, _) in clocks) - container.SpectatorClient.StartPlay(userId, 0); + SpectatorClient.StartPlay(userId, 0); }); AddStep("create leaderboard", () => @@ -55,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var scoreProcessor = new OsuScoreProcessor(); scoreProcessor.ApplyBeatmap(playable); - container.LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, container.Add); + LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, Add); }); AddUntilStep("wait for load", () => leaderboard.IsLoaded); @@ -76,42 +73,42 @@ namespace osu.Game.Tests.Visual.Multiplayer // For player 2, send frames in sets of 10. for (int i = 0; i < 100; i++) { - container.SpectatorClient.SendFrames(MultiplayerTestScene.PLAYER_1_ID, i, 1); + SpectatorClient.SendFrames(PLAYER_1_ID, i, 1); if (i % 10 == 0) - container.SpectatorClient.SendFrames(MultiplayerTestScene.PLAYER_2_ID, i, 10); + SpectatorClient.SendFrames(PLAYER_2_ID, i, 10); } }); - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 1); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 10); + assertCombo(PLAYER_1_ID, 1); + assertCombo(PLAYER_2_ID, 10); // Advance to a point where only user player 1's frame changes. setTime(500); - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 5); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 10); + assertCombo(PLAYER_1_ID, 5); + assertCombo(PLAYER_2_ID, 10); // Advance to a point where both user's frame changes. setTime(1100); - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 11); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 20); + assertCombo(PLAYER_1_ID, 11); + assertCombo(PLAYER_2_ID, 20); // Advance user player 2 only to a point where its frame changes. - setTime(MultiplayerTestScene.PLAYER_2_ID, 2100); - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 11); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 30); + setTime(PLAYER_2_ID, 2100); + assertCombo(PLAYER_1_ID, 11); + assertCombo(PLAYER_2_ID, 30); // Advance both users beyond their last frame setTime(101 * 100); - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 100); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 100); + assertCombo(PLAYER_1_ID, 100); + assertCombo(PLAYER_2_ID, 100); } [Test] public void TestNoFrames() { - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 0); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 0); + assertCombo(PLAYER_1_ID, 0); + assertCombo(PLAYER_2_ID, 0); } private void setTime(double time) => AddStep($"set time {time}", () => @@ -126,30 +123,18 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertCombo(int userId, int expectedCombo) => AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo); - private class TestContainer : TestMultiplayerRoomContainer - { - [Cached(typeof(SpectatorClient))] - public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); + protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); - [Cached(typeof(UserLookupCache))] + protected class TestDependencies : MultiplayerRoomTestDependencies + { + public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); public readonly UserLookupCache LookupCache = new TestUserLookupCache(); - protected override Container Content => content; - private readonly Container content; - - public TestContainer() + public TestDependencies() { - AddRangeInternal(new Drawable[] - { - SpectatorClient, - LookupCache, - content = new Container { RelativeSizeAxes = Axes.Both } - }); + CacheAs(SpectatorClient); + CacheAs(LookupCache); } - - public new Task LoadComponentAsync([NotNull] TLoadable component, Action onLoaded = null, CancellationToken cancellation = default, Scheduler scheduler = null) - where TLoadable : Drawable - => base.LoadComponentAsync(component, onLoaded, cancellation, scheduler); } private class TestUserLookupCache : UserLookupCache diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index b91391c409..64f4b4c0d6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -15,6 +15,7 @@ using osu.Game.Online.Spectator; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; using osu.Game.Users; @@ -22,11 +23,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiSpectatorScreen : MultiplayerTestScene { - [Cached(typeof(SpectatorClient))] - private TestSpectatorClient spectatorClient = new TestSpectatorClient(); + public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - [Cached(typeof(UserLookupCache))] - private UserLookupCache lookupCache = new TestUserLookupCache(); + protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; [Resolved] private OsuGameBase game { get; set; } @@ -51,25 +50,12 @@ namespace osu.Game.Tests.Visual.Multiplayer importedBeatmapId = importedBeatmap.OnlineBeatmapID ?? -1; } - public override void SetUpSteps() + [SetUp] + public new void Setup() => Schedule(() => { - base.SetUpSteps(); - - AddStep("reset sent frames", () => nextFrame.Clear()); - - AddStep("add streaming client", () => - { - Remove(spectatorClient); - Add(spectatorClient); - }); - - AddStep("finish previous gameplay", () => - { - foreach (var id in playingUserIds) - spectatorClient.EndPlay(id); - playingUserIds.Clear(); - }); - } + nextFrame.Clear(); + playingUserIds.Clear(); + }); [Test] public void TestDelayedStart() @@ -87,11 +73,11 @@ namespace osu.Game.Tests.Visual.Multiplayer loadSpectateScreen(false); AddWaitStep("wait a bit", 10); - AddStep("load player first_player_id", () => spectatorClient.StartPlay(PLAYER_1_ID, importedBeatmapId)); + AddStep("load player first_player_id", () => SpectatorClient.StartPlay(PLAYER_1_ID, importedBeatmapId)); AddUntilStep("one player added", () => spectatorScreen.ChildrenOfType().Count() == 1); AddWaitStep("wait a bit", 10); - AddStep("load player second_player_id", () => spectatorClient.StartPlay(PLAYER_2_ID, importedBeatmapId)); + AddStep("load player second_player_id", () => SpectatorClient.StartPlay(PLAYER_2_ID, importedBeatmapId)); AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType().Count() == 2); } @@ -251,7 +237,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (int id in userIds) { Client.CurrentMatchPlayingUserIds.Add(id); - spectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId); + SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId); playingUserIds.Add(id); nextFrame[id] = 0; } @@ -262,7 +248,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("end play", () => { - spectatorClient.EndPlay(userId); + SpectatorClient.EndPlay(userId); playingUserIds.Remove(userId); nextFrame.Remove(userId); }); @@ -276,7 +262,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { foreach (int id in userIds) { - spectatorClient.SendFrames(id, nextFrame[id], count); + SpectatorClient.SendFrames(id, nextFrame[id], count); nextFrame[id] += count; } }); @@ -298,6 +284,20 @@ namespace osu.Game.Tests.Visual.Multiplayer private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType().Single(p => p.UserId == userId); + protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + + protected class TestDependencies : MultiplayerRoomTestDependencies + { + public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); + public readonly UserLookupCache LookupCache = new TestUserLookupCache(); + + public TestDependencies() + { + CacheAs(SpectatorClient); + CacheAs(LookupCache); + } + } + internal class TestUserLookupCache : UserLookupCache { protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 2725ef5976..8bcb9cebbc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -29,7 +29,7 @@ using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiplayerMatchSongSelect : ScreenTestScene + public class TestSceneMultiplayerMatchSongSelect : MultiplayerTestScene { private BeatmapManager manager; private RulesetStore rulesets; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index e8ebc0c426..955be6ca21 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -49,13 +49,13 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUp] public new void Setup() => Schedule(() => { - Room.Name.Value = "Test Room"; + SelectedRoom.Value = new Room { Name = { Value = "Test Room" } }; }); [SetUpSteps] public void SetupSteps() { - AddStep("load match", () => LoadScreen(screen = new MultiplayerMatchSubScreen(Room))); + AddStep("load match", () => LoadScreen(screen = new MultiplayerMatchSubScreen(SelectedRoom.Value))); AddUntilStep("wait for load", () => screen.IsCurrentScreen()); } @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("set playlist", () => { - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set playlist", () => { - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set playlist", () => { - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 929cd6ca80..4f2ca34fb0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -27,7 +27,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneMultiplayerReadyButton : MultiplayerTestScene { private MultiplayerReadyButton button; - private OnlinePlayBeatmapAvailabilityTracker beatmapTracker; private BeatmapSetInfo importedSet; private readonly Bindable selectedItem = new Bindable(); @@ -43,18 +42,13 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); - - Add(beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker - { - SelectedItem = { BindTarget = selectedItem } - }); - - Dependencies.Cache(beatmapTracker); } [SetUp] public new void Setup() => Schedule(() => { + AvailabilityTracker.SelectedItem.BindTo(selectedItem); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); selectedItem.Value = new PlaylistItem diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index 80e36916b1..302e6998cc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -3,37 +3,38 @@ using System; using NUnit.Framework; -using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { [HeadlessTest] - public class TestSceneMultiplayerRoomManager : OsuTestScene + public class TestSceneMultiplayerRoomManager : MultiplayerTestScene { - private TestMultiplayerRoomContainer roomContainer; - private TestMultiplayerRoomManager roomManager => roomContainer.RoomManager; + protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + + public TestSceneMultiplayerRoomManager() + : base(false) + { + } [Test] public void TestPollsInitially() { AddStep("create room manager with a few rooms", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom(r => r.Name.Value = "1")); - roomManager.PartRoom(); - roomManager.CreateRoom(createRoom(r => r.Name.Value = "2")); - roomManager.PartRoom(); - roomManager.ClearRooms(); - }); + RoomManager.CreateRoom(createRoom(r => r.Name.Value = "1")); + RoomManager.PartRoom(); + RoomManager.CreateRoom(createRoom(r => r.Name.Value = "2")); + RoomManager.PartRoom(); + RoomManager.ClearRooms(); }); - AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2); - AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value); + AddAssert("manager polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 2); + AddAssert("initial rooms received", () => RoomManager.InitialRoomsReceived.Value); } [Test] @@ -41,19 +42,16 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a few rooms", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom()); - roomManager.PartRoom(); - roomManager.CreateRoom(createRoom()); - roomManager.PartRoom(); - }); + RoomManager.CreateRoom(createRoom()); + RoomManager.PartRoom(); + RoomManager.CreateRoom(createRoom()); + RoomManager.PartRoom(); }); - AddStep("disconnect", () => roomContainer.Client.Disconnect()); + AddStep("disconnect", () => Client.Disconnect()); - AddAssert("rooms cleared", () => ((RoomManager)roomManager).Rooms.Count == 0); - AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value); + AddAssert("rooms cleared", () => ((RoomManager)RoomManager).Rooms.Count == 0); + AddAssert("initial rooms not received", () => !RoomManager.InitialRoomsReceived.Value); } [Test] @@ -61,20 +59,17 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a few rooms", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom()); - roomManager.PartRoom(); - roomManager.CreateRoom(createRoom()); - roomManager.PartRoom(); - }); + RoomManager.CreateRoom(createRoom()); + RoomManager.PartRoom(); + RoomManager.CreateRoom(createRoom()); + RoomManager.PartRoom(); }); - AddStep("disconnect", () => roomContainer.Client.Disconnect()); - AddStep("connect", () => roomContainer.Client.Connect()); + AddStep("disconnect", () => Client.Disconnect()); + AddStep("connect", () => Client.Connect()); - AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2); - AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value); + AddAssert("manager polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 2); + AddAssert("initial rooms received", () => RoomManager.InitialRoomsReceived.Value); } [Test] @@ -82,15 +77,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a room", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom()); - roomManager.ClearRooms(); - }); + RoomManager.CreateRoom(createRoom()); + RoomManager.ClearRooms(); }); - AddAssert("manager not polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 0); - AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value); + AddAssert("manager not polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 0); + AddAssert("initial rooms not received", () => !RoomManager.InitialRoomsReceived.Value); } [Test] @@ -98,13 +90,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a room", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom()); - }); + RoomManager.CreateRoom(createRoom()); }); - AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null); + AddUntilStep("multiplayer room joined", () => Client.Room != null); } [Test] @@ -112,14 +101,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a room", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom()); - roomManager.PartRoom(); - }); + RoomManager.CreateRoom(createRoom()); + RoomManager.PartRoom(); }); - AddAssert("multiplayer room parted", () => roomContainer.Client.Room == null); + AddAssert("multiplayer room parted", () => Client.Room == null); } [Test] @@ -127,16 +113,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a room", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - var r = createRoom(); - roomManager.CreateRoom(r); - roomManager.PartRoom(); - roomManager.JoinRoom(r); - }); + var r = createRoom(); + RoomManager.CreateRoom(r); + RoomManager.PartRoom(); + RoomManager.JoinRoom(r); }); - AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null); + AddUntilStep("multiplayer room joined", () => Client.Room != null); } private Room createRoom(Action initFunc = null) @@ -161,18 +144,13 @@ namespace osu.Game.Tests.Visual.Multiplayer return room; } - private TestMultiplayerRoomManager createRoomManager() + private class TestDependencies : MultiplayerRoomTestDependencies { - Child = roomContainer = new TestMultiplayerRoomContainer + public TestDependencies() { - RoomManager = - { - TimeBetweenListingPolls = { Value = 1 }, - TimeBetweenSelectionPolls = { Value = 1 } - } - }; - - return roomManager; + RoomManager.TimeBetweenListingPolls.Value = 1; + RoomManager.TimeBetweenSelectionPolls.Value = 1; + } } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index d00404102c..070158f552 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -37,40 +37,19 @@ namespace osu.Game.Tests.Visual.Multiplayer private IDisposable readyClickOperation; - protected override Container Content => content; - private readonly Container content; - - public TestSceneMultiplayerSpectateButton() - { - base.Content.Add(content = new Container - { - RelativeSizeAxes = Axes.Both - }); - } - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - return dependencies; - } - [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - - var beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = selectedItem } }; - base.Content.Add(beatmapTracker); - Dependencies.Cache(beatmapTracker); - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); } [SetUp] public new void Setup() => Schedule(() => { + AvailabilityTracker.SelectedItem.BindTo(selectedItem); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); selectedItem.Value = new PlaylistItem diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs deleted file mode 100644 index 5c405ee9d3..0000000000 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs +++ /dev/null @@ -1,17 +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.Tests.Visual.OnlinePlay; - -namespace osu.Game.Tests.Visual.Multiplayer -{ - public class MultiplayerSubScreenTestScene : OnlinePlayTestScene, IMultiplayerRoomTestDependencies - { - public TestMultiplayerClient Client => RoomDependencies.Client; - public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; - - protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; - - protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); - } -} diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index c76d1053b2..69dfd41e04 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -2,66 +2,51 @@ // 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.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Screens.OnlinePlay; -using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public abstract class MultiplayerTestScene : RoomTestScene + public abstract class MultiplayerTestScene : OnlinePlayTestScene { public const int PLAYER_1_ID = 55; public const int PLAYER_2_ID = 56; - [Cached(typeof(MultiplayerClient))] - public TestMultiplayerClient Client { get; } + public TestMultiplayerClient Client => RoomDependencies.Client; + public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; - [Cached(typeof(IRoomManager))] - public TestMultiplayerRoomManager RoomManager { get; } + protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; - [Cached] - public Bindable Filter { get; } - - [Cached] - public OngoingOperationTracker OngoingOperationTracker { get; } - - protected override Container Content => content; - private readonly TestMultiplayerRoomContainer content; + protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); private readonly bool joinRoom; protected MultiplayerTestScene(bool joinRoom = true) { this.joinRoom = joinRoom; - base.Content.Add(content = new TestMultiplayerRoomContainer { RelativeSizeAxes = Axes.Both }); - - Client = content.Client; - RoomManager = content.RoomManager; - Filter = content.Filter; - OngoingOperationTracker = content.OngoingOperationTracker; } [SetUp] public new void Setup() => Schedule(() => { - RoomManager.Schedule(() => RoomManager.PartRoom()); - if (joinRoom) { - Room.Name.Value = "test name"; - Room.Playlist.Add(new PlaylistItem + var room = new Room { - Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo }, - Ruleset = { Value = Ruleset.Value } - }); + Name = { Value = "test name" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo }, + Ruleset = { Value = Ruleset.Value } + } + } + }; - RoomManager.Schedule(() => RoomManager.CreateRoom(Room)); + RoomManager.CreateRoom(room); + SelectedRoom.Value = room; } }); @@ -70,7 +55,9 @@ namespace osu.Game.Tests.Visual.Multiplayer base.SetUpSteps(); if (joinRoom) + { AddUntilStep("wait for room join", () => Client.Room != null); + } } } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs deleted file mode 100644 index 1abf4d8f5d..0000000000 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs +++ /dev/null @@ -1,48 +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.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Online.Multiplayer; -using osu.Game.Screens.OnlinePlay; -using osu.Game.Screens.OnlinePlay.Lounge.Components; - -namespace osu.Game.Tests.Visual.Multiplayer -{ - public class TestMultiplayerRoomContainer : Container - { - protected override Container Content => content; - private readonly Container content; - - [Cached(typeof(MultiplayerClient))] - public readonly TestMultiplayerClient Client; - - [Cached(typeof(IRoomManager))] - public readonly TestMultiplayerRoomManager RoomManager; - - [Cached] - public readonly Bindable Filter = new Bindable(new FilterCriteria()); - - [Cached] - public readonly OngoingOperationTracker OngoingOperationTracker; - - public TestMultiplayerRoomContainer() - { - RelativeSizeAxes = Axes.Both; - - RoomManager = new TestMultiplayerRoomManager(); - Client = new TestMultiplayerClient(RoomManager); - OngoingOperationTracker = new OngoingOperationTracker(); - - AddRangeInternal(new Drawable[] - { - Client, - RoomManager, - OngoingOperationTracker, - content = new Container { RelativeSizeAxes = Axes.Both } - }); - } - } -} diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index 315be510a3..6f4a464d57 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -29,10 +29,9 @@ namespace osu.Game.Tests.Visual.Multiplayer public new readonly List Rooms = new List(); - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); - int currentScoreId = 0; int currentRoomId = 0; int currentPlaylistItemId = 0; diff --git a/osu.Game/Tests/Visual/RoomTestScene.cs b/osu.Game/Tests/Visual/RoomTestScene.cs deleted file mode 100644 index aaf5c7624f..0000000000 --- a/osu.Game/Tests/Visual/RoomTestScene.cs +++ /dev/null @@ -1,33 +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 NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Game.Online.Rooms; - -namespace osu.Game.Tests.Visual -{ - public abstract class RoomTestScene : ScreenTestScene - { - [Cached] - private readonly Bindable currentRoom = new Bindable(); - - protected Room Room => currentRoom.Value; - - private CachedModelDependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - dependencies = new CachedModelDependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Model.BindTo(currentRoom); - return dependencies; - } - - [SetUp] - public void Setup() => Schedule(() => - { - currentRoom.Value = new Room(); - }); - } -} From 57ae87956a54e7ac3365abde44338a04f5d33a45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Jun 2021 15:27:40 +0900 Subject: [PATCH 185/457] Update execution state change blocking logic in line with framework changes --- osu.Game/OsuGameBase.cs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d0be03f8c1..dec738e5b3 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -24,6 +24,7 @@ using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Logging; +using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Database; using osu.Game.Input; @@ -156,6 +157,8 @@ namespace osu.Game private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(GLOBAL_TRACK_VOLUME_ADJUST); + private IBindable updateThreadState; + public OsuGameBase() { UseDevelopmentServer = DebugUtils.IsDebugBuild; @@ -183,7 +186,8 @@ namespace osu.Game dependencies.Cache(realmFactory = new RealmContextFactory(Storage)); - Host.UpdateThread.ThreadPausing += onUpdateThreadPausing; + updateThreadState = Host.UpdateThread.State.GetBoundCopy(); + updateThreadState.BindValueChanged(updateThreadStateChanged); AddInternal(realmFactory); @@ -359,10 +363,21 @@ namespace osu.Game Ruleset.BindValueChanged(onRulesetChanged); } - private void onUpdateThreadPausing() + private IDisposable blocking; + + private void updateThreadStateChanged(ValueChangedEvent state) { - var blocking = realmFactory.BlockAllOperations(); - Schedule(() => blocking.Dispose()); + switch (state.NewValue) + { + case GameThreadState.Running: + blocking.Dispose(); + blocking = null; + break; + + case GameThreadState.Paused: + blocking = realmFactory.BlockAllOperations(); + break; + } } protected override void LoadComplete() @@ -498,9 +513,6 @@ namespace osu.Game LocalConfig?.Dispose(); contextFactory.FlushConnections(); - - if (Host?.UpdateThread != null) - Host.UpdateThread.ThreadPausing -= onUpdateThreadPausing; } } } From d6ab08c95821927001a863ad2db40199fc8656a3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 15:30:23 +0900 Subject: [PATCH 186/457] Remove manual dependencies in TestSceneMultiplayerGameplayLeaderboard --- ...TestSceneMultiplayerGameplayLeaderboard.cs | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index af2f6fa5fe..9c30151f05 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -7,7 +7,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Configuration; @@ -20,6 +19,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Tests.Visual.Online; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer @@ -28,28 +28,15 @@ namespace osu.Game.Tests.Visual.Multiplayer { private const int users = 16; - [Cached(typeof(SpectatorClient))] - private TestMultiplayerSpectatorClient spectatorClient = new TestMultiplayerSpectatorClient(); + public TestMultiplayerSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - [Cached(typeof(UserLookupCache))] - private UserLookupCache lookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache(); + public UserLookupCache LookupCache => RoomDependencies?.LookupCache; + + protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; private MultiplayerGameplayLeaderboard leaderboard; - - protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; - private OsuConfigManager config; - public TestSceneMultiplayerGameplayLeaderboard() - { - base.Content.Children = new Drawable[] - { - spectatorClient, - lookupCache, - Content - }; - } - [BackgroundDependencyLoader] private void load() { @@ -59,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUpSteps] public override void SetUpSteps() { - AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = lookupCache.GetUserAsync(1).Result); + AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result); AddStep("create leaderboard", () => { @@ -71,12 +58,12 @@ namespace osu.Game.Tests.Visual.Multiplayer var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); for (int i = 0; i < users; i++) - spectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); + SpectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); - spectatorClient.Schedule(() => + SpectatorClient.Schedule(() => { Client.CurrentMatchPlayingUserIds.Clear(); - Client.CurrentMatchPlayingUserIds.AddRange(spectatorClient.PlayingUsers); + Client.CurrentMatchPlayingUserIds.AddRange(SpectatorClient.PlayingUsers); }); Children = new Drawable[] @@ -86,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer scoreProcessor.ApplyBeatmap(playable); - LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, spectatorClient.PlayingUsers.ToArray()) + LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, SpectatorClient.PlayingUsers.ToArray()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -100,7 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestScoreUpdates() { - AddRepeatStep("update state", () => spectatorClient.RandomlyUpdateState(), 100); + AddRepeatStep("update state", () => SpectatorClient.RandomlyUpdateState(), 100); AddToggleStep("switch compact mode", expanded => leaderboard.Expanded.Value = expanded); } @@ -113,11 +100,25 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestChangeScoringMode() { - AddRepeatStep("update state", () => spectatorClient.RandomlyUpdateState(), 5); + AddRepeatStep("update state", () => SpectatorClient.RandomlyUpdateState(), 5); AddStep("change to classic", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Classic)); AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised)); } + protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + + protected class TestDependencies : MultiplayerRoomTestDependencies + { + public readonly TestMultiplayerSpectatorClient SpectatorClient = new TestMultiplayerSpectatorClient(); + public readonly UserLookupCache LookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache(); + + public TestDependencies() + { + CacheAs(SpectatorClient); + CacheAs(LookupCache); + } + } + public class TestMultiplayerSpectatorClient : TestSpectatorClient { private readonly Dictionary lastHeaders = new Dictionary(); From 8241fee4a8aab8853d11853ca0757e08e13a3664 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 09:22:34 +0300 Subject: [PATCH 187/457] Add failing test case --- .../TestSceneRulesetSkinProvidingContainer.cs | 100 ++++++++++++++++++ .../Testing/TestSceneRulesetDependencies.cs | 2 +- 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs new file mode 100644 index 0000000000..dd78f28351 --- /dev/null +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -0,0 +1,100 @@ +// 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 JetBrains.Annotations; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Skinning; +using osu.Game.Tests.Testing; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Rulesets +{ + public class TestSceneRulesetSkinProvidingContainer : OsuTestScene + { + [Resolved] + private SkinManager skins { get; set; } + + private SkinRequester requester; + + protected override Ruleset CreateRuleset() => new TestRuleset(); + + [Test] + public void TestEarlyAddedSkinRequester() + { + ISample transformerSampleOnBdl = null; + + // need a legacy skin to plug the TestRuleset's legacy transformer, which is required for testing this. + AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info); + + AddStep("setup provider", () => + { + var rulesetSkinProvider = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin); + + rulesetSkinProvider.Add(requester = new SkinRequester()); + + requester.OnBdl += () => transformerSampleOnBdl = requester.GetSample(new SampleInfo(TestLegacySkinTransformer.VIRTUAL_SAMPLE_NAME)); + + Child = rulesetSkinProvider; + }); + + AddAssert("requester got correct initial sample", () => transformerSampleOnBdl != null); + } + + private class SkinRequester : Drawable, ISkin + { + private ISkinSource skin; + + public event Action OnBdl; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + this.skin = skin; + + OnBdl?.Invoke(); + } + + public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component); + + public Texture GetTexture(string componentName, WrapMode wrapModeS = default, WrapMode wrapModeT = default) => skin.GetTexture(componentName); + + public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo); + + public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); + } + + private class TestRuleset : TestSceneRulesetDependencies.TestRuleset + { + public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new TestLegacySkinTransformer(skin); + } + + private class TestLegacySkinTransformer : LegacySkinTransformer + { + public const string VIRTUAL_SAMPLE_NAME = "virtual-test-sample"; + + public TestLegacySkinTransformer([NotNull] ISkin skin) + : base(skin) + { + } + + public override ISample GetSample(ISampleInfo sampleInfo) + { + if (sampleInfo.LookupNames.Single() == VIRTUAL_SAMPLE_NAME) + return new SampleVirtual(); + + return base.GetSample(sampleInfo); + } + } + } +} diff --git a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs index 97087e31ab..fb50da32f3 100644 --- a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs +++ b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Testing Dependencies.Get() != null); } - private class TestRuleset : Ruleset + public class TestRuleset : Ruleset { public override string Description => string.Empty; public override string ShortName => string.Empty; From f07008a0a284023e50f63e7de3c2392d2de0e626 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 09:55:29 +0300 Subject: [PATCH 188/457] Fix `RulesetSkinProvidingContainer` potentially late in setting up skin sources --- .../Skinning/RulesetSkinProvidingContainer.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index c48aeca99a..abf5cb040a 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -42,27 +42,30 @@ namespace osu.Game.Skinning }; } - [Resolved] - private ISkinSource skinSource { get; set; } + private ISkinSource parentSource; - [BackgroundDependencyLoader] - private void load() + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - UpdateSkins(); - skinSource.SourceChanged += OnSourceChanged; + parentSource = parent.Get(); + + UpdateSkinSources(); + + parentSource.SourceChanged += OnSourceChanged; + + return base.CreateChildDependencies(parent); } protected override void OnSourceChanged() { - UpdateSkins(); + UpdateSkinSources(); base.OnSourceChanged(); } - protected virtual void UpdateSkins() + protected virtual void UpdateSkinSources() { SkinSources.Clear(); - foreach (var skin in skinSource.AllSources) + foreach (var skin in parentSource.AllSources) { switch (skin) { @@ -93,8 +96,8 @@ namespace osu.Game.Skinning { base.Dispose(isDisposing); - if (skinSource != null) - skinSource.SourceChanged -= OnSourceChanged; + if (parentSource != null) + parentSource.SourceChanged -= OnSourceChanged; } } } From 58839221771cf68b5d95fc5101befb3bdf70250f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Jun 2021 16:36:31 +0900 Subject: [PATCH 189/457] Remove mod multiplier completely --- .../TestSceneModSelectOverlay.cs | 22 +--------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 42 +------------------ .../OnlinePlay/FreeModSelectOverlay.cs | 1 - 3 files changed, 3 insertions(+), 62 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 2885dbee00..df8ef92a05 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; @@ -107,7 +106,6 @@ namespace osu.Game.Tests.Visual.UserInterface var conversionMods = osu.GetModsFor(ModType.Conversion); var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail); - var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden); var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); @@ -120,8 +118,6 @@ namespace osu.Game.Tests.Visual.UserInterface testMultiMod(doubleTimeMod); testIncompatibleMods(easy, hardRock); testDeselectAll(easierMods.Where(m => !(m is MultiMod))); - testMultiplierTextColour(noFailMod, () => modSelect.LowMultiplierColour); - testMultiplierTextColour(hiddenMod, () => modSelect.HighMultiplierColour); testUnimplementedMod(targetMod); } @@ -149,7 +145,7 @@ namespace osu.Game.Tests.Visual.UserInterface changeRuleset(0); - AddAssert("ensure mods still selected", () => modDisplay.Current.Value.Single(m => m is OsuModNoFail) != null); + AddAssert("ensure mods still selected", () => modDisplay.Current.Value.SingleOrDefault(m => m is OsuModNoFail) != null); changeRuleset(3); @@ -316,17 +312,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("check for no selection", () => !modSelect.SelectedMods.Value.Any()); } - private void testMultiplierTextColour(Mod mod, Func getCorrectColour) - { - checkLabelColor(() => Color4.White); - selectNext(mod); - AddWaitStep("wait for changing colour", 1); - checkLabelColor(getCorrectColour); - selectPrevious(mod); - AddWaitStep("wait for changing colour", 1); - checkLabelColor(() => Color4.White); - } - private void testModsWithSameBaseType(Mod modA, Mod modB) { selectNext(modA); @@ -348,7 +333,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert($"check {mod.Name} is selected", () => { var button = modSelect.GetModButton(mod); - return modSelect.SelectedMods.Value.Single(m => m.Name == mod.Name) != null && button.SelectedMod.GetType() == mod.GetType() && button.Selected; + return modSelect.SelectedMods.Value.SingleOrDefault(m => m.Name == mod.Name) != null && button.SelectedMod.GetType() == mod.GetType() && button.Selected; }); } @@ -370,8 +355,6 @@ namespace osu.Game.Tests.Visual.UserInterface }); } - private void checkLabelColor(Func getColour) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == getColour()); - private void createDisplay(Func createOverlayFunc) { Children = new Drawable[] @@ -408,7 +391,6 @@ namespace osu.Game.Tests.Visual.UserInterface return section.ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); } - public new OsuSpriteText MultiplierLabel => base.MultiplierLabel; public new TriangleButton DeselectAllButton => base.DeselectAllButton; public new Color4 LowMultiplierColour => base.LowMultiplierColour; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 85dcade986..e4aab978fc 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -37,9 +37,6 @@ namespace osu.Game.Overlays.Mods protected readonly TriangleButton CustomiseButton; protected readonly TriangleButton CloseButton; - protected readonly Drawable MultiplierSection; - protected readonly OsuSpriteText MultiplierLabel; - protected readonly FillFlowContainer FooterContainer; protected override bool BlockNonPositionalInput => false; @@ -324,20 +321,6 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }, - MultiplierSection = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(footer_button_spacing / 2, 0), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Child = MultiplierLabel = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Width = 70, // make width fixed so reflow doesn't occur when multiplier number changes. - }, - }, } } }, @@ -351,11 +334,8 @@ namespace osu.Game.Overlays.Mods } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, AudioManager audio, OsuGameBase osu) + private void load(AudioManager audio, OsuGameBase osu) { - LowMultiplierColour = colours.Red; - HighMultiplierColour = colours.Green; - availableMods = osu.AvailableMods.GetBoundCopy(); sampleOn = audio.Samples.Get(@"UI/check-on"); @@ -485,26 +465,6 @@ namespace osu.Game.Overlays.Mods foreach (var section in ModSectionsContainer.Children) section.UpdateSelectedButtons(selectedMods); - - updateMultiplier(); - } - - private void updateMultiplier() - { - var multiplier = 1.0; - - foreach (var mod in SelectedMods.Value) - { - multiplier *= mod.ScoreMultiplier; - } - - MultiplierLabel.Text = $"{multiplier:N2}x"; - if (multiplier > 1.0) - MultiplierLabel.FadeColour(HighMultiplierColour, 200); - else if (multiplier < 1.0) - MultiplierLabel.FadeColour(LowMultiplierColour, 200); - else - MultiplierLabel.FadeColour(Color4.White, 200); } private void modButtonPressed(Mod selectedMod) diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 5e2e9fd087..d5abaaab4e 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -32,7 +32,6 @@ namespace osu.Game.Screens.OnlinePlay { IsValidMod = m => true; - MultiplierSection.Alpha = 0; DeselectAllButton.Alpha = 0; Drawable selectAllButton; From 06e357647abce9f383d0dee2c66bfc85440a41e7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 10:40:07 +0300 Subject: [PATCH 190/457] OnBdl -> OnLoadAsync --- .../Rulesets/TestSceneRulesetSkinProvidingContainer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index dd78f28351..3072c466e0 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Rulesets [Test] public void TestEarlyAddedSkinRequester() { - ISample transformerSampleOnBdl = null; + ISample transformerSampleOnLoad = null; // need a legacy skin to plug the TestRuleset's legacy transformer, which is required for testing this. AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info); @@ -43,26 +43,26 @@ namespace osu.Game.Tests.Rulesets rulesetSkinProvider.Add(requester = new SkinRequester()); - requester.OnBdl += () => transformerSampleOnBdl = requester.GetSample(new SampleInfo(TestLegacySkinTransformer.VIRTUAL_SAMPLE_NAME)); + requester.OnLoadAsync += () => transformerSampleOnLoad = requester.GetSample(new SampleInfo(TestLegacySkinTransformer.VIRTUAL_SAMPLE_NAME)); Child = rulesetSkinProvider; }); - AddAssert("requester got correct initial sample", () => transformerSampleOnBdl != null); + AddAssert("requester got correct initial sample", () => transformerSampleOnLoad != null); } private class SkinRequester : Drawable, ISkin { private ISkinSource skin; - public event Action OnBdl; + public event Action OnLoadAsync; [BackgroundDependencyLoader] private void load(ISkinSource skin) { this.skin = skin; - OnBdl?.Invoke(); + OnLoadAsync?.Invoke(); } public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component); From 8d7705dc923b04ea037803260fc40fbc02f55933 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 10:55:23 +0300 Subject: [PATCH 191/457] Test using a simple `GetTexture` lookup instead Presumes that `RulesetSkinProvidingContainer` doesn't allow falling back to parent skins, whatsoever. --- .../TestSceneRulesetSkinProvidingContainer.cs | 37 +++---------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index 3072c466e0..50b75ea0c5 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; -using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; @@ -12,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Audio; -using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Skinning; using osu.Game.Tests.Testing; @@ -27,15 +24,14 @@ namespace osu.Game.Tests.Rulesets private SkinRequester requester; - protected override Ruleset CreateRuleset() => new TestRuleset(); + protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); [Test] public void TestEarlyAddedSkinRequester() { - ISample transformerSampleOnLoad = null; + Texture textureOnLoad = null; - // need a legacy skin to plug the TestRuleset's legacy transformer, which is required for testing this. - AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info); + AddStep("set skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info); AddStep("setup provider", () => { @@ -43,12 +39,12 @@ namespace osu.Game.Tests.Rulesets rulesetSkinProvider.Add(requester = new SkinRequester()); - requester.OnLoadAsync += () => transformerSampleOnLoad = requester.GetSample(new SampleInfo(TestLegacySkinTransformer.VIRTUAL_SAMPLE_NAME)); + requester.OnLoadAsync += () => textureOnLoad = requester.GetTexture("hitcircle"); Child = rulesetSkinProvider; }); - AddAssert("requester got correct initial sample", () => transformerSampleOnLoad != null); + AddAssert("requester got correct initial texture", () => textureOnLoad != null); } private class SkinRequester : Drawable, ISkin @@ -73,28 +69,5 @@ namespace osu.Game.Tests.Rulesets public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); } - - private class TestRuleset : TestSceneRulesetDependencies.TestRuleset - { - public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new TestLegacySkinTransformer(skin); - } - - private class TestLegacySkinTransformer : LegacySkinTransformer - { - public const string VIRTUAL_SAMPLE_NAME = "virtual-test-sample"; - - public TestLegacySkinTransformer([NotNull] ISkin skin) - : base(skin) - { - } - - public override ISample GetSample(ISampleInfo sampleInfo) - { - if (sampleInfo.LookupNames.Single() == VIRTUAL_SAMPLE_NAME) - return new SampleVirtual(); - - return base.GetSample(sampleInfo); - } - } } } From 13ed52a990cc4f1b53b3a1f1b37694859b5427cf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 11:16:26 +0300 Subject: [PATCH 192/457] Fix weird license misindent No idea how the hell that happened... R# silent about it, of course. --- .../Rulesets/TestSceneRulesetSkinProvidingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index 50b75ea0c5..b6800e40e4 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -1,5 +1,5 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. - // See the LICENCE file in the repository root for full licence text. +// See the LICENCE file in the repository root for full licence text. using System; using NUnit.Framework; From 7aefbe3da16c3abecb1cfa172f4aa18a0bb329a7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 17:37:02 +0900 Subject: [PATCH 193/457] Move UserLookupCache inside dependencies --- .../Visual/Gameplay/TestSceneSpectator.cs | 11 -------- .../TestSceneMultiSpectatorLeaderboard.cs | 18 ------------- .../TestSceneMultiSpectatorScreen.cs | 18 ------------- ...TestSceneMultiplayerGameplayLeaderboard.cs | 25 +++++++------------ ...ies.cs => IMultiplayerTestDependencies.cs} | 8 +++++- .../MultiplayerRoomTestDependencies.cs | 7 +++++- .../Multiplayer/MultiplayerTestScene.cs | 3 ++- ...cies.cs => IOnlinePlayTestDependencies.cs} | 2 +- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 2 +- .../Visual/OnlinePlay/RoomTestDependencies.cs | 2 +- osu.Game/Tests/Visual/TestUserLookupCache.cs | 19 ++++++++++++++ 11 files changed, 46 insertions(+), 69 deletions(-) rename osu.Game/Tests/Visual/Multiplayer/{IMultiplayerRoomTestDependencies.cs => IMultiplayerTestDependencies.cs} (71%) rename osu.Game/Tests/Visual/OnlinePlay/{IRoomTestDependencies.cs => IOnlinePlayTestDependencies.cs} (95%) create mode 100644 osu.Game/Tests/Visual/TestUserLookupCache.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 6eeb3596a8..7f3056771d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using System.Threading; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -232,14 +230,5 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("load screen", () => LoadScreen(spectatorScreen = new SoloSpectator(streamingUser))); AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded); } - - internal class TestUserLookupCache : UserLookupCache - { - protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) => Task.FromResult(new User - { - Id = lookup, - Username = $"User {lookup}" - }); - } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 54594fbfc8..7f586009a1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -3,19 +3,15 @@ using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Testing; using osu.Framework.Timing; -using osu.Game.Database; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play.HUD; using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; -using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { @@ -128,24 +124,10 @@ namespace osu.Game.Tests.Visual.Multiplayer protected class TestDependencies : MultiplayerRoomTestDependencies { public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); - public readonly UserLookupCache LookupCache = new TestUserLookupCache(); public TestDependencies() { CacheAs(SpectatorClient); - CacheAs(LookupCache); - } - } - - private class TestUserLookupCache : UserLookupCache - { - protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) - { - return Task.FromResult(new User - { - Id = lookup, - Username = $"User {lookup}" - }); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 64f4b4c0d6..e6634a598e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -3,21 +3,17 @@ using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Database; using osu.Game.Online.Spectator; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; -using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { @@ -289,24 +285,10 @@ namespace osu.Game.Tests.Visual.Multiplayer protected class TestDependencies : MultiplayerRoomTestDependencies { public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); - public readonly UserLookupCache LookupCache = new TestUserLookupCache(); public TestDependencies() { CacheAs(SpectatorClient); - CacheAs(LookupCache); - } - } - - internal class TestUserLookupCache : UserLookupCache - { - protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) - { - return Task.FromResult(new User - { - Id = lookup, - Username = $"User {lookup}" - }); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 9c30151f05..ae0938ae37 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -6,11 +6,11 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Configuration; -using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; @@ -18,7 +18,6 @@ using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; -using osu.Game.Tests.Visual.Online; using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; @@ -26,12 +25,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerGameplayLeaderboard : MultiplayerTestScene { - private const int users = 16; + private static IEnumerable users => Enumerable.Range(0, 16); public TestMultiplayerSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - public UserLookupCache LookupCache => RoomDependencies?.LookupCache; - protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; private MultiplayerGameplayLeaderboard leaderboard; @@ -57,14 +54,11 @@ namespace osu.Game.Tests.Visual.Multiplayer var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); - for (int i = 0; i < users; i++) - SpectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); + foreach (var user in users) + SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); - SpectatorClient.Schedule(() => - { - Client.CurrentMatchPlayingUserIds.Clear(); - Client.CurrentMatchPlayingUserIds.AddRange(SpectatorClient.PlayingUsers); - }); + // Todo: This is REALLY bad. + Client.CurrentMatchPlayingUserIds.AddRange(users); Children = new Drawable[] { @@ -73,7 +67,7 @@ namespace osu.Game.Tests.Visual.Multiplayer scoreProcessor.ApplyBeatmap(playable); - LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, SpectatorClient.PlayingUsers.ToArray()) + LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, users.ToArray()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -94,7 +88,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestUserQuit() { - AddRepeatStep("mark user quit", () => Client.CurrentMatchPlayingUserIds.RemoveAt(0), users); + foreach (var user in users) + AddStep($"mark user {user} quit", () => Client.RemoveUser(LookupCache.GetUserAsync(user).Result.AsNonNull())); } [Test] @@ -110,12 +105,10 @@ namespace osu.Game.Tests.Visual.Multiplayer protected class TestDependencies : MultiplayerRoomTestDependencies { public readonly TestMultiplayerSpectatorClient SpectatorClient = new TestMultiplayerSpectatorClient(); - public readonly UserLookupCache LookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache(); public TestDependencies() { CacheAs(SpectatorClient); - CacheAs(LookupCache); } } diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs similarity index 71% rename from osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs rename to osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs index 0b53bdd04e..331c4dcba6 100644 --- a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs @@ -1,13 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Database; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public interface IMultiplayerRoomTestDependencies : IRoomTestDependencies + public interface IMultiplayerTestDependencies : IOnlinePlayTestDependencies { /// /// The cached . @@ -18,5 +19,10 @@ namespace osu.Game.Tests.Visual.Multiplayer /// The cached . /// new TestMultiplayerRoomManager RoomManager { get; } + + /// + /// The cached . + /// + TestUserLookupCache LookupCache { get; } } } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs index e3913dd291..369563c5d9 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs @@ -1,21 +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.Database; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class MultiplayerRoomTestDependencies : RoomTestDependencies, IMultiplayerRoomTestDependencies + public class MultiplayerRoomTestDependencies : RoomTestDependencies, IMultiplayerTestDependencies { public TestMultiplayerClient Client { get; } + public TestUserLookupCache LookupCache { get; } public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; public MultiplayerRoomTestDependencies() { Client = new TestMultiplayerClient(RoomManager); + LookupCache = new TestUserLookupCache(); + CacheAs(Client); + CacheAs(LookupCache); } protected override IRoomManager CreateRoomManager() => new TestMultiplayerRoomManager(); diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 69dfd41e04..5c717bfb2f 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -8,13 +8,14 @@ using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public abstract class MultiplayerTestScene : OnlinePlayTestScene + public abstract class MultiplayerTestScene : OnlinePlayTestScene, IMultiplayerTestDependencies { public const int PLAYER_1_ID = 55; public const int PLAYER_2_ID = 56; public TestMultiplayerClient Client => RoomDependencies.Client; public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; + public TestUserLookupCache LookupCache => RoomDependencies?.LookupCache; protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; diff --git a/osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs similarity index 95% rename from osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs rename to osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs index 848e2aa77f..a4e0368adc 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs @@ -8,7 +8,7 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { - public interface IRoomTestDependencies + public interface IOnlinePlayTestDependencies { /// /// The cached . diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 5e46ea0544..39ce219092 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// A providing all the dependencies cached by for testing s. /// - public abstract class OnlinePlayTestScene : ScreenTestScene, IRoomTestDependencies + public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestDependencies { public Bindable SelectedRoom => RoomDependencies?.SelectedRoom; public IRoomManager RoomManager => RoomDependencies?.RoomManager; diff --git a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs index 9d22f9e286..b833a9400f 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// Contains dependencies for testing online-play rooms. /// - public class RoomTestDependencies : IReadOnlyDependencyContainer, IRoomTestDependencies + public class RoomTestDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies { public Bindable SelectedRoom { get; } public IRoomManager RoomManager { get; } diff --git a/osu.Game/Tests/Visual/TestUserLookupCache.cs b/osu.Game/Tests/Visual/TestUserLookupCache.cs new file mode 100644 index 0000000000..d2941b5bd5 --- /dev/null +++ b/osu.Game/Tests/Visual/TestUserLookupCache.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.Threading; +using System.Threading.Tasks; +using osu.Game.Database; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + public class TestUserLookupCache : UserLookupCache + { + protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) => Task.FromResult(new User + { + Id = lookup, + Username = $"User {lookup}" + }); + } +} From c0d2b41d4c54fa861b9945dd9b26795b064478da Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 17:55:16 +0900 Subject: [PATCH 194/457] Move SpectatorClient into multiplayer dependencies --- .../TestSceneMultiSpectatorLeaderboard.cs | 19 ------------------- .../TestSceneMultiSpectatorScreen.cs | 19 ------------------- ...TestSceneMultiplayerGameplayLeaderboard.cs | 11 ++--------- .../TestSceneMultiplayerRoomManager.cs | 1 + .../IMultiplayerTestDependencies.cs | 6 ++++++ .../MultiplayerRoomTestDependencies.cs | 8 ++++++++ .../Multiplayer/MultiplayerTestScene.cs | 2 ++ 7 files changed, 19 insertions(+), 47 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 7f586009a1..2537198503 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -6,21 +6,14 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Framework.Timing; -using osu.Game.Online.Spectator; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play.HUD; -using osu.Game.Tests.Visual.OnlinePlay; -using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene { - public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - - protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; - private Dictionary clocks; private MultiSpectatorLeaderboard leaderboard; @@ -118,17 +111,5 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertCombo(int userId, int expectedCombo) => AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo); - - protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); - - protected class TestDependencies : MultiplayerRoomTestDependencies - { - public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); - - public TestDependencies() - { - CacheAs(SpectatorClient); - } - } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index e6634a598e..873f8ca35b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -8,21 +8,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Online.Spectator; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; -using osu.Game.Tests.Visual.OnlinePlay; -using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiSpectatorScreen : MultiplayerTestScene { - public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - - protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; - [Resolved] private OsuGameBase game { get; set; } @@ -279,17 +272,5 @@ namespace osu.Game.Tests.Visual.Multiplayer private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType().Single(); private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType().Single(p => p.UserId == userId); - - protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); - - protected class TestDependencies : MultiplayerRoomTestDependencies - { - public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); - - public TestDependencies() - { - CacheAs(SpectatorClient); - } - } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index ae0938ae37..b7ccc17397 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -27,9 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { private static IEnumerable users => Enumerable.Range(0, 16); - public TestMultiplayerSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - - protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; + public new TestMultiplayerSpectatorClient SpectatorClient => (TestMultiplayerSpectatorClient)RoomDependencies?.SpectatorClient; private MultiplayerGameplayLeaderboard leaderboard; private OsuConfigManager config; @@ -104,12 +102,7 @@ namespace osu.Game.Tests.Visual.Multiplayer protected class TestDependencies : MultiplayerRoomTestDependencies { - public readonly TestMultiplayerSpectatorClient SpectatorClient = new TestMultiplayerSpectatorClient(); - - public TestDependencies() - { - CacheAs(SpectatorClient); - } + protected override TestSpectatorClient CreateSpectatorClient() => new TestMultiplayerSpectatorClient(); } public class TestMultiplayerSpectatorClient : TestSpectatorClient diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index 302e6998cc..225a9ff703 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -148,6 +148,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public TestDependencies() { + // Need to set these values as early as possible. RoomManager.TimeBetweenListingPolls.Value = 1; RoomManager.TimeBetweenSelectionPolls.Value = 1; } diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs index 331c4dcba6..c5f9e85003 100644 --- a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs @@ -5,6 +5,7 @@ using osu.Game.Database; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay; +using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { @@ -24,5 +25,10 @@ namespace osu.Game.Tests.Visual.Multiplayer /// The cached . /// TestUserLookupCache LookupCache { get; } + + /// + /// The cached . + /// + TestSpectatorClient SpectatorClient { get; } } } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs index 369563c5d9..a3f7156848 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs @@ -3,8 +3,10 @@ using osu.Game.Database; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Spectator; using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay; +using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { @@ -12,17 +14,23 @@ namespace osu.Game.Tests.Visual.Multiplayer { public TestMultiplayerClient Client { get; } public TestUserLookupCache LookupCache { get; } + public TestSpectatorClient SpectatorClient { get; } + public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; public MultiplayerRoomTestDependencies() { Client = new TestMultiplayerClient(RoomManager); LookupCache = new TestUserLookupCache(); + SpectatorClient = CreateSpectatorClient(); CacheAs(Client); CacheAs(LookupCache); + CacheAs(SpectatorClient); } protected override IRoomManager CreateRoomManager() => new TestMultiplayerRoomManager(); + + protected virtual TestSpectatorClient CreateSpectatorClient() => new TestSpectatorClient(); } } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 5c717bfb2f..19e7536286 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Game.Online.Rooms; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual.OnlinePlay; +using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { @@ -16,6 +17,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public TestMultiplayerClient Client => RoomDependencies.Client; public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; public TestUserLookupCache LookupCache => RoomDependencies?.LookupCache; + public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; From 04279e7f5cbfa0a90982f4cfb333be8f0e99f4b0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 18:02:53 +0900 Subject: [PATCH 195/457] Add some xmldocs --- .../Visual/Multiplayer/IMultiplayerTestDependencies.cs | 3 +++ .../Multiplayer/MultiplayerRoomTestDependencies.cs | 4 +++- .../Tests/Visual/Multiplayer/MultiplayerTestScene.cs | 9 +++++---- .../Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 3 +++ .../Visual/Multiplayer/TestMultiplayerRoomManager.cs | 4 ++++ osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs | 4 ++++ .../Visual/OnlinePlay/IOnlinePlayTestDependencies.cs | 3 +++ osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs | 2 +- osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs | 2 +- 9 files changed, 27 insertions(+), 7 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs index c5f9e85003..46ad5a5a1c 100644 --- a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs @@ -9,6 +9,9 @@ using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { + /// + /// Interface that defines the dependencies required for multiplayer test scenes. + /// public interface IMultiplayerTestDependencies : IOnlinePlayTestDependencies { /// diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs index a3f7156848..4c5afb9b58 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs @@ -10,12 +10,14 @@ using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { + /// + /// Contains the basic dependencies of multiplayer test scenes. + /// public class MultiplayerRoomTestDependencies : RoomTestDependencies, IMultiplayerTestDependencies { public TestMultiplayerClient Client { get; } public TestUserLookupCache LookupCache { get; } public TestSpectatorClient SpectatorClient { get; } - public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; public MultiplayerRoomTestDependencies() diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 19e7536286..6a9ae4c772 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -9,6 +9,9 @@ using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { + /// + /// The base test scene for all multiplayer components and screens. + /// public abstract class MultiplayerTestScene : OnlinePlayTestScene, IMultiplayerTestDependencies { public const int PLAYER_1_ID = 55; @@ -21,8 +24,6 @@ namespace osu.Game.Tests.Visual.Multiplayer protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; - protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); - private readonly bool joinRoom; protected MultiplayerTestScene(bool joinRoom = true) @@ -58,9 +59,9 @@ namespace osu.Game.Tests.Visual.Multiplayer base.SetUpSteps(); if (joinRoom) - { AddUntilStep("wait for room join", () => Client.Room != null); - } } + + protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index b12bd8091d..b0c8d6d19b 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -20,6 +20,9 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { + /// + /// A for use in multiplayer test scenes. Should generally not be used by itself outside of a . + /// public class TestMultiplayerClient : MultiplayerClient { public override IBindable IsConnected => isConnected; diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index 6f4a464d57..5d66cdba02 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -11,11 +11,15 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; namespace osu.Game.Tests.Visual.Multiplayer { + /// + /// A for use in multiplayer test scenes. Should generally not be used by itself outside of a . + /// public class TestMultiplayerRoomManager : MultiplayerRoomManager { [Resolved] diff --git a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs index 67beea9117..81b93fe5b5 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs @@ -8,10 +8,14 @@ using osu.Game.Beatmaps; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Users; namespace osu.Game.Tests.Visual.OnlinePlay { + /// + /// A very simple for use in online-play test scenes. + /// public class BasicTestRoomManager : IRoomManager { public event Action RoomsUpdated diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs index a4e0368adc..bc5d524bc4 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs @@ -8,6 +8,9 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { + /// + /// Interface that defines the dependencies required for online-play test scenes. + /// public interface IOnlinePlayTestDependencies { /// diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 39ce219092..6c1339fd85 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -14,7 +14,7 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// A providing all the dependencies cached by for testing s. + /// A base test scene for all online-play components and screens. /// public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestDependencies { diff --git a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs index b833a9400f..7b198c128b 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs @@ -13,7 +13,7 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// Contains dependencies for testing online-play rooms. + /// Contains the basic dependencies of online-play test scenes. /// public class RoomTestDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies { From a27a647ae72a7610caa442af2c293db494ccf9ae Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 18:07:47 +0900 Subject: [PATCH 196/457] Rename RoomDependencies -> OnlinePlayDependencies --- ...TestSceneMultiplayerGameplayLeaderboard.cs | 4 +- .../TestSceneMultiplayerRoomManager.cs | 2 +- .../TestScenePlaylistsMatchSettingsOverlay.cs | 4 +- .../MultiplayerRoomTestDependencies.cs | 2 +- .../Multiplayer/MultiplayerTestScene.cs | 12 ++--- .../Visual/OnlinePlay/BasicTestRoomManager.cs | 2 +- .../OnlinePlay/IOnlinePlayTestDependencies.cs | 2 +- ...ncies.cs => OnlinePlayTestDependencies.cs} | 6 +-- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 50 ++++++++++--------- 9 files changed, 44 insertions(+), 40 deletions(-) rename osu.Game/Tests/Visual/OnlinePlay/{RoomTestDependencies.cs => OnlinePlayTestDependencies.cs} (92%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index b7ccc17397..2938e813b2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { private static IEnumerable users => Enumerable.Range(0, 16); - public new TestMultiplayerSpectatorClient SpectatorClient => (TestMultiplayerSpectatorClient)RoomDependencies?.SpectatorClient; + public new TestMultiplayerSpectatorClient SpectatorClient => (TestMultiplayerSpectatorClient)OnlinePlayDependencies?.SpectatorClient; private MultiplayerGameplayLeaderboard leaderboard; private OsuConfigManager config; @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised)); } - protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); protected class TestDependencies : MultiplayerRoomTestDependencies { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index 225a9ff703..c750bbed4b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [HeadlessTest] public class TestSceneMultiplayerRoomManager : MultiplayerTestScene { - protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); public TestSceneMultiplayerRoomManager() : base(false) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index b52b6a6a0e..fd59ebad30 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Playlists private TestRoomSettings settings; - protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); [SetUp] public new void Setup() => Schedule(() => @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Playlists public OsuSpriteText ErrorText => ((MatchSettings)Settings).ErrorText; } - private class TestDependencies : RoomTestDependencies + private class TestDependencies : OnlinePlayTestDependencies { protected override IRoomManager CreateRoomManager() => new TestRoomManager(); } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs index 4c5afb9b58..e7885890b4 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// Contains the basic dependencies of multiplayer test scenes. /// - public class MultiplayerRoomTestDependencies : RoomTestDependencies, IMultiplayerTestDependencies + public class MultiplayerRoomTestDependencies : OnlinePlayTestDependencies, IMultiplayerTestDependencies { public TestMultiplayerClient Client { get; } public TestUserLookupCache LookupCache { get; } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 6a9ae4c772..90ffc83ccd 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -17,12 +17,12 @@ namespace osu.Game.Tests.Visual.Multiplayer public const int PLAYER_1_ID = 55; public const int PLAYER_2_ID = 56; - public TestMultiplayerClient Client => RoomDependencies.Client; - public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; - public TestUserLookupCache LookupCache => RoomDependencies?.LookupCache; - public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; + public TestMultiplayerClient Client => OnlinePlayDependencies.Client; + public new TestMultiplayerRoomManager RoomManager => OnlinePlayDependencies.RoomManager; + public TestUserLookupCache LookupCache => OnlinePlayDependencies?.LookupCache; + public TestSpectatorClient SpectatorClient => OnlinePlayDependencies?.SpectatorClient; - protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; + protected new MultiplayerRoomTestDependencies OnlinePlayDependencies => (MultiplayerRoomTestDependencies)base.OnlinePlayDependencies; private readonly bool joinRoom; @@ -62,6 +62,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room join", () => Client.Room != null); } - protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); + protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new MultiplayerRoomTestDependencies(); } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs index 81b93fe5b5..813e617ac5 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs @@ -14,7 +14,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// A very simple for use in online-play test scenes. + /// A very simple for use in online play test scenes. /// public class BasicTestRoomManager : IRoomManager { diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs index bc5d524bc4..8c262e718a 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs @@ -9,7 +9,7 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// Interface that defines the dependencies required for online-play test scenes. + /// Interface that defines the dependencies required for online play test scenes. /// public interface IOnlinePlayTestDependencies { diff --git a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs similarity index 92% rename from osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs rename to osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs index 7b198c128b..e45aadfd84 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs @@ -13,9 +13,9 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// Contains the basic dependencies of online-play test scenes. + /// Contains the basic dependencies of online play test scenes. /// - public class RoomTestDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies + public class OnlinePlayTestDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies { public Bindable SelectedRoom { get; } public IRoomManager RoomManager { get; } @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay private readonly List drawableComponents = new List(); private readonly DependencyContainer dependencies; - public RoomTestDependencies() + public OnlinePlayTestDependencies() { SelectedRoom = new Bindable(); RoomManager = CreateRoomManager(); diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 6c1339fd85..e1c21ce377 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -14,18 +14,22 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// A base test scene for all online-play components and screens. + /// A base test scene for all online play components and screens. /// public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestDependencies { - public Bindable SelectedRoom => RoomDependencies?.SelectedRoom; - public IRoomManager RoomManager => RoomDependencies?.RoomManager; - public Bindable Filter => RoomDependencies?.Filter; - public OngoingOperationTracker OngoingOperationTracker => RoomDependencies?.OngoingOperationTracker; - public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => RoomDependencies?.AvailabilityTracker; + public Bindable SelectedRoom => OnlinePlayDependencies?.SelectedRoom; + public IRoomManager RoomManager => OnlinePlayDependencies?.RoomManager; + public Bindable Filter => OnlinePlayDependencies?.Filter; + public OngoingOperationTracker OngoingOperationTracker => OnlinePlayDependencies?.OngoingOperationTracker; + public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => OnlinePlayDependencies?.AvailabilityTracker; - protected RoomTestDependencies RoomDependencies => delegatedDependencies?.RoomDependencies; - private DelegatedRoomDependencyContainer delegatedDependencies; + /// + /// All dependencies required for online play components and screens. + /// + protected OnlinePlayTestDependencies OnlinePlayDependencies => dependencies?.OnlinePlayDependencies; + + private DelegatedDependencyContainer dependencies; protected override Container Content => content; private readonly Container content; @@ -42,8 +46,8 @@ namespace osu.Game.Tests.Visual.OnlinePlay protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - delegatedDependencies = new DelegatedRoomDependencyContainer(base.CreateChildDependencies(parent)); - return delegatedDependencies; + dependencies = new DelegatedDependencyContainer(base.CreateChildDependencies(parent)); + return dependencies; } [SetUp] @@ -51,46 +55,46 @@ namespace osu.Game.Tests.Visual.OnlinePlay { // Reset the room dependencies to a fresh state. drawableDependenciesContainer.Clear(); - delegatedDependencies.RoomDependencies = CreateRoomDependencies(); - drawableDependenciesContainer.AddRange(RoomDependencies.DrawableComponents); + dependencies.OnlinePlayDependencies = CreateOnlinePlayDependencies(); + drawableDependenciesContainer.AddRange(OnlinePlayDependencies.DrawableComponents); }); /// /// Creates the room dependencies. Called every . /// /// - /// Any custom dependencies required for online-play sub-classes should be added here. + /// Any custom dependencies required for online play sub-classes should be added here. /// - protected virtual RoomTestDependencies CreateRoomDependencies() => new RoomTestDependencies(); + protected virtual OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new OnlinePlayTestDependencies(); /// - /// A providing a mutable lookup source for room dependencies. + /// A providing a mutable lookup source for online play dependencies. /// - private class DelegatedRoomDependencyContainer : IReadOnlyDependencyContainer + private class DelegatedDependencyContainer : IReadOnlyDependencyContainer { /// - /// The room's dependencies. + /// The online play dependencies. /// - public RoomTestDependencies RoomDependencies { get; set; } + public OnlinePlayTestDependencies OnlinePlayDependencies { get; set; } private readonly IReadOnlyDependencyContainer parent; private readonly DependencyContainer injectableDependencies; /// - /// Creates a new . + /// Creates a new . /// - /// The fallback to use when cannot satisfy a dependency. - public DelegatedRoomDependencyContainer(IReadOnlyDependencyContainer parent) + /// The fallback to use when cannot satisfy a dependency. + public DelegatedDependencyContainer(IReadOnlyDependencyContainer parent) { this.parent = parent; injectableDependencies = new DependencyContainer(this); } public object Get(Type type) - => RoomDependencies?.Get(type) ?? parent.Get(type); + => OnlinePlayDependencies?.Get(type) ?? parent.Get(type); public object Get(Type type, CacheInfo info) - => RoomDependencies?.Get(type, info) ?? parent.Get(type, info); + => OnlinePlayDependencies?.Get(type, info) ?? parent.Get(type, info); public void Inject(T instance) where T : class From ff5e590d323ce868cce4eeaed71280645e6ae729 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 12:00:46 +0300 Subject: [PATCH 197/457] Add local source for testing --- .../TestSceneRulesetSkinProvidingContainer.cs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index b6800e40e4..b058cc3694 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -19,11 +19,11 @@ namespace osu.Game.Tests.Rulesets { public class TestSceneRulesetSkinProvidingContainer : OsuTestScene { - [Resolved] - private SkinManager skins { get; set; } - private SkinRequester requester; + [Cached(typeof(ISkin))] + private readonly TestSkinProvider testSkin = new TestSkinProvider(); + protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); [Test] @@ -31,15 +31,13 @@ namespace osu.Game.Tests.Rulesets { Texture textureOnLoad = null; - AddStep("set skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info); - AddStep("setup provider", () => { var rulesetSkinProvider = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin); rulesetSkinProvider.Add(requester = new SkinRequester()); - requester.OnLoadAsync += () => textureOnLoad = requester.GetTexture("hitcircle"); + requester.OnLoadAsync += () => textureOnLoad = requester.GetTexture(TestSkinProvider.TEXTURE_NAME); Child = rulesetSkinProvider; }); @@ -69,5 +67,18 @@ namespace osu.Game.Tests.Rulesets public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); } + + private class TestSkinProvider : ISkin + { + public const string TEXTURE_NAME = "some-texture"; + + public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException(); + + public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => componentName == TEXTURE_NAME ? Texture.WhitePixel : null; + + public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + + public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); + } } } From 04cc390c419f73da5bc074fe0ef168f594e51a7f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 19:16:54 +0900 Subject: [PATCH 198/457] Fix TestSceneMultiplayer resolving the wrong client --- .../Multiplayer/TestSceneMultiplayer.cs | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 599dfb082b..c93640e7b5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -18,6 +18,7 @@ using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; @@ -30,14 +31,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayer : ScreenTestScene { - private TestMultiplayer multiplayerScreen; - private BeatmapManager beatmaps; private RulesetStore rulesets; private BeatmapSetInfo importedSet; - private TestMultiplayerClient client => multiplayerScreen.Client; - private Room room => client.APIRoom; + private DependenciesScreen dependenciesScreen; + private TestMultiplayer multiplayerScreen; + private TestMultiplayerClient client; public TestSceneMultiplayer() { @@ -229,30 +229,43 @@ namespace osu.Game.Tests.Visual.Multiplayer private void loadMultiplayer() { - AddStep("show", () => + AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer()); + + AddStep("load dependencies", () => { - multiplayerScreen = new TestMultiplayer(); + client = new TestMultiplayerClient(multiplayerScreen.RoomManager); - // Needs to be added at a higher level since the multiplayer screen becomes non-current. - Child = multiplayerScreen.Client; + // The screen gets suspended so it stops receiving updates. + Child = client; - LoadScreen(multiplayerScreen); + LoadScreen(dependenciesScreen = new DependenciesScreen(client)); }); - AddUntilStep("wait for loaded", () => multiplayerScreen.IsLoaded); + AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded); + + AddStep("load multiplayer", () => LoadScreen(multiplayerScreen)); + AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded); } - private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer + /// + /// Used for the sole purpose of adding as a resolvable dependency. + /// + private class DependenciesScreen : OsuScreen { [Cached(typeof(MultiplayerClient))] public readonly TestMultiplayerClient Client; - public TestMultiplayer() + public DependenciesScreen(TestMultiplayerClient client) { - Client = new TestMultiplayerClient((TestMultiplayerRoomManager)RoomManager); + Client = client; } + } - protected override RoomManager CreateRoomManager() => new TestMultiplayerRoomManager(); + private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer + { + public new TestMultiplayerRoomManager RoomManager { get; private set; } + + protected override RoomManager CreateRoomManager() => RoomManager = new TestMultiplayerRoomManager(); } } } From 84c9ede966af09a0b4b1337ce7750d01b14d7c03 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 13:17:11 +0300 Subject: [PATCH 199/457] Fix incorrect pushed changes This should've been in the original commit, but for some reason got deleted out. --- .../TestSceneRulesetSkinProvidingContainer.cs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index b058cc3694..0dde0a8194 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; @@ -21,11 +22,15 @@ namespace osu.Game.Tests.Rulesets { private SkinRequester requester; - [Cached(typeof(ISkin))] - private readonly TestSkinProvider testSkin = new TestSkinProvider(); - protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(new TestSkinProvider()); + return dependencies; + } + [Test] public void TestEarlyAddedSkinRequester() { @@ -68,7 +73,7 @@ namespace osu.Game.Tests.Rulesets public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); } - private class TestSkinProvider : ISkin + private class TestSkinProvider : ISkinSource { public const string TEXTURE_NAME = "some-texture"; @@ -79,6 +84,16 @@ namespace osu.Game.Tests.Rulesets public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); + + public event Action SourceChanged + { + add { } + remove { } + } + + public ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : null; + + public IEnumerable AllSources => new[] { this }; } } } From 57f2b4f812870bb1d9804ee7d8c079c106bd84d9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 20:09:03 +0900 Subject: [PATCH 200/457] Fix incorrect parent class --- .../Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs index afe66d2686..4e08ffef17 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs @@ -5,11 +5,10 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; -using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiplayerMatchFooter : OnlinePlayTestScene + public class TestSceneMultiplayerMatchFooter : MultiplayerTestScene { [SetUp] public new void Setup() => Schedule(() => From 1ed61b9b98757b1c2b63a1127c3569f0d728624a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 20:11:38 +0900 Subject: [PATCH 201/457] Fix up dependencies class names --- .../Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs | 4 ++-- .../Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs | 4 ++-- .../Playlists/TestScenePlaylistsMatchSettingsOverlay.cs | 4 ++-- osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs | 4 ++-- ...tDependencies.cs => MultiplayerTestSceneDependencies.cs} | 4 ++-- osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs | 6 +++--- ...stDependencies.cs => OnlinePlayTestSceneDependencies.cs} | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) rename osu.Game/Tests/Visual/Multiplayer/{MultiplayerRoomTestDependencies.cs => MultiplayerTestSceneDependencies.cs} (89%) rename osu.Game/Tests/Visual/OnlinePlay/{OnlinePlayTestDependencies.cs => OnlinePlayTestSceneDependencies.cs} (94%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 2938e813b2..0e368b59dd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -98,9 +98,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised)); } - protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); + protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies(); - protected class TestDependencies : MultiplayerRoomTestDependencies + protected class TestDependencies : MultiplayerTestSceneDependencies { protected override TestSpectatorClient CreateSpectatorClient() => new TestMultiplayerSpectatorClient(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index c750bbed4b..b17427a30b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [HeadlessTest] public class TestSceneMultiplayerRoomManager : MultiplayerTestScene { - protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); + protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies(); public TestSceneMultiplayerRoomManager() : base(false) @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return room; } - private class TestDependencies : MultiplayerRoomTestDependencies + private class TestDependencies : MultiplayerTestSceneDependencies { public TestDependencies() { diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index fd59ebad30..a320cb240f 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Playlists private TestRoomSettings settings; - protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); + protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies(); [SetUp] public new void Setup() => Schedule(() => @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Playlists public OsuSpriteText ErrorText => ((MatchSettings)Settings).ErrorText; } - private class TestDependencies : OnlinePlayTestDependencies + private class TestDependencies : OnlinePlayTestSceneDependencies { protected override IRoomManager CreateRoomManager() => new TestRoomManager(); } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 90ffc83ccd..da01010dbf 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public TestUserLookupCache LookupCache => OnlinePlayDependencies?.LookupCache; public TestSpectatorClient SpectatorClient => OnlinePlayDependencies?.SpectatorClient; - protected new MultiplayerRoomTestDependencies OnlinePlayDependencies => (MultiplayerRoomTestDependencies)base.OnlinePlayDependencies; + protected new MultiplayerTestSceneDependencies OnlinePlayDependencies => (MultiplayerTestSceneDependencies)base.OnlinePlayDependencies; private readonly bool joinRoom; @@ -62,6 +62,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room join", () => Client.Room != null); } - protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new MultiplayerRoomTestDependencies(); + protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new MultiplayerTestSceneDependencies(); } } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs similarity index 89% rename from osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs rename to osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs index e7885890b4..8fd21b5e4a 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs @@ -13,14 +13,14 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// Contains the basic dependencies of multiplayer test scenes. /// - public class MultiplayerRoomTestDependencies : OnlinePlayTestDependencies, IMultiplayerTestDependencies + public class MultiplayerTestSceneDependencies : OnlinePlayTestSceneDependencies, IMultiplayerTestDependencies { public TestMultiplayerClient Client { get; } public TestUserLookupCache LookupCache { get; } public TestSpectatorClient SpectatorClient { get; } public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; - public MultiplayerRoomTestDependencies() + public MultiplayerTestSceneDependencies() { Client = new TestMultiplayerClient(RoomManager); LookupCache = new TestUserLookupCache(); diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index e1c21ce377..4920ca2058 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// All dependencies required for online play components and screens. /// - protected OnlinePlayTestDependencies OnlinePlayDependencies => dependencies?.OnlinePlayDependencies; + protected OnlinePlayTestSceneDependencies OnlinePlayDependencies => dependencies?.OnlinePlayDependencies; private DelegatedDependencyContainer dependencies; @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// Any custom dependencies required for online play sub-classes should be added here. /// - protected virtual OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new OnlinePlayTestDependencies(); + protected virtual OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new OnlinePlayTestSceneDependencies(); /// /// A providing a mutable lookup source for online play dependencies. @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// The online play dependencies. /// - public OnlinePlayTestDependencies OnlinePlayDependencies { get; set; } + public OnlinePlayTestSceneDependencies OnlinePlayDependencies { get; set; } private readonly IReadOnlyDependencyContainer parent; private readonly DependencyContainer injectableDependencies; diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs similarity index 94% rename from osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs rename to osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs index e45aadfd84..b73a982fb6 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// Contains the basic dependencies of online play test scenes. /// - public class OnlinePlayTestDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies + public class OnlinePlayTestSceneDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies { public Bindable SelectedRoom { get; } public IRoomManager RoomManager { get; } @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay private readonly List drawableComponents = new List(); private readonly DependencyContainer dependencies; - public OnlinePlayTestDependencies() + public OnlinePlayTestSceneDependencies() { SelectedRoom = new Bindable(); RoomManager = CreateRoomManager(); From c93c615f5e704d248f82fecb1f6fdda06e248aed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 20:15:30 +0900 Subject: [PATCH 202/457] Also fix up interface names --- ...TestDependencies.cs => IMultiplayerTestSceneDependencies.cs} | 2 +- osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs | 2 +- .../Visual/Multiplayer/MultiplayerTestSceneDependencies.cs | 2 +- ...yTestDependencies.cs => IOnlinePlayTestSceneDependencies.cs} | 2 +- osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs | 2 +- .../Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game/Tests/Visual/Multiplayer/{IMultiplayerTestDependencies.cs => IMultiplayerTestSceneDependencies.cs} (92%) rename osu.Game/Tests/Visual/OnlinePlay/{IOnlinePlayTestDependencies.cs => IOnlinePlayTestSceneDependencies.cs} (95%) diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs similarity index 92% rename from osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs rename to osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs index 46ad5a5a1c..204c189591 100644 --- a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// Interface that defines the dependencies required for multiplayer test scenes. /// - public interface IMultiplayerTestDependencies : IOnlinePlayTestDependencies + public interface IMultiplayerTestSceneDependencies : IOnlinePlayTestSceneDependencies { /// /// The cached . diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index da01010dbf..b7d3793ab1 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// The base test scene for all multiplayer components and screens. /// - public abstract class MultiplayerTestScene : OnlinePlayTestScene, IMultiplayerTestDependencies + public abstract class MultiplayerTestScene : OnlinePlayTestScene, IMultiplayerTestSceneDependencies { public const int PLAYER_1_ID = 55; public const int PLAYER_2_ID = 56; diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs index 8fd21b5e4a..a2b0b066a7 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// Contains the basic dependencies of multiplayer test scenes. /// - public class MultiplayerTestSceneDependencies : OnlinePlayTestSceneDependencies, IMultiplayerTestDependencies + public class MultiplayerTestSceneDependencies : OnlinePlayTestSceneDependencies, IMultiplayerTestSceneDependencies { public TestMultiplayerClient Client { get; } public TestUserLookupCache LookupCache { get; } diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs similarity index 95% rename from osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs rename to osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs index 8c262e718a..6e1e831d9b 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs @@ -11,7 +11,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// Interface that defines the dependencies required for online play test scenes. /// - public interface IOnlinePlayTestDependencies + public interface IOnlinePlayTestSceneDependencies { /// /// The cached . diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 4920ca2058..997c910dd4 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// A base test scene for all online play components and screens. /// - public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestDependencies + public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestSceneDependencies { public Bindable SelectedRoom => OnlinePlayDependencies?.SelectedRoom; public IRoomManager RoomManager => OnlinePlayDependencies?.RoomManager; diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs index b73a982fb6..ddbbfe501b 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// Contains the basic dependencies of online play test scenes. /// - public class OnlinePlayTestSceneDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies + public class OnlinePlayTestSceneDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestSceneDependencies { public Bindable SelectedRoom { get; } public IRoomManager RoomManager { get; } From 3e5ae7ea58090d9b30d31cbb52ccb2dfd68fe45a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 20:44:00 +0900 Subject: [PATCH 203/457] Don't join room in participants test --- .../Multiplayer/TestSceneMultiplayerParticipantsList.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 7f8f04b718..e94750c695 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -22,6 +22,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerParticipantsList : MultiplayerTestScene { + public TestSceneMultiplayerParticipantsList() + : base(false) + { + } + [SetUp] public new void Setup() => Schedule(createNewParticipantsList); From d035633f9514d78b7dfc88a1f567220169eb061b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 20:56:13 +0900 Subject: [PATCH 204/457] Load participants list after joining room --- .../TestSceneMultiplayerParticipantsList.cs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index e94750c695..6526f7eea7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -22,14 +22,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerParticipantsList : MultiplayerTestScene { - public TestSceneMultiplayerParticipantsList() - : base(false) + [SetUpSteps] + public void SetupSteps() { + createNewParticipantsList(); } - [SetUp] - public new void Setup() => Schedule(createNewParticipantsList); - [Test] public void TestAddUser() { @@ -93,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestCorrectInitialState() { AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); - AddStep("recreate list", createNewParticipantsList); + createNewParticipantsList(); checkProgressBarVisibility(true); } @@ -238,7 +236,17 @@ namespace osu.Game.Tests.Visual.Multiplayer private void createNewParticipantsList() { - Child = new ParticipantsList { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, Size = new Vector2(380, 0.7f) }; + ParticipantsList participantsList = null; + + AddStep("create new list", () => Child = participantsList = new ParticipantsList + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Size = new Vector2(380, 0.7f) + }); + + AddUntilStep("wait for list to load", () => participantsList.IsLoaded); } private void checkProgressBarVisibility(bool visible) => From 6bc71590c539ef0dba6993aab0a5a48a95dfea7e Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Fri, 25 Jun 2021 09:21:26 -0700 Subject: [PATCH 205/457] Disable logo click sound when exiting --- osu.Game/Screens/Menu/ButtonSystem.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index da0edd07db..38290a6530 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -261,7 +261,7 @@ namespace osu.Game.Screens.Menu switch (state) { default: - return true; + return false; case ButtonSystemState.Initial: State = ButtonSystemState.TopLevel; @@ -274,10 +274,6 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.Play: buttonsPlay.First().Click(); return false; - - // no sound should be played if the logo is clicked on while transitioning to song select - case ButtonSystemState.EnteringMode: - return false; } } From 50c27d26357f2646548c126c7201398a23ee489e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Jun 2021 19:10:04 +0200 Subject: [PATCH 206/457] Update usages of `IHasTooltip` in line with framework localisation changes --- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 3 ++- .../Sliders/Components/PathControlPointPiece.cs | 3 ++- osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs | 3 ++- osu.Game/Configuration/SettingSourceAttribute.cs | 2 +- osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs | 3 ++- osu.Game/Graphics/Containers/OsuClickableContainer.cs | 3 ++- osu.Game/Graphics/Cursor/OsuTooltipContainer.cs | 3 ++- osu.Game/Graphics/UserInterface/ExternalLinkButton.cs | 3 ++- osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs | 3 ++- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 3 ++- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 5 +++-- osu.Game/Overlays/BeatmapSet/BasicStats.cs | 2 +- osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs | 3 ++- .../Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs | 3 ++- osu.Game/Overlays/Comments/DrawableComment.cs | 5 +++-- osu.Game/Overlays/Mods/ModButton.cs | 3 ++- osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs | 3 ++- .../Overlays/Profile/Header/Components/DrawableBadge.cs | 3 ++- .../Profile/Header/Components/ExpandDetailsButton.cs | 3 ++- .../Overlays/Profile/Header/Components/FollowersButton.cs | 3 ++- osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs | 3 ++- .../Profile/Header/Components/LevelProgressBar.cs | 3 ++- .../Profile/Header/Components/MappingSubscribersButton.cs | 3 ++- .../Profile/Header/Components/MessageUserButton.cs | 3 ++- .../Profile/Header/Components/OverlinedTotalPlayTime.cs | 3 ++- .../Overlays/Profile/Header/Components/SupporterIcon.cs | 3 ++- .../Sections/Historical/DrawableMostPlayedBeatmap.cs | 2 +- osu.Game/Overlays/RestoreDefaultValueButton.cs | 3 ++- .../Overlays/Settings/Sections/Audio/OffsetSettings.cs | 3 ++- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 2 +- .../Overlays/Settings/Sections/Input/MouseSettings.cs | 3 ++- osu.Game/Overlays/Settings/Sections/SizeSlider.cs | 3 ++- .../Settings/Sections/UserInterface/GeneralSettings.cs | 3 ++- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 5 +++-- osu.Game/Overlays/Settings/SettingsButton.cs | 8 +++++--- osu.Game/Overlays/Settings/SettingsItem.cs | 4 ++-- osu.Game/Rulesets/UI/ModIcon.cs | 3 ++- .../Screens/Edit/Compose/Components/SelectionBoxButton.cs | 3 ++- .../Screens/OnlinePlay/Components/DrawableGameType.cs | 3 ++- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 +- osu.Game/Users/Drawables/ClickableAvatar.cs | 7 ++++--- osu.Game/Users/Drawables/DrawableFlag.cs | 3 ++- 42 files changed, 87 insertions(+), 50 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 1c89d9cd00..f89750a96e 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mania.Configuration; @@ -47,7 +48,7 @@ namespace osu.Game.Rulesets.Mania private class TimeSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString("N0") + "ms"; + public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms"; } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 48e4db11ca..5b476526c9 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; @@ -283,6 +284,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } } - public string TooltipText => ControlPoint.Type.Value.ToString() ?? string.Empty; + public LocalisableString TooltipText => ControlPoint.Type.Value.ToString() ?? string.Empty; } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs index c5374d50ab..096bccae9e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; @@ -59,7 +60,7 @@ namespace osu.Game.Tests.Visual.UserInterface private class Icon : Container, IHasTooltip { - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public SpriteIcon SpriteIcon { get; } diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 55636495df..f373e59417 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -31,7 +31,7 @@ namespace osu.Game.Configuration { public LocalisableString Label { get; } - public string Description { get; } + public LocalisableString Description { get; } public int? OrderPosition { get; } diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs index 75c73af0ce..ce8a9c8f9f 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs @@ -4,12 +4,13 @@ using Markdig.Syntax.Inlines; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownImage : MarkdownImage, IHasTooltip { - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public OsuMarkdownImage(LinkInline linkInline) : base(linkInline.Url) diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index 60ded8952d..0bc3c876e1 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.Containers @@ -24,7 +25,7 @@ namespace osu.Game.Graphics.Containers this.sampleSet = sampleSet; } - public virtual string TooltipText { get; set; } + public virtual LocalisableString TooltipText { get; set; } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index 57f39bb8c7..81dca99ddd 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.Cursor @@ -32,7 +33,7 @@ namespace osu.Game.Graphics.Cursor public override bool SetContent(object content) { - if (!(content is string contentString)) + if (!(content is LocalisableString contentString)) return false; if (contentString == text.Text) return true; diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 5a1eb53fe1..6ad88eaaba 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Platform; using osuTK; using osuTK.Graphics; @@ -58,6 +59,6 @@ namespace osu.Game.Graphics.UserInterface return true; } - public string TooltipText => "view in browser"; + public LocalisableString TooltipText => "view in browser"; } } diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs index ac6f5ceb1b..8e82f4a7c1 100644 --- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Platform; namespace osu.Game.Graphics.UserInterface @@ -105,7 +106,7 @@ namespace osu.Game.Graphics.UserInterface private class CapsWarning : SpriteIcon, IHasTooltip { - public string TooltipText => @"caps lock is active"; + public LocalisableString TooltipText => "caps lock is active"; public CapsWarning() { diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index f58962f8e1..ae16169123 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Localisation; namespace osu.Game.Graphics.UserInterface { @@ -34,7 +35,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public virtual string TooltipText { get; private set; } + public virtual LocalisableString TooltipText { get; private set; } /// /// Whether to format the tooltip as a percentage or the actual value. diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 795540b65d..e35d3d6461 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -295,7 +296,7 @@ namespace osu.Game.Online.Leaderboards public override bool Contains(Vector2 screenSpacePos) => content.Contains(screenSpacePos); - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public ScoreComponentLabel(LeaderboardScoreStatistic statistic) { @@ -365,7 +366,7 @@ namespace osu.Game.Online.Leaderboards }; } - public string TooltipText { get; } + public LocalisableString TooltipText { get; } } public class LeaderboardScoreStatistic diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index cf74c0d4d3..b81c60a5b9 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.BeatmapSet { private readonly OsuSpriteText value; - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public LocalisableString Value { diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 7ad6906cea..bb87e7151b 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -28,7 +29,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private readonly IBindable localUser = new Bindable(); - public string TooltipText + public LocalisableString TooltipText { get { diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs index 6d27342049..cef623e59b 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -26,7 +27,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private readonly bool noVideo; - public string TooltipText => button.Enabled.Value ? "download this beatmap" : "login to download"; + public LocalisableString TooltipText => button.Enabled.Value ? "download this beatmap" : "login to download"; private readonly IBindable localUser = new Bindable(); diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 7c47ac655f..d94f8c4b8b 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -20,6 +20,7 @@ using System; using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.IEnumerableExtensions; using System.Collections.Specialized; +using osu.Framework.Localisation; using osu.Game.Overlays.Comments.Buttons; namespace osu.Game.Overlays.Comments @@ -395,7 +396,7 @@ namespace osu.Game.Overlays.Comments private class ParentUsername : FillFlowContainer, IHasTooltip { - public string TooltipText => getParentMessage(); + public LocalisableString TooltipText => getParentMessage(); private readonly Comment parentComment; @@ -427,7 +428,7 @@ namespace osu.Game.Overlays.Comments if (parentComment == null) return string.Empty; - return parentComment.HasMessage ? parentComment.Message : parentComment.IsDeleted ? @"deleted" : string.Empty; + return parentComment.HasMessage ? parentComment.Message : parentComment.IsDeleted ? "deleted" : string.Empty; } } } diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 70424101fd..d0bd24496a 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -14,6 +14,7 @@ using System; using System.Linq; using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -34,7 +35,7 @@ namespace osu.Game.Overlays.Mods /// public Action SelectionChanged; - public string TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty; + public LocalisableString TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty; private const Easing mod_switch_easing = Easing.InOutSine; private const double mod_switch_duration = 120; diff --git a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs index 87b9d89d4d..0ece96b56c 100644 --- a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs +++ b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics.UserInterface; using osu.Framework.Allocation; using osuTK.Graphics; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; namespace osu.Game.Overlays { @@ -56,7 +57,7 @@ namespace osu.Game.Overlays [Resolved] private OverlayColourProvider colourProvider { get; set; } - public string TooltipText => $@"{Value} view"; + public LocalisableString TooltipText => $@"{Value} view"; private readonly SpriteIcon icon; diff --git a/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs index 7eed4d3b6b..74f3ed846b 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; using osu.Game.Users; using osuTK; @@ -42,6 +43,6 @@ namespace osu.Game.Overlays.Profile.Header.Components InternalChild.FadeInFromZero(200); } - public string TooltipText => badge.Description; + public LocalisableString TooltipText => badge.Description; } } diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs index 29e13e4f51..527c70685f 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osuTK; namespace osu.Game.Overlays.Profile.Header.Components @@ -14,7 +15,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly BindableBool DetailsVisible = new BindableBool(); - public override string TooltipText => DetailsVisible.Value ? "collapse" : "expand"; + public override LocalisableString TooltipText => DetailsVisible.Value ? "collapse" : "expand"; private SpriteIcon icon; diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index bd8aa7b3bd..db94762efd 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -12,7 +13,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public override string TooltipText => "followers"; + public override LocalisableString TooltipText => "followers"; protected override IconUsage Icon => FontAwesome.Solid.User; diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs index 29471375b5..a0b8ef0f11 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Users; @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public string TooltipText { get; } + public LocalisableString TooltipText { get; } private OsuSpriteText levelText; diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs index c97df3bc4d..528b05a80a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public string TooltipText { get; } + public LocalisableString TooltipText { get; } private Bar levelProgressBar; private OsuSpriteText levelProgressText; diff --git a/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs b/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs index b4d7c9a05c..ae3d024fbf 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -12,7 +13,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public override string TooltipText => "mapping subscribers"; + public override LocalisableString TooltipText => "mapping subscribers"; protected override IconUsage Icon => FontAwesome.Solid.Bell; diff --git a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs index 228765ee1a..4c2cc768ce 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Online.API; using osu.Game.Online.Chat; using osu.Game.Users; @@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public override string TooltipText => "send message"; + public override LocalisableString TooltipText => "send message"; [Resolved(CanBeNull = true)] private ChannelManager channelManager { get; set; } diff --git a/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs index be96840217..aa7cb8636a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -14,7 +15,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public string TooltipText { get; set; } + public LocalisableString TooltipText { get; set; } private OverlinedInfoContainer info; diff --git a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs index d581e2750c..9a43997030 100644 --- a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Graphics; namespace osu.Game.Overlays.Profile.Header.Components @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private readonly FillFlowContainer iconContainer; private readonly CircularContainer content; - public string TooltipText => "osu!supporter"; + public LocalisableString TooltipText => "osu!supporter"; public int SupportLevel { diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index 6d6ff32aac..6f1869966a 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -143,7 +143,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical private class PlayCountText : CompositeDrawable, IHasTooltip { - public string TooltipText => "times played"; + public LocalisableString TooltipText => "times played"; public PlayCountText(int playCount) { diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 213ad2ba68..fe36f6ba6d 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -76,7 +77,7 @@ namespace osu.Game.Overlays UpdateState(); } - public string TooltipText => "revert to default"; + public LocalisableString TooltipText => "revert to default"; protected override bool OnHover(HoverEvent e) { diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index c9a81b955b..1ae297f2a9 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -32,7 +33,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio private class OffsetSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString(@"0ms"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0ms"); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 937bcc8abf..669753d2cb 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -233,7 +233,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private class UIScaleSlider : OsuSliderBar { - public override string TooltipText => base.TooltipText + "x"; + public override LocalisableString TooltipText => base.TooltipText + "x"; } private class ResolutionSettingsDropdown : SettingsDropdown diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index fb908a7669..e87572e2ca 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Mouse; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Input; @@ -116,7 +117,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private class SensitivitySlider : OsuSliderBar { - public override string TooltipText => Current.Disabled ? "enable high precision mouse to adjust sensitivity" : $"{base.TooltipText}x"; + public override LocalisableString TooltipText => Current.Disabled ? "enable high precision mouse to adjust sensitivity" : $"{base.TooltipText}x"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs index 101d8f43f7..8aeb440be1 100644 --- a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs +++ b/osu.Game/Overlays/Settings/Sections/SizeSlider.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.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings.Sections @@ -10,6 +11,6 @@ namespace osu.Game.Overlays.Settings.Sections /// internal class SizeSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString(@"0.##x"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x"); } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index 19adfc5dd9..a6eb008623 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -44,7 +45,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface private class TimeSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString("N0") + "ms"; + public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index c73a783d37..2470c0a6c5 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -62,12 +63,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface private class MaximumStarsSlider : StarsSlider { - public override string TooltipText => Current.IsDefault ? "no limit" : base.TooltipText; + public override LocalisableString TooltipText => Current.IsDefault ? "no limit" : base.TooltipText; } private class StarsSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString(@"0.## stars"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); } } } diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs index 088d69c031..87b1aa0e46 100644 --- a/osu.Game/Overlays/Settings/SettingsButton.cs +++ b/osu.Game/Overlays/Settings/SettingsButton.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings @@ -17,14 +18,15 @@ namespace osu.Game.Overlays.Settings Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }; } - public string TooltipText { get; set; } + public LocalisableString TooltipText { get; set; } public override IEnumerable FilterTerms { get { - if (TooltipText != null) - return base.FilterTerms.Append(TooltipText); + if (TooltipText != default) + // TODO: this won't work as intended once the tooltip text is translated. + return base.FilterTerms.Append(TooltipText.ToString()); return base.FilterTerms; } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 807916e7f6..15a0a42d31 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Settings public bool ShowsDefaultIndicator = true; - public string TooltipText { get; set; } + public LocalisableString TooltipText { get; set; } [Resolved] private OsuColour colours { get; set; } @@ -142,4 +142,4 @@ namespace osu.Game.Overlays.Settings labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1; } } -} \ No newline at end of file +} diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index cae5da3d16..725cfa9c26 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osuTK; using osu.Framework.Bindables; +using osu.Framework.Localisation; namespace osu.Game.Rulesets.UI { @@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.UI private const float size = 80; - public virtual string TooltipText => showTooltip ? mod.IconTooltip : null; + public virtual LocalisableString TooltipText => showTooltip ? mod.IconTooltip : null; private Mod mod; private readonly bool showTooltip; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs index 3b1dae6c3d..3ac40fda0f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osuTK; using osuTK.Graphics; @@ -58,6 +59,6 @@ namespace osu.Game.Screens.Edit.Compose.Components icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint); } - public string TooltipText { get; } + public LocalisableString TooltipText { get; } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs b/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs index c4dc2a2b8f..ae1ca1b967 100644 --- a/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs +++ b/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Online.Rooms; @@ -16,7 +17,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { private readonly GameType type; - public string TooltipText => type.Name; + public LocalisableString TooltipText => type.Name; public DrawableGameType(GameType type) { diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index e1cf0cef4e..4a35202df2 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -431,7 +431,7 @@ namespace osu.Game.Screens.Select public class InfoLabel : Container, IHasTooltip { - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public InfoLabel(BeatmapStatistic statistic) { diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index c3bf740108..f73489ac61 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics.Containers; namespace osu.Game.Users.Drawables @@ -68,11 +69,11 @@ namespace osu.Game.Users.Drawables private class ClickableArea : OsuClickableContainer { - private string tooltip = default_tooltip_text; + private LocalisableString tooltip = default_tooltip_text; - public override string TooltipText + public override LocalisableString TooltipText { - get => Enabled.Value ? tooltip : null; + get => Enabled.Value ? tooltip : default; set => tooltip = value; } diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index 1d648e46b6..aea40a01ae 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; namespace osu.Game.Users.Drawables { @@ -13,7 +14,7 @@ namespace osu.Game.Users.Drawables { private readonly Country country; - public string TooltipText => country?.FullName; + public LocalisableString TooltipText => country?.FullName; public DrawableFlag(Country country) { From 3b822cd5cf45af3cd21bbfed9471b4f3b60a07c5 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 26 Jun 2021 11:19:14 +0800 Subject: [PATCH 207/457] Refactor `SeedSettingsControl` and related controls --- .../Overlays/Settings/OutlinedNumberBox.cs | 10 ++++ osu.Game/Overlays/Settings/OutlinedTextBox.cs | 49 +++++++++++++++++++ .../Overlays/Settings/SettingsNumberBox.cs | 8 +-- osu.Game/Overlays/Settings/SettingsTextBox.cs | 47 +----------------- osu.Game/Rulesets/Mods/SeedSettingsControl.cs | 33 +++---------- 5 files changed, 70 insertions(+), 77 deletions(-) create mode 100644 osu.Game/Overlays/Settings/OutlinedNumberBox.cs create mode 100644 osu.Game/Overlays/Settings/OutlinedTextBox.cs diff --git a/osu.Game/Overlays/Settings/OutlinedNumberBox.cs b/osu.Game/Overlays/Settings/OutlinedNumberBox.cs new file mode 100644 index 0000000000..6fcadc09e1 --- /dev/null +++ b/osu.Game/Overlays/Settings/OutlinedNumberBox.cs @@ -0,0 +1,10 @@ +// 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.Overlays.Settings +{ + public class OutlinedNumberBox : OutlinedTextBox + { + protected override bool CanAddCharacter(char character) => char.IsNumber(character); + } +} diff --git a/osu.Game/Overlays/Settings/OutlinedTextBox.cs b/osu.Game/Overlays/Settings/OutlinedTextBox.cs new file mode 100644 index 0000000000..93eaf74b77 --- /dev/null +++ b/osu.Game/Overlays/Settings/OutlinedTextBox.cs @@ -0,0 +1,49 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Settings +{ + public class OutlinedTextBox : OsuTextBox + { + private const float border_thickness = 3; + + private Color4 borderColourFocused; + private Color4 borderColourUnfocused; + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + borderColourUnfocused = colour.Gray4.Opacity(0.5f); + borderColourFocused = BorderColour; + + updateBorder(); + } + + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + + updateBorder(); + } + + protected override void OnFocusLost(FocusLostEvent e) + { + base.OnFocusLost(e); + + updateBorder(); + } + + private void updateBorder() + { + BorderThickness = border_thickness; + BorderColour = HasFocus ? borderColourFocused : borderColourUnfocused; + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index ca9a8e9c08..d4d1fc8610 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -7,15 +7,11 @@ namespace osu.Game.Overlays.Settings { public class SettingsNumberBox : SettingsItem { - protected override Drawable CreateControl() => new NumberBox + protected override Drawable CreateControl() => new OutlinedNumberBox { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, + CommitOnFocusLost = true }; - - public class NumberBox : SettingsTextBox.TextBox - { - protected override bool CanAddCharacter(char character) => char.IsNumber(character); - } } } diff --git a/osu.Game/Overlays/Settings/SettingsTextBox.cs b/osu.Game/Overlays/Settings/SettingsTextBox.cs index 25424e85a1..d28dbf1068 100644 --- a/osu.Game/Overlays/Settings/SettingsTextBox.cs +++ b/osu.Game/Overlays/Settings/SettingsTextBox.cs @@ -1,60 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using osuTK.Graphics; namespace osu.Game.Overlays.Settings { public class SettingsTextBox : SettingsItem { - protected override Drawable CreateControl() => new TextBox + protected override Drawable CreateControl() => new OutlinedTextBox { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true, + CommitOnFocusLost = true }; - - public class TextBox : OsuTextBox - { - private const float border_thickness = 3; - - private Color4 borderColourFocused; - private Color4 borderColourUnfocused; - - [BackgroundDependencyLoader] - private void load(OsuColour colour) - { - borderColourUnfocused = colour.Gray4.Opacity(0.5f); - borderColourFocused = BorderColour; - - updateBorder(); - } - - protected override void OnFocus(FocusEvent e) - { - base.OnFocus(e); - - updateBorder(); - } - - protected override void OnFocusLost(FocusLostEvent e) - { - base.OnFocusLost(e); - - updateBorder(); - } - - private void updateBorder() - { - BorderThickness = border_thickness; - BorderColour = HasFocus ? borderColourFocused : borderColourUnfocused; - } - } } } diff --git a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs index 1280197532..1eaf31874b 100644 --- a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mods } } - private readonly SettingsNumberBox.NumberBox seedNumberBox; + private readonly OutlinedNumberBox seedNumberBox; public SeedControl() { @@ -42,31 +42,11 @@ namespace osu.Game.Rulesets.Mods InternalChildren = new[] { - new GridContainer + seedNumberBox = new OutlinedNumberBox { + Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 2), - new Dimension(GridSizeMode.Relative, 0.25f) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] - { - seedNumberBox = new SettingsNumberBox.NumberBox - { - RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true - } - } - } + CommitOnFocusLost = true } }; @@ -83,8 +63,9 @@ namespace osu.Game.Rulesets.Mods protected override void Update() { - if (current.Value == null) - seedNumberBox.Text = current.Current.Value.ToString(); + base.Update(); + if (Current.Value == null) + seedNumberBox.Current.Value = ""; } } } From e5eea503dbdfebee5f2911354ded244885a5f5d1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 26 Jun 2021 12:21:49 +0300 Subject: [PATCH 208/457] Remove finalizer logic from `ResourcesSkin` --- osu.Game/Skinning/ResourcesSkin.cs | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/osu.Game/Skinning/ResourcesSkin.cs b/osu.Game/Skinning/ResourcesSkin.cs index 3d17d7cc3d..90020495c3 100644 --- a/osu.Game/Skinning/ResourcesSkin.cs +++ b/osu.Game/Skinning/ResourcesSkin.cs @@ -19,7 +19,7 @@ namespace osu.Game.Skinning /// /// An that uses an underlying with namespaces for resources retrieval. /// - public class ResourcesSkin : ISkin + public class ResourcesSkin : ISkin, IDisposable { private readonly TextureStore textures; private readonly ISampleStore samples; @@ -48,33 +48,10 @@ namespace osu.Game.Skinning public IBindable? GetConfig(TLookup lookup) => null; - #region Disposal - - ~ResourcesSkin() - { - // required to potentially clean up sample store from audio hierarchy. - Dispose(false); - } - public void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); - } - - private bool isDisposed; - - protected virtual void Dispose(bool isDisposing) - { - if (isDisposed) - return; - - isDisposed = true; - textures.Dispose(); samples.Dispose(); } - - #endregion } } From e8e9fdd5331dec8d6522c23dcb7b03827b4ef666 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 26 Jun 2021 12:23:05 +0300 Subject: [PATCH 209/457] Dispose `ResourcesSkin` before clearing skin sources --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 54bf91523f..8a807eff21 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.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.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -72,6 +73,9 @@ namespace osu.Game.Skinning protected virtual void UpdateSkins() { + foreach (var resourcesSkin in SkinSources.OfType()) + resourcesSkin.Dispose(); + SkinSources.Clear(); foreach (var skin in skinSource.AllSources) From 8e1bcc4d6bd751a5112451f3f9c0ed06a8937714 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 26 Jun 2021 21:02:31 +0700 Subject: [PATCH 210/457] add overall difficulty in filter criteria --- osu.Game/Screens/Select/FilterCriteria.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 208048380a..b9e912df8e 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -24,6 +24,7 @@ namespace osu.Game.Screens.Select public OptionalRange ApproachRate; public OptionalRange DrainRate; public OptionalRange CircleSize; + public OptionalRange OverallDifficulty; public OptionalRange Length; public OptionalRange BPM; public OptionalRange BeatDivisor; From 4df4afe533aed92ee7c8d7f22d14b1047007283b Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 26 Jun 2021 21:02:57 +0700 Subject: [PATCH 211/457] add test for overall difficulty filter query --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 9bd262a569..a55bdd2df8 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -90,6 +90,20 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.Less(filterCriteria.DrainRate.Min, 6.1f); } + [Test] + public void TestApplyOverallDifficultyQueries() + { + const string query = "od>4 easy od<8"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("easy", filterCriteria.SearchText.Trim()); + Assert.AreEqual(1, filterCriteria.SearchTerms.Length); + Assert.Greater(filterCriteria.OverallDifficulty.Min, 4.0); + Assert.Less(filterCriteria.OverallDifficulty.Min, 4.1); + Assert.Greater(filterCriteria.OverallDifficulty.Max, 7.9); + Assert.Less(filterCriteria.OverallDifficulty.Max, 8.0); + } + [Test] public void TestApplyBPMQueries() { From 2b1d3c8e9c9ce4eb5e5790032d64da556a0f7c6f Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 26 Jun 2021 21:05:01 +0700 Subject: [PATCH 212/457] add od filter in search filter --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 1 + osu.Game/Screens/Select/FilterQueryParser.cs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 521b90202d..f95ddfee41 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -42,6 +42,7 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(Beatmap.BaseDifficulty.ApproachRate); match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(Beatmap.BaseDifficulty.DrainRate); match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(Beatmap.BaseDifficulty.CircleSize); + match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(Beatmap.BaseDifficulty.OverallDifficulty); match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(Beatmap.Length); match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(Beatmap.BPM); diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index db2803d29a..72d10019b2 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -51,6 +51,9 @@ namespace osu.Game.Screens.Select case "cs": return TryUpdateCriteriaRange(ref criteria.CircleSize, op, value); + case "od": + return TryUpdateCriteriaRange(ref criteria.OverallDifficulty, op, value); + case "bpm": return TryUpdateCriteriaRange(ref criteria.BPM, op, value, 0.01d / 2); From d8117fa73032af9b130c768fd1b1c0a694268580 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:20:34 +0200 Subject: [PATCH 213/457] Add muted objects check --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 1 + .../Rulesets/Edit/Checks/CheckMutedObjects.cs | 133 ++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index d208c7fe07..462a87af85 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Edit // Audio new CheckAudioPresence(), new CheckAudioQuality(), + new CheckMutedObjects(), // Compose new CheckUnsnappedObjects(), diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs new file mode 100644 index 0000000000..cbe7c7fbab --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -0,0 +1,133 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Utils; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckMutedObjects : ICheck + { + /// + /// Volume percentages lower than this are typically inaudible. + /// + private const int muted_threshold = 5; + + /// + /// Volume percentages lower than this can sometimes be inaudible depending on sample used and music volume. + /// + private const int low_volume_threshold = 20; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Low volume hitobjects"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateMutedActive(this), + new IssueTemplateLowVolumeActive(this), + new IssueTemplateMutedPassive(this) + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + foreach (var hitObject in context.Beatmap.HitObjects) + { + // Worth keeping in mind: The samples of an object always play at its end time. + // Objects like spinners have no sound at its start because of this, while hold notes have nested objects to accomplish this. + foreach (var nestedHitObject in hitObject.NestedHitObjects) + { + foreach (var issue in getVolumeIssues(hitObject, nestedHitObject)) + yield return issue; + } + + foreach (var issue in getVolumeIssues(hitObject)) + yield return issue; + } + } + + private IEnumerable getVolumeIssues(HitObject hitObject, HitObject sampledHitObject = null) + { + sampledHitObject ??= hitObject; + if (!sampledHitObject.Samples.Any()) + yield break; + + // Samples that allow themselves to be overridden by control points have a volume of 0. + int maxVolume = sampledHitObject.Samples.Max(sample => sample.Volume > 0 ? sample.Volume : sampledHitObject.SampleControlPoint.SampleVolume); + double samplePlayTime = sampledHitObject.GetEndTime(); + + bool head = Precision.AlmostEquals(samplePlayTime, hitObject.StartTime, 1f); + bool tail = Precision.AlmostEquals(samplePlayTime, hitObject.GetEndTime(), 1f); + bool repeat = false; + + if (hitObject is IHasRepeats hasRepeats && !head && !tail) + { + double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); + repeat = Precision.AlmostEquals((samplePlayTime - hitObject.StartTime) % spanDuration, 0f, 1f); + } + + // We only care about samples played on the edges of objects, not ones like spinnerspin or slidertick. + if (!head && !tail && !repeat) + yield break; + + string postfix = null; + if (hitObject is IHasDuration) + postfix = head ? "head" : tail ? "tail" : "repeat"; + + if (maxVolume <= muted_threshold) + { + if (head) + yield return new IssueTemplateMutedActive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); + else + yield return new IssueTemplateMutedPassive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); + } + else if (maxVolume <= low_volume_threshold && head) + { + yield return new IssueTemplateLowVolumeActive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); + } + } + + public abstract class IssueTemplateMuted : IssueTemplate + { + protected IssueTemplateMuted(ICheck check, IssueType type, string unformattedMessage) + : base(check, type, unformattedMessage) + { + } + + public Issue Create(HitObject hitobject, double volume, double time, string postfix = "") + { + string objectName = hitobject.GetType().Name; + if (!string.IsNullOrEmpty(postfix)) + objectName += " " + postfix; + + return new Issue(hitobject, this, objectName, volume) { Time = time }; + } + } + + public class IssueTemplateMutedActive : IssueTemplateMuted + { + public IssueTemplateMutedActive(ICheck check) + : base(check, IssueType.Problem, "{0} has a volume of {1:0%}. Clickable objects must have clearly audible feedback.") + { + } + } + + public class IssueTemplateLowVolumeActive : IssueTemplateMuted + { + public IssueTemplateLowVolumeActive(ICheck check) + : base(check, IssueType.Warning, "{0} has a volume of {1:0%}, ensure this is audible.") + { + } + } + + public class IssueTemplateMutedPassive : IssueTemplateMuted + { + public IssueTemplateMutedPassive(ICheck check) + : base(check, IssueType.Negligible, "{0} has a volume of {1:0%}, ensure there is no distinct sound here in the song if inaudible.") + { + } + } + } +} From 4b436b774dcb3c35d145410fb56edc12e2c66ebb Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:20:46 +0200 Subject: [PATCH 214/457] Add few hitsounds check --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 1 + .../Rulesets/Edit/Checks/CheckFewHitsounds.cs | 161 ++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 462a87af85..706eec226c 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Edit new CheckAudioPresence(), new CheckAudioQuality(), new CheckMutedObjects(), + new CheckFewHitsounds(), // Compose new CheckUnsnappedObjects(), diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs new file mode 100644 index 0000000000..07ca470e62 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -0,0 +1,161 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Audio; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckFewHitsounds : ICheck + { + /// + /// 2 measures (4/4) of 120 BPM, typically makes up a few patterns in the map. + /// This is almost always ok, but can still be useful for the mapper to make sure hitsounding coverage is good. + /// + private const int negligible_threshold_time = 4000; + + /// + /// 4 measures (4/4) of 120 BPM, typically makes up a large portion of a section in the song. + /// This is ok if the section is a quiet intro, for example. + /// + private const int warning_threshold_time = 8000; + + /// + /// 12 measures (4/4) of 120 BPM, typically makes up multiple sections in the song. + /// + private const int problem_threshold_time = 24000; + + // Should pass at least this many objects without hitsounds to be considered an issue (should work for Easy diffs too). + private const int warning_threshold_objects = 4; + private const int problem_threshold_objects = 16; + + private static readonly string[] hitsound_types = { HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE, HitSampleInfo.HIT_FINISH }; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Few or no hitsounds"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateLongPeriodProblem(this), + new IssueTemplateLongPeriodWarning(this), + new IssueTemplateNoHitsounds(this) + }; + + private bool hasHitsounds; + private int objectsWithoutHitsounds; + private double lastHitsoundTime; + + public IEnumerable Run(BeatmapVerifierContext context) + { + if (!context.Beatmap.HitObjects.Any()) + yield break; + + hasHitsounds = false; + objectsWithoutHitsounds = 0; + lastHitsoundTime = context.Beatmap.HitObjects.First().StartTime; + + var hitObjectCount = context.Beatmap.HitObjects.Count; + + for (int i = 0; i < hitObjectCount; ++i) + { + var hitObject = context.Beatmap.HitObjects[i]; + + // Samples play on the end of objects. Some objects have nested objects to accomplish playing them elsewhere (e.g. slider head/repeat). + foreach (var nestedHitObject in hitObject.NestedHitObjects) + { + foreach (var issue in applyHitsoundUpdate(nestedHitObject)) + yield return issue; + } + + // This is used to perform an update at the end so that the period after the last hitsounded object can be an issue. + bool isLastObject = i == hitObjectCount - 1; + + foreach (var issue in applyHitsoundUpdate(hitObject, isLastObject)) + yield return issue; + } + + if (!hasHitsounds) + yield return new IssueTemplateNoHitsounds(this).Create(); + } + + private IEnumerable applyHitsoundUpdate(HitObject hitObject, bool isLastObject = false) + { + var time = hitObject.GetEndTime(); + + // Only generating issues on hitsounded or last objects ensures we get one issue per long period. + // If there are no hitsounds we let the "No hitsounds" template take precedence. + if (hasHitsound(hitObject) || isLastObject && hasHitsounds) + { + var timeWithoutHitsounds = time - lastHitsoundTime; + + if (timeWithoutHitsounds > problem_threshold_time && objectsWithoutHitsounds > problem_threshold_objects) + yield return new IssueTemplateLongPeriodProblem(this).Create(lastHitsoundTime, timeWithoutHitsounds); + else if (timeWithoutHitsounds > warning_threshold_time && objectsWithoutHitsounds > warning_threshold_objects) + yield return new IssueTemplateLongPeriodWarning(this).Create(lastHitsoundTime, timeWithoutHitsounds); + else if (timeWithoutHitsounds > negligible_threshold_time && objectsWithoutHitsounds > warning_threshold_objects) + yield return new IssueTemplateLongPeriodNegligible(this).Create(lastHitsoundTime, timeWithoutHitsounds); + } + + if (hasHitsound(hitObject)) + { + hasHitsounds = true; + objectsWithoutHitsounds = 0; + lastHitsoundTime = time; + } + else if (couldHaveHitsound(hitObject)) + ++objectsWithoutHitsounds; + } + + private bool hasHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitsound); + private bool couldHaveHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitnormal); + + private bool isHitsound(HitSampleInfo sample) => hitsound_types.Any(sample.Name.Contains); + private bool isHitnormal(HitSampleInfo sample) => sample.Name.Contains(HitSampleInfo.HIT_NORMAL); + + public abstract class IssueTemplateLongPeriod : IssueTemplate + { + protected IssueTemplateLongPeriod(ICheck check, IssueType type) + : base(check, type, "Long period without hitsounds ({0:F1} seconds).") + { + } + + public Issue Create(double time, double duration) => new Issue(this, duration / 1000f) { Time = time }; + } + + public class IssueTemplateLongPeriodProblem : IssueTemplateLongPeriod + { + public IssueTemplateLongPeriodProblem(ICheck check) + : base(check, IssueType.Problem) + { + } + } + + public class IssueTemplateLongPeriodWarning : IssueTemplateLongPeriod + { + public IssueTemplateLongPeriodWarning(ICheck check) + : base(check, IssueType.Warning) + { + } + } + + public class IssueTemplateLongPeriodNegligible : IssueTemplateLongPeriod + { + public IssueTemplateLongPeriodNegligible(ICheck check) + : base(check, IssueType.Negligible) + { + } + } + + public class IssueTemplateNoHitsounds : IssueTemplate + { + public IssueTemplateNoHitsounds(ICheck check) + : base(check, IssueType.Problem, "There are no hitsounds.") + { + } + + public Issue Create() => new Issue(this); + } + } +} From 7b9569a1176d7a982ef6e9212574a9385c9d665b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:21:01 +0200 Subject: [PATCH 215/457] Add muted object check tests --- .../Editing/Checks/CheckMutedObjectsTest.cs | 317 ++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs new file mode 100644 index 0000000000..4bfe62a64d --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs @@ -0,0 +1,317 @@ +// 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 System.Threading; +using NUnit.Framework; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckMutedObjectsTest + { + private CheckMutedObjects check; + private ControlPointInfo cpi; + + private const int volume_regular = 50; + private const int volume_low = 15; + private const int volume_muted = 5; + + private sealed class MockNestableHitObject : HitObject, IHasDuration + { + private readonly IEnumerable toBeNested; + + public MockNestableHitObject(IEnumerable toBeNested, double startTime, double endTime) + { + this.toBeNested = toBeNested; + StartTime = startTime; + EndTime = endTime; + } + + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) + { + foreach (var hitObject in toBeNested) + AddNested(hitObject); + } + + public double EndTime { get; } + + public double Duration + { + get => EndTime - StartTime; + set => throw new System.NotImplementedException(); + } + } + + [SetUp] + public void Setup() + { + check = new CheckMutedObjects(); + + cpi = new ControlPointInfo(); + cpi.Add(0, new SampleControlPoint { SampleVolume = volume_regular }); + cpi.Add(1000, new SampleControlPoint { SampleVolume = volume_low }); + cpi.Add(2000, new SampleControlPoint { SampleVolume = volume_muted }); + } + + [Test] + public void TestNormalControlPointVolume() + { + var hitcircle = new HitCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertOk(new List { hitcircle }); + } + + [Test] + public void TestLowControlPointVolume() + { + var hitcircle = new HitCircle + { + StartTime = 1000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertLowVolume(new List { hitcircle }); + } + + [Test] + public void TestMutedControlPointVolume() + { + var hitcircle = new HitCircle + { + StartTime = 2000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMuted(new List { hitcircle }); + } + + [Test] + public void TestNormalSampleVolume() + { + // The sample volume should take precedence over the control point volume. + var hitcircle = new HitCircle + { + StartTime = 2000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertOk(new List { hitcircle }); + } + + [Test] + public void TestLowSampleVolume() + { + var hitcircle = new HitCircle + { + StartTime = 2000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_low) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertLowVolume(new List { hitcircle }); + } + + [Test] + public void TestMutedSampleVolume() + { + var hitcircle = new HitCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMuted(new List { hitcircle }); + } + + [Test] + public void TestNormalSampleVolumeSlider() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 250, + Samples = new List { new HitSampleInfo("slidertick", volume: volume_muted) } // Should be fine. + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertOk(new List { slider }); + } + + [Test] + public void TestMutedSampleVolumeSliderHead() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 250, + Samples = new List { new HitSampleInfo("slidertick") } + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } // Applies to the tail. + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMuted(new List { slider }); + } + + [Test] + public void TestMutedSampleVolumeSliderTail() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 250, + Samples = new List { new HitSampleInfo("slidertick") } + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 2500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) } // Applies to the tail. + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMutedPassive(new List { slider }); + } + + [Test] + public void TestMutedControlPointVolumeSliderHead() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 2000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 2250, + Samples = new List { new HitSampleInfo("slidertick") } + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 2000, endTime: 2500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMuted(new List { slider }); + } + + [Test] + public void TestMutedControlPointVolumeSliderTail() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 250, + Samples = new List { new HitSampleInfo("slidertick") } + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + // Ends after the 5% control point. + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 2500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMutedPassive(new List { slider }); + } + + private void assertOk(List hitObjects) + { + Assert.That(check.Run(getContext(hitObjects)), Is.Empty); + } + + private void assertLowVolume(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckMutedObjects.IssueTemplateLowVolumeActive)); + } + + private void assertMuted(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckMutedObjects.IssueTemplateMutedActive)); + } + + private void assertMutedPassive(List hitObjects) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Any(issue => issue.Template is CheckMutedObjects.IssueTemplateMutedPassive)); + } + + private BeatmapVerifierContext getContext(List hitObjects) + { + var beatmap = new Beatmap + { + ControlPointInfo = cpi, + HitObjects = hitObjects + }; + + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} From a5abc664f30b25c249cb39e50ee67067eccebfb7 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:21:15 +0200 Subject: [PATCH 216/457] Add few hitsounds check tests --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs new file mode 100644 index 0000000000..8ae8cd8163 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -0,0 +1,166 @@ +// 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.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckFewHitsoundsTest + { + private CheckFewHitsounds check; + + [SetUp] + public void Setup() + { + check = new CheckFewHitsounds(); + } + + [Test] + public void TestHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 16; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if ((i + 1) % 2 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP)); + if ((i + 1) % 3 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + if ((i + 1) % 4 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH)); + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + assertOk(hitObjects); + } + + [Test] + public void TestLightlyHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 30; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if (i % 8 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + assertLongPeriodNegligible(hitObjects, count: 3); + } + + [Test] + public void TestRarelyHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 30; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if (i == 0 || i == 15) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + // Should prompt one warning between 1st and 11th, and another between 11th and 20th. + assertLongPeriodWarning(hitObjects, count: 2); + } + + [Test] + public void TestExtremelyRarelyHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 80; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if (i == 40) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + // Should prompt one problem between 1st and 40th, and another between 40th and 80th. + assertLongPeriodProblem(hitObjects, count: 2); + } + + [Test] + public void TestNotHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 20; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + // Should prompt one problem between 1st and 40th, and another between 40th and 80th. + assertNoHitsounds(hitObjects); + } + + private void assertOk(List hitObjects) + { + Assert.That(check.Run(getContext(hitObjects)), Is.Empty); + } + + private void assertLongPeriodProblem(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckFewHitsounds.IssueTemplateLongPeriodProblem)); + } + + private void assertLongPeriodWarning(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckFewHitsounds.IssueTemplateLongPeriodWarning)); + } + + private void assertLongPeriodNegligible(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckFewHitsounds.IssueTemplateLongPeriodNegligible)); + } + + private void assertNoHitsounds(List hitObjects) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Any(issue => issue.Template is CheckFewHitsounds.IssueTemplateNoHitsounds)); + } + + private BeatmapVerifierContext getContext(List hitObjects) + { + var beatmap = new Beatmap { HitObjects = hitObjects }; + + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} From 82b64f5589a950b43afc9dc9d6b0e02c548e285c Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:57:12 +0200 Subject: [PATCH 217/457] Add hitsounded with break test --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index 8ae8cd8163..8edc6d096e 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -47,6 +47,31 @@ namespace osu.Game.Tests.Editing.Checks assertOk(hitObjects); } + [Test] + public void TestHitsoundedWithBreak() + { + var hitObjects = new List(); + + for (int i = 0; i < 32; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if ((i + 1) % 2 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP)); + if ((i + 1) % 3 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + if ((i + 1) % 4 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH)); + // Leaves a gap in which no hitsounds exist or can be added, and so shouldn't be an issue. + if (i > 8 && i < 24) + continue; + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + assertOk(hitObjects); + } + [Test] public void TestLightlyHitsounded() { From 51888d0d5a3fbbcf8fbd4787ffa58b9f3d7b56e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 19:27:34 +0200 Subject: [PATCH 218/457] Rename test methods --- .../Visual/Online/TestSceneBeatmapListingOverlay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index cd382c2bb2..16122e496f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestNonSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() + public void TestUserWithoutSupporterUsesSupporterOnlyFiltersWithoutResults() { AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() + public void TestUserWithSupporterUsesSupporterOnlyFiltersWithoutResults() { AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestNonSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() + public void TestUserWithoutSupporterUsesSupporterOnlyFiltersWithResults() { AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); @@ -162,7 +162,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() + public void TestUserWithSupporterUsesSupporterOnlyFiltersWithResults() { AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); From b7c4fe2052a38f30aa1789f8c8b9103660b51b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 20:24:12 +0200 Subject: [PATCH 219/457] Rewrite test helpers to also handle clearing filters --- .../Online/TestSceneBeatmapListingOverlay.cs | 100 +++++++++--------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 16122e496f..66e7248207 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.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.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -14,6 +15,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osu.Game.Rulesets; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Tests.Visual.Online @@ -77,27 +79,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); - // test non-supporter on Rank Achieved filter - toggleRankFilter(Scoring.ScoreRank.XH); + // only Rank Achieved filter + setRankAchievedFilter(new[] { ScoreRank.XH }); supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); notFoundPlaceholderShown(); - // test non-supporter on Played filter - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // only Played filter + setPlayedFilter(SearchPlayed.Played); supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setPlayedFilter(SearchPlayed.Any); notFoundPlaceholderShown(); - // test non-supporter on both Rank Achieved and Played filter - toggleRankFilter(Scoring.ScoreRank.XH); - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // both RankAchieved and Played filters + setRankAchievedFilter(new[] { ScoreRank.XH }); + setPlayedFilter(SearchPlayed.Played); supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setRankAchievedFilter(Array.Empty()); + setPlayedFilter(SearchPlayed.Any); notFoundPlaceholderShown(); } @@ -107,27 +109,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); - // test supporter on Rank Achieved filter - toggleRankFilter(Scoring.ScoreRank.XH); + // only Rank Achieved filter + setRankAchievedFilter(new[] { ScoreRank.XH }); notFoundPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); notFoundPlaceholderShown(); - // test supporter on Played filter - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // only Played filter + setPlayedFilter(SearchPlayed.Played); notFoundPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setPlayedFilter(SearchPlayed.Any); notFoundPlaceholderShown(); - // test supporter on both Rank Achieved and Played filter - toggleRankFilter(Scoring.ScoreRank.XH); - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // both Rank Achieved and Played filters + setRankAchievedFilter(new[] { ScoreRank.XH }); + setPlayedFilter(SearchPlayed.Played); notFoundPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setRankAchievedFilter(Array.Empty()); + setPlayedFilter(SearchPlayed.Any); notFoundPlaceholderShown(); } @@ -137,27 +139,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); - // test non-supporter on Rank Achieved filter - toggleRankFilter(Scoring.ScoreRank.XH); + // only Rank Achieved filter + setRankAchievedFilter(new[] { ScoreRank.XH }); supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); noPlaceholderShown(); - // test non-supporter on Played filter - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // only Played filter + setPlayedFilter(SearchPlayed.Played); supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setPlayedFilter(SearchPlayed.Any); noPlaceholderShown(); - // test non-supporter on both Rank Achieved and Played filter - toggleRankFilter(Scoring.ScoreRank.XH); - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // both Rank Achieved and Played filters + setRankAchievedFilter(new[] { ScoreRank.XH }); + setPlayedFilter(SearchPlayed.Played); supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setRankAchievedFilter(Array.Empty()); + setPlayedFilter(SearchPlayed.Any); noPlaceholderShown(); } @@ -167,27 +169,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); - // test supporter on Rank Achieved filter - toggleRankFilter(Scoring.ScoreRank.XH); + // only Rank Achieved filter + setRankAchievedFilter(new[] { ScoreRank.XH }); noPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); noPlaceholderShown(); - // test supporter on Played filter - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // only Played filter + setPlayedFilter(SearchPlayed.Played); noPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setPlayedFilter(SearchPlayed.Any); noPlaceholderShown(); - // test supporter on both Rank Achieved and Played filter - toggleRankFilter(Scoring.ScoreRank.XH); - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // both Rank Achieved and Played filters + setRankAchievedFilter(new[] { ScoreRank.XH }); + setPlayedFilter(SearchPlayed.Played); noPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); + setPlayedFilter(SearchPlayed.Any); noPlaceholderShown(); } @@ -200,18 +202,18 @@ namespace osu.Game.Tests.Visual.Online searchControl.Query.TriggerChange(); } - private void toggleRankFilter(Scoring.ScoreRank rank) + private void setRankAchievedFilter(ScoreRank[] ranks) { - AddStep("toggle Rank Achieved filter", () => + AddStep($"set Rank Achieved filter to [{string.Join(',', ranks)}]", () => { searchControl.Ranks.Clear(); - searchControl.Ranks.Add(rank); + searchControl.Ranks.AddRange(ranks); }); } - private void toggleSupporterOnlyPlayedFilter(SearchPlayed played) + private void setPlayedFilter(SearchPlayed played) { - AddStep("toggle Played filter", () => searchControl.Played.Value = played); + AddStep($"set Played filter to {played}", () => searchControl.Played.Value = played); } private void supporterRequiredPlaceholderShown() From 709e555566a80bcc9a7623c6721c99c645da2e5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 20:27:15 +0200 Subject: [PATCH 220/457] Rename test steps for legibility --- .../Visual/Online/TestSceneBeatmapListingOverlay.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 66e7248207..5bfb676f81 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -218,17 +218,19 @@ namespace osu.Game.Tests.Visual.Online private void supporterRequiredPlaceholderShown() { - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("\"supporter required\" placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); } private void notFoundPlaceholderShown() { - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("\"no maps found\" placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); } private void noPlaceholderShown() { - AddUntilStep("no placeholder shown", () => !overlay.ChildrenOfType().Any() && !overlay.ChildrenOfType().Any()); + AddUntilStep("no placeholder shown", () => + !overlay.ChildrenOfType().Any() + && !overlay.ChildrenOfType().Any()); } private class TestAPIBeatmapSet : APIBeatmapSet From b56dd7ff25db96dbcd73c1e0f651987bda726e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 20:31:26 +0200 Subject: [PATCH 221/457] Fix naming and xmldocs in new beatmap search result structures --- .../BeatmapListingFilterControl.cs | 43 +++++++++++++------ osu.Game/Overlays/BeatmapListingOverlay.cs | 4 +- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index b6a0846407..d80ef075e9 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -26,8 +26,6 @@ namespace osu.Game.Overlays.BeatmapListing { /// /// Fired when a search finishes. - /// SearchFinished.Type = ResultsReturned when results returned. Contains only new items in the case of pagination. - /// SearchFinished.Type = SupporterOnlyFilter when a non-supporter user applied supporter-only filters. /// public Action SearchFinished; @@ -216,7 +214,7 @@ namespace osu.Game.Overlays.BeatmapListing lastResponse = response; getSetsRequest = null; - // check if an non-supporter user used supporter-only filters + // check if a non-supporter used supporter-only filters if (!api.LocalUser.Value.IsSupporter) { List filters = new List(); @@ -229,7 +227,7 @@ namespace osu.Game.Overlays.BeatmapListing if (filters.Any()) { - SearchFinished?.Invoke(SearchResult.SupporterOnlyFilter(filters)); + SearchFinished?.Invoke(SearchResult.SupporterOnlyFilters(filters)); return; } } @@ -260,21 +258,40 @@ namespace osu.Game.Overlays.BeatmapListing base.Dispose(isDisposing); } + /// + /// Indicates the type of result of a user-requested beatmap search. + /// public enum SearchResultType { - // returned with Results + /// + /// Actual results have been returned from API. + /// ResultsReturned, - // non-supporter user applied supporter-only filters - SupporterOnlyFilter + + /// + /// The user is not a supporter, but used supporter-only search filters. + /// + SupporterOnlyFilters } - // Results only valid when Type == ResultsReturned - // Filters only valid when Type == SupporterOnlyFilter + /// + /// Describes the result of a user-requested beatmap search. + /// public struct SearchResult { public SearchResultType Type { get; private set; } + + /// + /// Contains the beatmap sets returned from API. + /// Valid for read if and only if is . + /// public List Results { get; private set; } - public List Filters { get; private set; } + + /// + /// Contains the names of supporter-only filters requested by the user. + /// Valid for read if and only if is . + /// + public List SupporterOnlyFiltersUsed { get; private set; } public static SearchResult ResultsReturned(List results) => new SearchResult { @@ -282,10 +299,10 @@ namespace osu.Game.Overlays.BeatmapListing Results = results }; - public static SearchResult SupporterOnlyFilter(List filters) => new SearchResult + public static SearchResult SupporterOnlyFilters(List filters) => new SearchResult { - Type = SearchResultType.SupporterOnlyFilter, - Filters = filters + Type = SearchResultType.SupporterOnlyFilters, + SupporterOnlyFiltersUsed = filters }; } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index c2ba3d5bc0..5489f0277f 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -123,9 +123,9 @@ namespace osu.Game.Overlays private void onSearchFinished(BeatmapListingFilterControl.SearchResult searchResult) { // non-supporter user used supporter-only filters - if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilter) + if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilters) { - supporterRequiredContent.UpdateText(searchResult.Filters); + supporterRequiredContent.UpdateText(searchResult.SupporterOnlyFiltersUsed); addContentToPlaceholder(supporterRequiredContent); return; } From 9061ab0a278e058704db7dd4c2ffa6ea476463d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 20:40:54 +0200 Subject: [PATCH 222/457] Update/reword comments in listing overlay --- osu.Game/Overlays/BeatmapListingOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 5489f0277f..460b4ba4c9 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -122,7 +122,6 @@ namespace osu.Game.Overlays private void onSearchFinished(BeatmapListingFilterControl.SearchResult searchResult) { - // non-supporter user used supporter-only filters if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilters) { supporterRequiredContent.UpdateText(searchResult.SupporterOnlyFiltersUsed); @@ -185,7 +184,7 @@ namespace osu.Game.Overlays if (lastContent == notFoundContent || lastContent == supporterRequiredContent) { - // the placeholder may be used multiple times, so don't expire/dispose it. + // the placeholders may be used multiple times, so don't expire/dispose them. transform.Schedule(() => panelTarget.Remove(lastContent)); } else @@ -253,7 +252,8 @@ namespace osu.Game.Overlays } } - // using string literals as there's no proper processing for LocalizeStrings yet + // TODO: localisation requires Text/LinkFlowContainer support for localising strings with links inside + // (https://github.com/ppy/osu-framework/issues/4530) public class SupporterRequiredDrawable : CompositeDrawable { private LinkFlowContainer supporterRequiredText; From 51147405c59e7c01fd035efa55f8ac68fadd755d Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 20:44:39 +0200 Subject: [PATCH 223/457] Make || and && priority explicit --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index 07ca470e62..f9897ea20c 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Edit.Checks // Only generating issues on hitsounded or last objects ensures we get one issue per long period. // If there are no hitsounds we let the "No hitsounds" template take precedence. - if (hasHitsound(hitObject) || isLastObject && hasHitsounds) + if (hasHitsound(hitObject) || (isLastObject && hasHitsounds)) { var timeWithoutHitsounds = time - lastHitsoundTime; From f78cc9397eff96d67f4b198ffd758934ba8b09c3 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 20:45:31 +0200 Subject: [PATCH 224/457] Factor out edge type logic --- .../Rulesets/Edit/Checks/CheckMutedObjects.cs | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index cbe7c7fbab..23d89a2f44 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -22,6 +22,14 @@ namespace osu.Game.Rulesets.Edit.Checks /// private const int low_volume_threshold = 20; + private enum EdgeType + { + Head, + Repeat, + Tail, + None + } + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Low volume hitobjects"); public IEnumerable PossibleTemplates => new IssueTemplate[] @@ -58,37 +66,43 @@ namespace osu.Game.Rulesets.Edit.Checks int maxVolume = sampledHitObject.Samples.Max(sample => sample.Volume > 0 ? sample.Volume : sampledHitObject.SampleControlPoint.SampleVolume); double samplePlayTime = sampledHitObject.GetEndTime(); - bool head = Precision.AlmostEquals(samplePlayTime, hitObject.StartTime, 1f); - bool tail = Precision.AlmostEquals(samplePlayTime, hitObject.GetEndTime(), 1f); - bool repeat = false; - - if (hitObject is IHasRepeats hasRepeats && !head && !tail) - { - double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); - repeat = Precision.AlmostEquals((samplePlayTime - hitObject.StartTime) % spanDuration, 0f, 1f); - } - + EdgeType edgeType = getEdgeAtTime(hitObject, samplePlayTime); // We only care about samples played on the edges of objects, not ones like spinnerspin or slidertick. - if (!head && !tail && !repeat) + if (edgeType == EdgeType.None) yield break; - string postfix = null; - if (hitObject is IHasDuration) - postfix = head ? "head" : tail ? "tail" : "repeat"; + string postfix = hitObject is IHasDuration ? edgeType.ToString().ToLower() : null; if (maxVolume <= muted_threshold) { - if (head) + if (edgeType == EdgeType.Head) yield return new IssueTemplateMutedActive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); else yield return new IssueTemplateMutedPassive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); } - else if (maxVolume <= low_volume_threshold && head) + else if (maxVolume <= low_volume_threshold && edgeType == EdgeType.Head) { yield return new IssueTemplateLowVolumeActive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); } } + private EdgeType getEdgeAtTime(HitObject hitObject, double time) + { + if (Precision.AlmostEquals(time, hitObject.StartTime, 1f)) + return EdgeType.Head; + if (Precision.AlmostEquals(time, hitObject.GetEndTime(), 1f)) + return EdgeType.Tail; + + if (hitObject is IHasRepeats hasRepeats) + { + double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); + if (Precision.AlmostEquals((time - hitObject.StartTime) % spanDuration, 0f, 1f)) + return EdgeType.Repeat; + } + + return EdgeType.None; + } + public abstract class IssueTemplateMuted : IssueTemplate { protected IssueTemplateMuted(ICheck check, IssueType type, string unformattedMessage) From 5642d321b70f55550b4bc992c0ccf992514cf5b9 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 23:43:08 +0200 Subject: [PATCH 225/457] Fix comments in few hitsounds check tests --- osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index 8edc6d096e..7561e94d2e 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Editing.Checks hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } - // Should prompt one warning between 1st and 11th, and another between 11th and 20th. + // Should prompt one warning between 1st and 16th, and another between 16th and 31st. assertLongPeriodWarning(hitObjects, count: 2); } @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Editing.Checks hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } - // Should prompt one problem between 1st and 40th, and another between 40th and 80th. + // Should prompt one problem between 1st and 41st, and another between 41st and 81st. assertLongPeriodProblem(hitObjects, count: 2); } @@ -140,7 +140,6 @@ namespace osu.Game.Tests.Editing.Checks hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } - // Should prompt one problem between 1st and 40th, and another between 40th and 80th. assertNoHitsounds(hitObjects); } From 191308434215634a96f88dc3501bacee9a2da354 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 23:48:28 +0200 Subject: [PATCH 226/457] Use `HitSampleInfo.AllAdditions` instead of new list --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index f9897ea20c..acdac83141 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -32,8 +32,6 @@ namespace osu.Game.Rulesets.Edit.Checks private const int warning_threshold_objects = 4; private const int problem_threshold_objects = 16; - private static readonly string[] hitsound_types = { HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE, HitSampleInfo.HIT_FINISH }; - public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Few or no hitsounds"); public IEnumerable PossibleTemplates => new IssueTemplate[] @@ -111,7 +109,7 @@ namespace osu.Game.Rulesets.Edit.Checks private bool hasHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitsound); private bool couldHaveHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitnormal); - private bool isHitsound(HitSampleInfo sample) => hitsound_types.Any(sample.Name.Contains); + private bool isHitsound(HitSampleInfo sample) => HitSampleInfo.AllAdditions.Any(sample.Name.Contains); private bool isHitnormal(HitSampleInfo sample) => sample.Name.Contains(HitSampleInfo.HIT_NORMAL); public abstract class IssueTemplateLongPeriod : IssueTemplate From d29e6f46953624bbfac6dbea4f5c0929a0071ab6 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 23:49:06 +0200 Subject: [PATCH 227/457] Add negligible template to `PossibleTemplates` --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index acdac83141..1de3652a39 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Edit.Checks { new IssueTemplateLongPeriodProblem(this), new IssueTemplateLongPeriodWarning(this), + new IssueTemplateLongPeriodNegligible(this), new IssueTemplateNoHitsounds(this) }; From 5bc08ebadb95ff4c215a9f7214dfde7d66a11031 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 23:49:25 +0200 Subject: [PATCH 228/457] Rename `hasHitsounds` -> `mapHasHitsounds` --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index 1de3652a39..8d8243938a 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateNoHitsounds(this) }; - private bool hasHitsounds; + private bool mapHasHitsounds; private int objectsWithoutHitsounds; private double lastHitsoundTime; @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (!context.Beatmap.HitObjects.Any()) yield break; - hasHitsounds = false; + mapHasHitsounds = false; objectsWithoutHitsounds = 0; lastHitsoundTime = context.Beatmap.HitObjects.First().StartTime; @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Edit.Checks yield return issue; } - if (!hasHitsounds) + if (!mapHasHitsounds) yield return new IssueTemplateNoHitsounds(this).Create(); } @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Edit.Checks // Only generating issues on hitsounded or last objects ensures we get one issue per long period. // If there are no hitsounds we let the "No hitsounds" template take precedence. - if (hasHitsound(hitObject) || (isLastObject && hasHitsounds)) + if (hasHitsound(hitObject) || (isLastObject && mapHasHitsounds)) { var timeWithoutHitsounds = time - lastHitsoundTime; @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (hasHitsound(hitObject)) { - hasHitsounds = true; + mapHasHitsounds = true; objectsWithoutHitsounds = 0; lastHitsoundTime = time; } From 4796b1b2089f6e5c64449bbb01e81182231491fd Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 00:04:30 +0200 Subject: [PATCH 229/457] Use local variables for `hasHitsound` & `couldHaveHitsound` --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index 8d8243938a..1fca6b26a4 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -82,10 +82,12 @@ namespace osu.Game.Rulesets.Edit.Checks private IEnumerable applyHitsoundUpdate(HitObject hitObject, bool isLastObject = false) { var time = hitObject.GetEndTime(); + bool hasHitsound = hitObject.Samples.Any(isHitsound); + bool couldHaveHitsound = hitObject.Samples.Any(isHitnormal); // Only generating issues on hitsounded or last objects ensures we get one issue per long period. // If there are no hitsounds we let the "No hitsounds" template take precedence. - if (hasHitsound(hitObject) || (isLastObject && mapHasHitsounds)) + if (hasHitsound || (isLastObject && mapHasHitsounds)) { var timeWithoutHitsounds = time - lastHitsoundTime; @@ -97,19 +99,16 @@ namespace osu.Game.Rulesets.Edit.Checks yield return new IssueTemplateLongPeriodNegligible(this).Create(lastHitsoundTime, timeWithoutHitsounds); } - if (hasHitsound(hitObject)) + if (hasHitsound) { mapHasHitsounds = true; objectsWithoutHitsounds = 0; lastHitsoundTime = time; } - else if (couldHaveHitsound(hitObject)) + else if (couldHaveHitsound) ++objectsWithoutHitsounds; } - private bool hasHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitsound); - private bool couldHaveHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitnormal); - private bool isHitsound(HitSampleInfo sample) => HitSampleInfo.AllAdditions.Any(sample.Name.Contains); private bool isHitnormal(HitSampleInfo sample) => sample.Name.Contains(HitSampleInfo.HIT_NORMAL); From 0c0fd291d9381d718668e0588d6fb5dee60e91c7 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 01:25:03 +0200 Subject: [PATCH 230/457] Order hitobjects by endtime --- .../Rulesets/Edit/Checks/CheckFewHitsounds.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index 1fca6b26a4..5185ba6c99 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -55,18 +55,23 @@ namespace osu.Game.Rulesets.Edit.Checks objectsWithoutHitsounds = 0; lastHitsoundTime = context.Beatmap.HitObjects.First().StartTime; - var hitObjectCount = context.Beatmap.HitObjects.Count; + var hitObjectsIncludingNested = new List(); + + foreach (var hitObject in context.Beatmap.HitObjects) + { + // Samples play on the end of objects. Some objects have nested objects to accomplish playing them elsewhere (e.g. slider head/repeat). + foreach (var nestedHitObject in hitObject.NestedHitObjects) + hitObjectsIncludingNested.Add(nestedHitObject); + + hitObjectsIncludingNested.Add(hitObject); + } + + var hitObjectsByEndTime = hitObjectsIncludingNested.OrderBy(o => o.GetEndTime()).ToList(); + var hitObjectCount = hitObjectsByEndTime.Count; for (int i = 0; i < hitObjectCount; ++i) { - var hitObject = context.Beatmap.HitObjects[i]; - - // Samples play on the end of objects. Some objects have nested objects to accomplish playing them elsewhere (e.g. slider head/repeat). - foreach (var nestedHitObject in hitObject.NestedHitObjects) - { - foreach (var issue in applyHitsoundUpdate(nestedHitObject)) - yield return issue; - } + var hitObject = hitObjectsByEndTime[i]; // This is used to perform an update at the end so that the period after the last hitsounded object can be an issue. bool isLastObject = i == hitObjectCount - 1; From 2cd7eda3c4d62e7b7898c7a60de3d78ea5447b5f Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 02:30:12 +0200 Subject: [PATCH 231/457] Add "or equal to" to volume threshold xmldocs --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index 23d89a2f44..2ac1efc636 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -13,12 +13,12 @@ namespace osu.Game.Rulesets.Edit.Checks public class CheckMutedObjects : ICheck { /// - /// Volume percentages lower than this are typically inaudible. + /// Volume percentages lower than or equal to this are typically inaudible. /// private const int muted_threshold = 5; /// - /// Volume percentages lower than this can sometimes be inaudible depending on sample used and music volume. + /// Volume percentages lower than or equal to this can sometimes be inaudible depending on sample used and music volume. /// private const int low_volume_threshold = 20; From 4cfa0ae5ec79bf2e1922132f23515baa374f2b4e Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 03:26:35 +0200 Subject: [PATCH 232/457] Improve precision for repeat edges --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index 2ac1efc636..c743b5693e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.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.Collections.Generic; using System.Linq; using osu.Framework.Utils; @@ -96,7 +97,9 @@ namespace osu.Game.Rulesets.Edit.Checks if (hitObject is IHasRepeats hasRepeats) { double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); - if (Precision.AlmostEquals((time - hitObject.StartTime) % spanDuration, 0f, 1f)) + double spans = (time - hitObject.StartTime) / spanDuration; + + if (Precision.AlmostEquals(spans, Math.Ceiling(spans)) || Precision.AlmostEquals(spans, Math.Floor(spans))) return EdgeType.Repeat; } From d1f852d102489ae8ae69069dc5ae1416ea0927da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 13:06:20 +0900 Subject: [PATCH 233/457] Make `Populate` abstract to avoid unnecessary base call async complexity --- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 4 ++++ osu.Game/Skinning/SkinManager.cs | 6 +++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 8efd451857..c1a4a6e18a 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -727,7 +727,7 @@ namespace osu.Game.Database /// The model to populate. /// The archive to use as a reference for population. May be null. /// An optional cancellation token. - protected virtual Task Populate(TModel model, [CanBeNull] ArchiveReader archive, CancellationToken cancellationToken = default) => Task.CompletedTask; + protected abstract Task Populate(TModel model, [CanBeNull] ArchiveReader archive, CancellationToken cancellationToken = default); /// /// Perform any final actions before the import to database executes. diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 9d3b952ada..d5bea0affc 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Linq.Expressions; using System.Threading; +using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using osu.Framework.Bindables; @@ -72,6 +73,9 @@ namespace osu.Game.Scoring } } + protected override Task Populate(ScoreInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) + => Task.CompletedTask; + protected override void ExportModelTo(ScoreInfo model, Stream outputStream) { var file = model.Files.SingleOrDefault(); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 4cde4cd2b8..645c943d09 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -142,16 +142,16 @@ namespace osu.Game.Skinning return base.ComputeHash(item, reader); } - protected override async Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) + protected override Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) { - await base.Populate(model, archive, cancellationToken).ConfigureAwait(false); - var instance = GetSkin(model); model.InstantiationInfo ??= instance.GetType().GetInvariantInstantiationInfo(); if (model.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true) populateMetadata(model, instance); + + return Task.CompletedTask; } private void populateMetadata(SkinInfo item, Skin instance) From 46f8100f4371b64e2bda9b484493597b4a868bf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 13:06:47 +0900 Subject: [PATCH 234/457] Remove overly verbose logging during beatmap imports --- osu.Game/Beatmaps/BeatmapManager.cs | 2 -- osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs | 1 - 2 files changed, 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 00af06703d..86c8fb611f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -191,8 +191,6 @@ namespace osu.Game.Beatmaps { var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList(); - LogForModel(beatmapSet, $"Validating online IDs for {beatmapSet.Beatmaps.Count} beatmaps..."); - // ensure all IDs are unique if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1)) { diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index 5dff4fe282..7824205257 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -48,7 +48,6 @@ namespace osu.Game.Beatmaps public Task UpdateAsync(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken) { - LogForModel(beatmapSet, "Performing online lookups..."); return Task.WhenAll(beatmapSet.Beatmaps.Select(b => UpdateAsync(beatmapSet, b, cancellationToken)).ToArray()); } From 15af28d2a0af88ac7f167341d1d438121ccf3a62 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 14:48:51 +0900 Subject: [PATCH 235/457] Remove comparison of online beatmap IDs during dedupe checks --- .../Beatmaps/IO/ImportBeatmapTest.cs | 35 ------------------- osu.Game/Beatmaps/BeatmapManager.cs | 12 ------- osu.Game/Database/ArchiveModelManager.cs | 4 +-- 3 files changed, 2 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 0d117f8755..2087bdf144 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -439,41 +439,6 @@ namespace osu.Game.Tests.Beatmaps.IO } } - [TestCase(true)] - [TestCase(false)] - public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) - { - // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-{set}")) - { - try - { - var osu = LoadOsuIntoHost(host); - - var imported = await LoadOszIntoOsu(osu); - - if (set) - imported.OnlineBeatmapSetID = 1234; - else - imported.Beatmaps.First().OnlineBeatmapID = 1234; - - osu.Dependencies.Get().Update(imported); - - deleteBeatmapSet(imported, osu); - - var importedSecondTime = await LoadOszIntoOsu(osu); - - // check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched) - Assert.IsTrue(imported.ID != importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); - } - finally - { - host.Exit(); - } - } - } - [Test] public async Task TestImportWithDuplicateBeatmapIDs() { diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 00af06703d..221774b018 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -319,18 +319,6 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); - protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import) - { - if (!base.CanReuseExisting(existing, import)) - return false; - - var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); - var importIds = import.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); - - // force re-import if we are not in a sane state. - return existing.OnlineBeatmapSetID == import.OnlineBeatmapSetID && existingIds.SequenceEqual(importIds); - } - /// /// Returns a list of all usable s. /// diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 8efd451857..29c83b4699 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -377,7 +377,7 @@ namespace osu.Game.Database if (existing != null) { - if (CanReuseExisting(existing, item)) + if (canReuseExisting(existing, item)) { Undelete(existing); LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); @@ -751,7 +751,7 @@ namespace osu.Game.Database /// The existing model. /// The newly imported model. /// Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import. - protected virtual bool CanReuseExisting(TModel existing, TModel import) => + private bool canReuseExisting(TModel existing, TModel import) => // for the best or worst, we copy and import files of a new import before checking whether // it is a duplicate. so to check if anything has changed, we can just compare all FileInfo IDs. getIDs(existing.Files).SequenceEqual(getIDs(import.Files)) && From e493685c14acd80e430092e62700bcc75e59f618 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 16:34:40 +0900 Subject: [PATCH 236/457] Add optimised existing check earlier in import process --- osu.Game/Database/ArchiveModelManager.cs | 61 +++++++++++++++++++----- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 29c83b4699..80faa64953 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -317,7 +317,11 @@ namespace osu.Game.Database /// protected virtual string ComputeHash(TModel item, ArchiveReader reader = null) { - // for now, concatenate all .osu files in the set to create a unique hash. + if (reader != null) + // fast hashing for cases where the item's files may not be populated. + return computeHashFast(reader); + + // for now, concatenate all hashable files in the set to create a unique hash. MemoryStream hashable = new MemoryStream(); foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(ext => f.Filename.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f.Filename)) @@ -329,12 +333,25 @@ namespace osu.Game.Database if (hashable.Length > 0) return hashable.ComputeSHA2Hash(); - if (reader != null) - return reader.Name.ComputeSHA2Hash(); - return item.Hash; } + private string computeHashFast(ArchiveReader reader) + { + MemoryStream hashable = new MemoryStream(); + + foreach (var file in reader.Filenames.Where(f => HashableFileTypes.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f)) + { + using (Stream s = reader.GetStream(file)) + s.CopyTo(hashable); + } + + if (hashable.Length > 0) + return hashable.ComputeSHA2Hash(); + + return reader.Name.ComputeSHA2Hash(); + } + /// /// Silently import an item from a . /// @@ -348,6 +365,21 @@ namespace osu.Game.Database delayEvents(); + if (archive != null) + { + // fast bail to improve large import performance. + item.Hash = computeHashFast(archive); + + var fastExisting = CheckForExisting(item); + + if (fastExisting != null) + { + // bare minimum comparisons + if (getFilenames(fastExisting.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened))) + return fastExisting; + } + } + void rollback() { if (!Delete(item)) @@ -644,18 +676,14 @@ namespace osu.Game.Database { var fileInfos = new List(); - string prefix = reader.Filenames.GetCommonPrefix(); - if (!(prefix.EndsWith('/') || prefix.EndsWith('\\'))) - prefix = string.Empty; - // import files to manager - foreach (string file in reader.Filenames) + foreach (var filenames in getShortenedFilenames(reader)) { - using (Stream s = reader.GetStream(file)) + using (Stream s = reader.GetStream(filenames.original)) { fileInfos.Add(new TFileModel { - Filename = file.Substring(prefix.Length).ToStandardisedPath(), + Filename = filenames.shortened, FileInfo = files.Add(s) }); } @@ -664,6 +692,17 @@ namespace osu.Game.Database return fileInfos; } + private IEnumerable<(string original, string shortened)> getShortenedFilenames(ArchiveReader reader) + { + string prefix = reader.Filenames.GetCommonPrefix(); + if (!(prefix.EndsWith('/') || prefix.EndsWith('\\'))) + prefix = string.Empty; + + // import files to manager + foreach (string file in reader.Filenames) + yield return (file, file.Substring(prefix.Length).ToStandardisedPath()); + } + #region osu-stable import /// From 44f875b802f8d958d58cb34b065474fcc954b5ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 16:35:13 +0900 Subject: [PATCH 237/457] Bypass optimised existing check in `SkinManager` (due to custom hashing function) --- osu.Game/Database/ArchiveModelManager.cs | 8 +++++++- osu.Game/Skinning/SkinManager.cs | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 80faa64953..22e5486909 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -309,6 +309,12 @@ namespace osu.Game.Database Logger.Log($"{prefix} {message}", LoggingTarget.Database); } + /// + /// Whether the implementation overrides with a custom implementation. + /// Custom has implementations must bypass the early exit in the import flow (see usage). + /// + protected virtual bool HasCustomHashFunction => false; + /// /// Create a SHA-2 hash from the provided archive based on file content of all files matching . /// @@ -365,7 +371,7 @@ namespace osu.Game.Database delayEvents(); - if (archive != null) + if (archive != null && !HasCustomHashFunction) { // fast bail to improve large import performance. item.Hash = computeHashFast(archive); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 4cde4cd2b8..43cf6b6874 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -125,6 +125,8 @@ namespace osu.Game.Skinning private const string unknown_creator_string = "Unknown"; + protected override bool HasCustomHashFunction => true; + protected override string ComputeHash(SkinInfo item, ArchiveReader reader = null) { // we need to populate early to create a hash based off skin.ini contents From 9120321731baac949045283dd0057ab1e8a723bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 16:48:42 +0900 Subject: [PATCH 238/457] Add comments mentioning shortcomings and avoid potential double check --- osu.Game/Database/ArchiveModelManager.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 22e5486909..ed5b54f446 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -371,18 +371,25 @@ namespace osu.Game.Database delayEvents(); + bool checkedExisting = false; + TModel existing = null; + if (archive != null && !HasCustomHashFunction) { - // fast bail to improve large import performance. + // this is a fast bail condition to improve large import performance. item.Hash = computeHashFast(archive); - var fastExisting = CheckForExisting(item); + checkedExisting = true; + existing = CheckForExisting(item); - if (fastExisting != null) + if (existing != null) { // bare minimum comparisons - if (getFilenames(fastExisting.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened))) - return fastExisting; + // + // note that this should really be checking filesizes on disk (of existing files) for some degree of sanity. + // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files. + if (getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) + return existing; } } @@ -411,7 +418,8 @@ namespace osu.Game.Database { if (!write.IsTransactionLeader) throw new InvalidOperationException($"Ensure there is no parent transaction so errors can correctly be handled by {this}"); - var existing = CheckForExisting(item); + if (!checkedExisting) + existing = CheckForExisting(item); if (existing != null) { From f2164049526e8c94e40ad4db5ec96edecdc257d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 20:22:48 +0900 Subject: [PATCH 239/457] Fix missing undelete call on using existing --- osu.Game/Database/ArchiveModelManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index ed5b54f446..7ea6fe067c 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -389,7 +389,10 @@ namespace osu.Game.Database // note that this should really be checking filesizes on disk (of existing files) for some degree of sanity. // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files. if (getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) + { + Undelete(existing); return existing; + } } } From cd9aa38d3debcc69730ef7e89f5330d463b2c634 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 20:24:16 +0900 Subject: [PATCH 240/457] Add back ignore cases for intentionally broken tests --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 2087bdf144..79c85f6c61 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -192,6 +192,7 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] + [Ignore("intentionally broken by import optimisations")] public async Task TestImportThenImportWithChangedFile() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) @@ -294,6 +295,7 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] + [Ignore("intentionally broken by import optimisations")] public async Task TestImportCorruptThenImport() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. From f470b7095d9e6aafa201a9abf1ba098093ede571 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 20:36:01 +0900 Subject: [PATCH 241/457] Move private method down in class --- osu.Game/Database/ArchiveModelManager.cs | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 7ea6fe067c..9b89037334 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -342,22 +342,6 @@ namespace osu.Game.Database return item.Hash; } - private string computeHashFast(ArchiveReader reader) - { - MemoryStream hashable = new MemoryStream(); - - foreach (var file in reader.Filenames.Where(f => HashableFileTypes.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f)) - { - using (Stream s = reader.GetStream(file)) - s.CopyTo(hashable); - } - - if (hashable.Length > 0) - return hashable.ComputeSHA2Hash(); - - return reader.Name.ComputeSHA2Hash(); - } - /// /// Silently import an item from a . /// @@ -686,6 +670,22 @@ namespace osu.Game.Database } } + private string computeHashFast(ArchiveReader reader) + { + MemoryStream hashable = new MemoryStream(); + + foreach (var file in reader.Filenames.Where(f => HashableFileTypes.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f)) + { + using (Stream s = reader.GetStream(file)) + s.CopyTo(hashable); + } + + if (hashable.Length > 0) + return hashable.ComputeSHA2Hash(); + + return reader.Name.ComputeSHA2Hash(); + } + /// /// Create all required s for the provided archive, adding them to the global file store. /// From e755dcc34dffbaa8b6415fa9adf5fe61580cf940 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 20:37:12 +0900 Subject: [PATCH 242/457] Add log method for new flow --- osu.Game/Database/ArchiveModelManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 9b89037334..e1202ed95f 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -374,6 +374,7 @@ namespace osu.Game.Database // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files. if (getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) { + LogForModel(item, $"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); Undelete(existing); return existing; } From c2ceb83bbb20c53edff6c4973fb45e48a984cad2 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:16:40 +0200 Subject: [PATCH 243/457] Move `MockNestedHitObject` to own class --- .../Editing/Checks/CheckMutedObjectsTest.cs | 28 --------------- .../Editing/Checks/MockNestableHitObject.cs | 36 +++++++++++++++++++ 2 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs index 4bfe62a64d..41a8f72305 100644 --- a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using System.Threading; using NUnit.Framework; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -11,7 +10,6 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; @@ -27,32 +25,6 @@ namespace osu.Game.Tests.Editing.Checks private const int volume_low = 15; private const int volume_muted = 5; - private sealed class MockNestableHitObject : HitObject, IHasDuration - { - private readonly IEnumerable toBeNested; - - public MockNestableHitObject(IEnumerable toBeNested, double startTime, double endTime) - { - this.toBeNested = toBeNested; - StartTime = startTime; - EndTime = endTime; - } - - protected override void CreateNestedHitObjects(CancellationToken cancellationToken) - { - foreach (var hitObject in toBeNested) - AddNested(hitObject); - } - - public double EndTime { get; } - - public double Duration - { - get => EndTime - StartTime; - set => throw new System.NotImplementedException(); - } - } - [SetUp] public void Setup() { diff --git a/osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs b/osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs new file mode 100644 index 0000000000..29938839d3 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs @@ -0,0 +1,36 @@ +// 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.Threading; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Tests.Editing.Checks +{ + public sealed class MockNestableHitObject : HitObject, IHasDuration + { + private readonly IEnumerable toBeNested; + + public MockNestableHitObject(IEnumerable toBeNested, double startTime, double endTime) + { + this.toBeNested = toBeNested; + StartTime = startTime; + EndTime = endTime; + } + + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) + { + foreach (var hitObject in toBeNested) + AddNested(hitObject); + } + + public double EndTime { get; } + + public double Duration + { + get => EndTime - StartTime; + set => throw new System.NotImplementedException(); + } + } +} From 1d5bff166043e0b7b41c98603c65af3e411ec115 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:26:52 +0200 Subject: [PATCH 244/457] Add concurrent hitobjects test for few hitsounds check See https://github.com/ppy/osu/pull/13669#discussion_r659314980 --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index 7561e94d2e..d681c3fd3d 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; @@ -143,6 +144,35 @@ namespace osu.Game.Tests.Editing.Checks assertNoHitsounds(hitObjects); } + [Test] + public void TestConcurrentObjects() + { + var hitObjects = new List(); + + var notHitsounded = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + var hitsounded = new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL), + new HitSampleInfo(HitSampleInfo.HIT_FINISH) + }; + + var ticks = new List(); + for (int i = 1; i < 10; ++i) + ticks.Add(new SliderTick { StartTime = 5000 * i, Samples = hitsounded }); + + var nested = new MockNestableHitObject(ticks.ToList(), 0, 50000) + { + Samples = notHitsounded + }; + nested.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + hitObjects.Add(nested); + + for (int i = 1; i <= 6; ++i) + hitObjects.Add(new HitCircle { StartTime = 10000 * i, Samples = notHitsounded }); + + assertOk(hitObjects); + } + private void assertOk(List hitObjects) { Assert.That(check.Run(getContext(hitObjects)), Is.Empty); From a4a5325b73e8e6108137aacd40dbdf93c074f980 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:39:31 +0200 Subject: [PATCH 245/457] Improve acceptable difference for repeat edges Likelihood that `spanDuration` is greater than E+7 is quite low in any realistic case, so this should work fine. --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index c743b5693e..0559a8b0cd 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -98,9 +98,13 @@ namespace osu.Game.Rulesets.Edit.Checks { double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); double spans = (time - hitObject.StartTime) / spanDuration; + double acceptableDifference = 1 / spanDuration; // 1 ms of acceptable difference, as with head/tail above. - if (Precision.AlmostEquals(spans, Math.Ceiling(spans)) || Precision.AlmostEquals(spans, Math.Floor(spans))) + if (Precision.AlmostEquals(spans, Math.Ceiling(spans), acceptableDifference) || + Precision.AlmostEquals(spans, Math.Floor(spans), acceptableDifference)) + { return EdgeType.Repeat; + } } return EdgeType.None; From 9f9e96ce9ee797024585af8515338f67bc0175d6 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:40:09 +0200 Subject: [PATCH 246/457] Add check for `spanDuration` <= 0 prior to division --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index 0559a8b0cd..a4ff921b7e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -97,6 +97,10 @@ namespace osu.Game.Rulesets.Edit.Checks if (hitObject is IHasRepeats hasRepeats) { double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); + if (spanDuration <= 0) + // Prevents undefined behaviour in cases like where zero/negative-length sliders/hold notes exist. + return EdgeType.None; + double spans = (time - hitObject.StartTime) / spanDuration; double acceptableDifference = 1 / spanDuration; // 1 ms of acceptable difference, as with head/tail above. From 1dbac76da5c4366bab40f1962faa40e0322d0c5b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:57:41 +0200 Subject: [PATCH 247/457] Use local variables for common sample lists --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 37 +++++++------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index d681c3fd3d..21e274c2c0 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -20,10 +20,19 @@ namespace osu.Game.Tests.Editing.Checks { private CheckFewHitsounds check; + private List notHitsounded; + private List hitsounded; + [SetUp] public void Setup() { check = new CheckFewHitsounds(); + notHitsounded = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + hitsounded = new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL), + new HitSampleInfo(HitSampleInfo.HIT_FINISH) + }; } [Test] @@ -80,10 +89,7 @@ namespace osu.Game.Tests.Editing.Checks for (int i = 0; i < 30; ++i) { - var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - - if (i % 8 == 0) - samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + var samples = i % 8 == 0 ? hitsounded : notHitsounded; hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } @@ -98,10 +104,7 @@ namespace osu.Game.Tests.Editing.Checks for (int i = 0; i < 30; ++i) { - var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - - if (i == 0 || i == 15) - samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + var samples = (i == 0 || i == 15) ? hitsounded : notHitsounded; hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } @@ -117,10 +120,7 @@ namespace osu.Game.Tests.Editing.Checks for (int i = 0; i < 80; ++i) { - var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - - if (i == 40) - samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + var samples = i == 40 ? hitsounded : notHitsounded; hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } @@ -135,11 +135,7 @@ namespace osu.Game.Tests.Editing.Checks var hitObjects = new List(); for (int i = 0; i < 20; ++i) - { - var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - - hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); - } + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = notHitsounded }); assertNoHitsounds(hitObjects); } @@ -149,13 +145,6 @@ namespace osu.Game.Tests.Editing.Checks { var hitObjects = new List(); - var notHitsounded = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - var hitsounded = new List - { - new HitSampleInfo(HitSampleInfo.HIT_NORMAL), - new HitSampleInfo(HitSampleInfo.HIT_FINISH) - }; - var ticks = new List(); for (int i = 1; i < 10; ++i) ticks.Add(new SliderTick { StartTime = 5000 * i, Samples = hitsounded }); From b58644106c188d09802dd8d1e455633193651f5d Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:58:00 +0200 Subject: [PATCH 248/457] Add nested hitobject tests for few hitsounds check --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index 21e274c2c0..cf5b3a42a4 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -140,6 +140,38 @@ namespace osu.Game.Tests.Editing.Checks assertNoHitsounds(hitObjects); } + [Test] + public void TestNestedObjectsHitsounded() + { + var ticks = new List(); + for (int i = 1; i < 16; ++i) + ticks.Add(new SliderTick { StartTime = 1000 * i, Samples = hitsounded }); + + var nested = new MockNestableHitObject(ticks.ToList(), 0, 16000) + { + Samples = hitsounded + }; + nested.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertOk(new List { nested }); + } + + [Test] + public void TestNestedObjectsRarelyHitsounded() + { + var ticks = new List(); + for (int i = 1; i < 16; ++i) + ticks.Add(new SliderTick { StartTime = 1000 * i, Samples = i == 0 ? hitsounded : notHitsounded }); + + var nested = new MockNestableHitObject(ticks.ToList(), 0, 16000) + { + Samples = hitsounded + }; + nested.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertLongPeriodWarning(new List { nested }); + } + [Test] public void TestConcurrentObjects() { From 9a96cd4a1df625322e644fc9b708676fbe62cd04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 09:54:18 +0900 Subject: [PATCH 249/457] Revert "Remove comparison of online beatmap IDs during dedupe checks" This reverts commit 15af28d2a0af88ac7f167341d1d438121ccf3a62. --- .../Beatmaps/IO/ImportBeatmapTest.cs | 35 +++++++++++++++++++ osu.Game/Beatmaps/BeatmapManager.cs | 12 +++++++ osu.Game/Database/ArchiveModelManager.cs | 4 +-- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 79c85f6c61..990e75df81 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -441,6 +441,41 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [TestCase(true)] + [TestCase(false)] + public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) + { + // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-{set}")) + { + try + { + var osu = LoadOsuIntoHost(host); + + var imported = await LoadOszIntoOsu(osu); + + if (set) + imported.OnlineBeatmapSetID = 1234; + else + imported.Beatmaps.First().OnlineBeatmapID = 1234; + + osu.Dependencies.Get().Update(imported); + + deleteBeatmapSet(imported, osu); + + var importedSecondTime = await LoadOszIntoOsu(osu); + + // check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched) + Assert.IsTrue(imported.ID != importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + } + finally + { + host.Exit(); + } + } + } + [Test] public async Task TestImportWithDuplicateBeatmapIDs() { diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 221774b018..00af06703d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -319,6 +319,18 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); + protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import) + { + if (!base.CanReuseExisting(existing, import)) + return false; + + var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); + var importIds = import.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); + + // force re-import if we are not in a sane state. + return existing.OnlineBeatmapSetID == import.OnlineBeatmapSetID && existingIds.SequenceEqual(importIds); + } + /// /// Returns a list of all usable s. /// diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index e1202ed95f..84473f57fe 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -411,7 +411,7 @@ namespace osu.Game.Database if (existing != null) { - if (canReuseExisting(existing, item)) + if (CanReuseExisting(existing, item)) { Undelete(existing); LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); @@ -808,7 +808,7 @@ namespace osu.Game.Database /// The existing model. /// The newly imported model. /// Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import. - private bool canReuseExisting(TModel existing, TModel import) => + protected virtual bool CanReuseExisting(TModel existing, TModel import) => // for the best or worst, we copy and import files of a new import before checking whether // it is a duplicate. so to check if anything has changed, we can just compare all FileInfo IDs. getIDs(existing.Files).SequenceEqual(getIDs(import.Files)) && From 90b87cbb9eaba15ade0585a881ded8978192d871 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 10:11:27 +0900 Subject: [PATCH 250/457] Add back unidirectional online id check --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 10 +++------- osu.Game/Beatmaps/BeatmapManager.cs | 8 ++++++++ osu.Game/Database/ArchiveModelManager.cs | 12 +++++++++++- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 990e75df81..e87b82cd2a 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -441,9 +441,7 @@ namespace osu.Game.Tests.Beatmaps.IO } } - [TestCase(true)] - [TestCase(false)] - public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) + public async Task TestImportThenDeleteThenImportWithOnlineIDsMissing() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-{set}")) @@ -454,10 +452,8 @@ namespace osu.Game.Tests.Beatmaps.IO var imported = await LoadOszIntoOsu(osu); - if (set) - imported.OnlineBeatmapSetID = 1234; - else - imported.Beatmaps.First().OnlineBeatmapID = 1234; + foreach (var b in imported.Beatmaps) + b.OnlineBeatmapID = null; osu.Dependencies.Get().Update(imported); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 00af06703d..f854a5fecb 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -319,6 +319,14 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); + protected override bool CanSkipImport(BeatmapSetInfo existing, BeatmapSetInfo import) + { + if (!base.CanReuseExisting(existing, import)) + return false; + + return existing.Beatmaps.Any(b => b.OnlineBeatmapID != null); + } + protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import) { if (!base.CanReuseExisting(existing, import)) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 84473f57fe..6d8b671fd8 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -372,7 +372,8 @@ namespace osu.Game.Database // // note that this should really be checking filesizes on disk (of existing files) for some degree of sanity. // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files. - if (getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) + if (CanSkipImport(existing, item) && + getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) { LogForModel(item, $"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); Undelete(existing); @@ -801,6 +802,15 @@ namespace osu.Game.Database /// An existing model which matches the criteria to skip importing, else null. protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); + /// + /// Whether inport can be skipped after finding an existing import early in the process. + /// Only valid when is not overridden. + /// + /// The existing model. + /// The newly imported model. + /// Whether to skip this import completely. + protected virtual bool CanSkipImport(TModel existing, TModel import) => true; + /// /// After an existing is found during an import process, the default behaviour is to use/restore the existing /// item and skip the import. This method allows changing that behaviour. From 128f08ccbadaf5a2b027f88b651c8ace8a7696f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 10:42:28 +0900 Subject: [PATCH 251/457] Fix test oversights --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index e87b82cd2a..02b1d8bcfc 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -441,10 +441,11 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [Test] public async Task TestImportThenDeleteThenImportWithOnlineIDsMissing() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-{set}")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}")) { try { From 4a557e73a781b4d71f79f797ccfe876c90b9d89c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 10:42:42 +0900 Subject: [PATCH 252/457] Add logging to help understand existing case skips better --- osu.Game/Database/ArchiveModelManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 6d8b671fd8..91ffe2966a 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -379,6 +379,8 @@ namespace osu.Game.Database Undelete(existing); return existing; } + + LogForModel(item, $"Found existing (optimised) but failed pre-check."); } } @@ -422,6 +424,7 @@ namespace osu.Game.Database return existing; } + LogForModel(item, $"Found existing but failed re-use check."); Delete(existing); ModelStore.PurgeDeletable(s => s.ID == existing.ID); } From 0cceef8da50264bbfa0c539e183acd04ee1d6e68 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 28 Jun 2021 11:00:07 +0800 Subject: [PATCH 253/457] Moved the `string` to `int?` conversion logic into `SettingsNumberBox` --- .../Screens/Editors/RoundEditorScreen.cs | 12 ++-- .../Screens/Editors/SeedingEditorScreen.cs | 14 ++-- .../Screens/Editors/TeamEditorScreen.cs | 12 ++-- .../Overlays/Settings/SettingsNumberBox.cs | 59 +++++++++++++-- osu.Game/Rulesets/Mods/ModRandom.cs | 3 +- osu.Game/Rulesets/Mods/SeedSettingsControl.cs | 72 ------------------- 6 files changed, 72 insertions(+), 100 deletions(-) delete mode 100644 osu.Game/Rulesets/Mods/SeedSettingsControl.cs diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs index 069ddfa4db..e91eed420e 100644 --- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -147,7 +147,7 @@ namespace osu.Game.Tournament.Screens.Editors [Resolved] protected IAPIProvider API { get; private set; } - private readonly Bindable beatmapId = new Bindable(); + private readonly Bindable beatmapId = new Bindable(); private readonly Bindable mods = new Bindable(); @@ -220,14 +220,12 @@ namespace osu.Game.Tournament.Screens.Editors [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - beatmapId.Value = Model.ID.ToString(); - beatmapId.BindValueChanged(idString => + beatmapId.Value = Model.ID; + beatmapId.BindValueChanged(idInt => { - int.TryParse(idString.NewValue, out var parsed); + Model.ID = idInt.NewValue ?? 0; - Model.ID = parsed; - - if (idString.NewValue != idString.OldValue) + if (idInt.NewValue != idInt.OldValue) Model.BeatmapInfo = null; if (Model.BeatmapInfo != null) diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs index 7bd8d3f6a0..4b50d74dc8 100644 --- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs @@ -147,7 +147,7 @@ namespace osu.Game.Tournament.Screens.Editors [Resolved] protected IAPIProvider API { get; private set; } - private readonly Bindable beatmapId = new Bindable(); + private readonly Bindable beatmapId = new Bindable(); private readonly Bindable score = new Bindable(); @@ -228,16 +228,12 @@ namespace osu.Game.Tournament.Screens.Editors [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - beatmapId.Value = Model.ID.ToString(); - beatmapId.BindValueChanged(idString => + beatmapId.Value = Model.ID; + beatmapId.BindValueChanged(idInt => { - int parsed; + Model.ID = idInt.NewValue ?? 0; - int.TryParse(idString.NewValue, out parsed); - - Model.ID = parsed; - - if (idString.NewValue != idString.OldValue) + if (idInt.NewValue != idInt.OldValue) Model.BeatmapInfo = null; if (Model.BeatmapInfo != null) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index aa1be143ea..22ec2d3398 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -214,7 +214,7 @@ namespace osu.Game.Tournament.Screens.Editors [Resolved] private TournamentGameBase game { get; set; } - private readonly Bindable userId = new Bindable(); + private readonly Bindable userId = new Bindable(); private readonly Container drawableContainer; @@ -278,14 +278,12 @@ namespace osu.Game.Tournament.Screens.Editors [BackgroundDependencyLoader] private void load() { - userId.Value = user.Id.ToString(); - userId.BindValueChanged(idString => + userId.Value = user.Id; + userId.BindValueChanged(idInt => { - int.TryParse(idString.NewValue, out var parsed); + user.Id = idInt.NewValue ?? 0; - user.Id = parsed; - - if (idString.NewValue != idString.OldValue) + if (idInt.NewValue != idInt.OldValue) user.Username = string.Empty; if (!string.IsNullOrEmpty(user.Username)) diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index d4d1fc8610..bc86f2c6dc 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -1,17 +1,68 @@ // 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { - public class SettingsNumberBox : SettingsItem + public class SettingsNumberBox : SettingsItem { - protected override Drawable CreateControl() => new OutlinedNumberBox + protected override Drawable CreateControl() => new NumberControl { - Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true + Margin = new MarginPadding { Top = 5 } }; + + private sealed class NumberControl : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + private readonly OutlinedNumberBox numberBox; + + public Bindable Current + { + get => current; + set + { + current.Current = value; + numberBox.Text = value.Value.ToString(); + } + } + + public NumberControl() + { + AutoSizeAxes = Axes.Y; + + InternalChildren = new[] + { + numberBox = new OutlinedNumberBox + { + Margin = new MarginPadding { Top = 5 }, + RelativeSizeAxes = Axes.X, + CommitOnFocusLost = true + } + }; + + numberBox.Current.BindValueChanged(e => + { + int? value = null; + + if (int.TryParse(e.NewValue, out var intVal)) + value = intVal; + + current.Value = value; + }); + } + + protected override void Update() + { + base.Update(); + if (Current.Value == null) + numberBox.Current.Value = ""; + } + } } } diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index 61297c162d..1f7742b075 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods { @@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.Dice; public override double ScoreMultiplier => 1; - [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SeedSettingsControl))] + [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))] public Bindable Seed { get; } = new Bindable { Default = null, diff --git a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs deleted file mode 100644 index 1eaf31874b..0000000000 --- a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs +++ /dev/null @@ -1,72 +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.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Overlays.Settings; - -namespace osu.Game.Rulesets.Mods -{ - /// - /// A settings control for use by mods which have a customisable seed value. - /// - public class SeedSettingsControl : SettingsItem - { - protected override Drawable CreateControl() => new SeedControl - { - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 5 } - }; - - private sealed class SeedControl : CompositeDrawable, IHasCurrentValue - { - private readonly BindableWithCurrent current = new BindableWithCurrent(); - - public Bindable Current - { - get => current; - set - { - current.Current = value; - seedNumberBox.Text = value.Value.ToString(); - } - } - - private readonly OutlinedNumberBox seedNumberBox; - - public SeedControl() - { - AutoSizeAxes = Axes.Y; - - InternalChildren = new[] - { - seedNumberBox = new OutlinedNumberBox - { - Margin = new MarginPadding { Top = 5 }, - RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true - } - }; - - seedNumberBox.Current.BindValueChanged(e => - { - int? value = null; - - if (int.TryParse(e.NewValue, out var intVal)) - value = intVal; - - current.Value = value; - }); - } - - protected override void Update() - { - base.Update(); - if (Current.Value == null) - seedNumberBox.Current.Value = ""; - } - } - } -} From 4d6002ab889e449313f2fe6a8d6718377640a9f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 12:13:11 +0900 Subject: [PATCH 254/457] Remove redundant string interpolation (and mark all local logging strings as verbatim) --- osu.Game/Database/ArchiveModelManager.cs | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 91ffe2966a..5446c3b851 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -78,7 +78,7 @@ namespace osu.Game.Database private readonly Bindable> itemRemoved = new Bindable>(); - public virtual IEnumerable HandledExtensions => new[] { ".zip" }; + public virtual IEnumerable HandledExtensions => new[] { @".zip" }; protected readonly FileStore Files; @@ -99,7 +99,7 @@ namespace osu.Game.Database ModelStore.ItemUpdated += item => handleEvent(() => itemUpdated.Value = new WeakReference(item)); ModelStore.ItemRemoved += item => handleEvent(() => itemRemoved.Value = new WeakReference(item)); - exportStorage = storage.GetStorageForDirectory("exports"); + exportStorage = storage.GetStorageForDirectory(@"exports"); Files = new FileStore(contextFactory, storage); @@ -282,7 +282,7 @@ namespace osu.Game.Database } catch (Exception e) { - LogForModel(model, $"Model creation of {archive.Name} failed.", e); + LogForModel(model, @$"Model creation of {archive.Name} failed.", e); return null; } @@ -375,12 +375,12 @@ namespace osu.Game.Database if (CanSkipImport(existing, item) && getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) { - LogForModel(item, $"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); + LogForModel(item, @$"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); Undelete(existing); return existing; } - LogForModel(item, $"Found existing (optimised) but failed pre-check."); + LogForModel(item, @"Found existing (optimised) but failed pre-check."); } } @@ -389,14 +389,14 @@ namespace osu.Game.Database if (!Delete(item)) { // We may have not yet added the model to the underlying table, but should still clean up files. - LogForModel(item, "Dereferencing files for incomplete import."); + LogForModel(item, @"Dereferencing files for incomplete import."); Files.Dereference(item.Files.Select(f => f.FileInfo).ToArray()); } } try { - LogForModel(item, "Beginning import..."); + LogForModel(item, @"Beginning import..."); item.Files = archive != null ? createFileInfos(archive, Files) : new List(); item.Hash = ComputeHash(item, archive); @@ -407,7 +407,7 @@ namespace osu.Game.Database { try { - if (!write.IsTransactionLeader) throw new InvalidOperationException($"Ensure there is no parent transaction so errors can correctly be handled by {this}"); + if (!write.IsTransactionLeader) throw new InvalidOperationException(@$"Ensure there is no parent transaction so errors can correctly be handled by {this}"); if (!checkedExisting) existing = CheckForExisting(item); @@ -417,14 +417,14 @@ namespace osu.Game.Database if (CanReuseExisting(existing, item)) { Undelete(existing); - LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); + LogForModel(item, @$"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); // existing item will be used; rollback new import and exit early. rollback(); flushEvents(true); return existing; } - LogForModel(item, $"Found existing but failed re-use check."); + LogForModel(item, @"Found existing but failed re-use check."); Delete(existing); ModelStore.PurgeDeletable(s => s.ID == existing.ID); } @@ -441,12 +441,12 @@ namespace osu.Game.Database } } - LogForModel(item, "Import successfully completed!"); + LogForModel(item, @"Import successfully completed!"); } catch (Exception e) { if (!(e is TaskCanceledException)) - LogForModel(item, "Database import or population failed and has been rolled back.", e); + LogForModel(item, @"Database import or population failed and has been rolled back.", e); rollback(); flushEvents(false); @@ -466,7 +466,7 @@ namespace osu.Game.Database var retrievedItem = ModelStore.ConsumableItems.FirstOrDefault(s => s.ID == item.ID); if (retrievedItem == null) - throw new ArgumentException("Specified model could not be found", nameof(item)); + throw new ArgumentException(@"Specified model could not be found", nameof(item)); using (var outputStream = exportStorage.GetStream($"{getValidFilename(item.ToString())}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create)) ExportModelTo(retrievedItem, outputStream); @@ -757,7 +757,7 @@ namespace osu.Game.Database { string fullPath = storage.GetFullPath(ImportFromStablePath); - Logger.Log($"Folder \"{fullPath}\" not available in the target osu!stable installation to import {HumanisedModelName}s.", LoggingTarget.Information, LogLevel.Error); + Logger.Log(@$"Folder ""{fullPath}"" not available in the target osu!stable installation to import {HumanisedModelName}s.", LoggingTarget.Information, LogLevel.Error); return Task.CompletedTask; } @@ -841,7 +841,7 @@ namespace osu.Game.Database private DbSet queryModel() => ContextFactory.Get().Set(); - protected virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace("Info", "").ToLower()}"; + protected virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}"; #region Event handling / delaying From 3d19364a7121702bb9b6c61068e82fb7223129af Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 28 Jun 2021 11:20:00 +0800 Subject: [PATCH 255/457] Use `BindValueChanged` instead of setting the value in property setter --- .../Overlays/Settings/SettingsNumberBox.cs | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index bc86f2c6dc..de3e5a6bb0 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -20,22 +20,18 @@ namespace osu.Game.Overlays.Settings { private readonly BindableWithCurrent current = new BindableWithCurrent(); - private readonly OutlinedNumberBox numberBox; - public Bindable Current { - get => current; - set - { - current.Current = value; - numberBox.Text = value.Value.ToString(); - } + get => current.Current; + set => current.Current = value; } public NumberControl() { AutoSizeAxes = Axes.Y; + OutlinedNumberBox numberBox; + InternalChildren = new[] { numberBox = new OutlinedNumberBox @@ -55,13 +51,11 @@ namespace osu.Game.Overlays.Settings current.Value = value; }); - } - protected override void Update() - { - base.Update(); - if (Current.Value == null) - numberBox.Current.Value = ""; + Current.BindValueChanged(e => + { + numberBox.Current.Value = e.NewValue?.ToString(); + }); } } } From a3946a1265fbee6968daf04dbf563b95e06330b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 14:07:21 +0900 Subject: [PATCH 256/457] Fix typo in newly added xmldoc Co-authored-by: Salman Ahmed --- osu.Game/Database/ArchiveModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 5446c3b851..3798c0c6ae 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -311,7 +311,7 @@ namespace osu.Game.Database /// /// Whether the implementation overrides with a custom implementation. - /// Custom has implementations must bypass the early exit in the import flow (see usage). + /// Custom hash implementations must bypass the early exit in the import flow (see usage). /// protected virtual bool HasCustomHashFunction => false; From e387feb1d6261b491246e014de199e921a290103 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 14:39:55 +0900 Subject: [PATCH 257/457] Add inline comment mentioning why `CreateChildDependencies` is being used in this instance --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index abf5cb040a..cb8b0fb3c8 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -47,11 +47,11 @@ namespace osu.Game.Skinning protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { parentSource = parent.Get(); - - UpdateSkinSources(); - parentSource.SourceChanged += OnSourceChanged; + // ensure sources are populated and ready for use before childrens' asynchronous load flow. + UpdateSkinSources(); + return base.CreateChildDependencies(parent); } From 73bd88cb318ae1c8b4690a8398587bb87f83b3ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 14:44:52 +0900 Subject: [PATCH 258/457] Simplify caching in test --- .../Rulesets/TestSceneRulesetSkinProvidingContainer.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index 0dde0a8194..25619de323 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -24,12 +24,8 @@ namespace osu.Game.Tests.Rulesets protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(new TestSkinProvider()); - return dependencies; - } + [Cached(typeof(ISkinSource))] + private readonly ISkinSource testSource = new TestSkinProvider(); [Test] public void TestEarlyAddedSkinRequester() From f777741ca788c3ea1a83830cd5db7286f943e67f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 14:51:27 +0900 Subject: [PATCH 259/457] Simplify instantiation --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 8a807eff21..a1db4ba6d1 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -96,12 +96,14 @@ namespace osu.Game.Skinning { int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + var rulesetResources = new ResourcesSkin(resources, host, audio); + if (defaultSkinIndex >= 0) - SkinSources.Insert(defaultSkinIndex, new ResourcesSkin(resources, host, audio)); + SkinSources.Insert(defaultSkinIndex, rulesetResources); else { // Tests may potentially override the SkinManager with another source that doesn't include it in AllSources. - SkinSources.Add(new ResourcesSkin(resources, host, audio)); + SkinSources.Add(rulesetResources); } } } From c281e43cd8beb91f50e0853579fab33699718502 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 15:04:14 +0900 Subject: [PATCH 260/457] Remove `Dispose()` special case and add explicit exception to make debugging issues non-deadlock --- osu.Game/Database/RealmContextFactory.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 4d81f8676f..2f70d8af22 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -97,6 +97,9 @@ namespace osu.Game.Database { try { + if (IsDisposed) + throw new InvalidOperationException(@"Attempted to retrieve a context after the factor has already been disposed."); + blockingLock.Wait(); contexts_created.Value++; @@ -128,11 +131,8 @@ namespace osu.Game.Database { base.Dispose(isDisposing); - // In the standard case, operations will already be blocked by the Update thread "pausing" from GameHost exit. - // This avoids waiting (forever) on an already entered semaphore. - if (context != null || active_usages.Value > 0) - BlockAllOperations(); - + // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal. + BlockAllOperations(); blockingLock?.Dispose(); } From 7dd566dc46639a28e86fda9bd0ba13030aaf74ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 15:08:49 +0900 Subject: [PATCH 261/457] Add null check for safety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index dec738e5b3..bf1b449292 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -370,7 +370,7 @@ namespace osu.Game switch (state.NewValue) { case GameThreadState.Running: - blocking.Dispose(); + blocking?.Dispose(); blocking = null; break; From 692f24437ef5ec3afd2eed87143da90bd621c5fd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 09:11:28 +0300 Subject: [PATCH 262/457] Maintain ruleset resources skin across multiple source changes --- .../Skinning/RulesetSkinProvidingContainer.cs | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index a1db4ba6d1..076f8b03dc 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -46,21 +46,20 @@ namespace osu.Game.Skinning }; } - [Resolved] - private GameHost host { get; set; } - - [Resolved] - private AudioManager audio { get; set; } - [Resolved] private SkinManager skinManager { get; set; } [Resolved] private ISkinSource skinSource { get; set; } + private ResourcesSkin rulesetResourcesSkin; + [BackgroundDependencyLoader] - private void load() + private void load(GameHost host, AudioManager audio) { + if (Ruleset.CreateResourceStore() is IResourceStore resources) + rulesetResourcesSkin = new ResourcesSkin(resources, host, audio); + UpdateSkins(); skinSource.SourceChanged += OnSourceChanged; } @@ -73,9 +72,6 @@ namespace osu.Game.Skinning protected virtual void UpdateSkins() { - foreach (var resourcesSkin in SkinSources.OfType()) - resourcesSkin.Dispose(); - SkinSources.Clear(); foreach (var skin in skinSource.AllSources) @@ -92,20 +88,12 @@ namespace osu.Game.Skinning } } - if (Ruleset.CreateResourceStore() is IResourceStore resources) - { - int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); - var rulesetResources = new ResourcesSkin(resources, host, audio); - - if (defaultSkinIndex >= 0) - SkinSources.Insert(defaultSkinIndex, rulesetResources); - else - { - // Tests may potentially override the SkinManager with another source that doesn't include it in AllSources. - SkinSources.Add(rulesetResources); - } - } + if (defaultSkinIndex >= 0) + SkinSources.Insert(defaultSkinIndex, rulesetResourcesSkin); + else + SkinSources.Add(rulesetResourcesSkin); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) @@ -126,6 +114,8 @@ namespace osu.Game.Skinning if (skinSource != null) skinSource.SourceChanged -= OnSourceChanged; + + rulesetResourcesSkin?.Dispose(); } } } From f598de4cdb0069109abc42485349b2026ea323f8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 09:18:29 +0300 Subject: [PATCH 263/457] ResourcesSkin -> ResourceStoreBackedSkin --- .../{ResourcesSkin.cs => ResourceStoreBackedSkin.cs} | 4 ++-- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) rename osu.Game/Skinning/{ResourcesSkin.cs => ResourceStoreBackedSkin.cs} (91%) diff --git a/osu.Game/Skinning/ResourcesSkin.cs b/osu.Game/Skinning/ResourceStoreBackedSkin.cs similarity index 91% rename from osu.Game/Skinning/ResourcesSkin.cs rename to osu.Game/Skinning/ResourceStoreBackedSkin.cs index 90020495c3..f041b82cf4 100644 --- a/osu.Game/Skinning/ResourcesSkin.cs +++ b/osu.Game/Skinning/ResourceStoreBackedSkin.cs @@ -19,12 +19,12 @@ namespace osu.Game.Skinning /// /// An that uses an underlying with namespaces for resources retrieval. /// - public class ResourcesSkin : ISkin, IDisposable + public class ResourceStoreBackedSkin : ISkin, IDisposable { private readonly TextureStore textures; private readonly ISampleStore samples; - public ResourcesSkin(IResourceStore resources, GameHost host, AudioManager audio) + public ResourceStoreBackedSkin(IResourceStore resources, GameHost host, AudioManager audio) { textures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(resources, @"Textures"))); samples = audio.GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 076f8b03dc..d35e9c1ff3 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.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.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -52,13 +51,13 @@ namespace osu.Game.Skinning [Resolved] private ISkinSource skinSource { get; set; } - private ResourcesSkin rulesetResourcesSkin; + private ResourceStoreBackedSkin rulesetResourcesSkin; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { if (Ruleset.CreateResourceStore() is IResourceStore resources) - rulesetResourcesSkin = new ResourcesSkin(resources, host, audio); + rulesetResourcesSkin = new ResourceStoreBackedSkin(resources, host, audio); UpdateSkins(); skinSource.SourceChanged += OnSourceChanged; From 88c6143aaeedc656e9fe81d2ea2efa7ce8069209 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 15:23:19 +0900 Subject: [PATCH 264/457] Rename variables in line with standards --- osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs | 6 +++--- osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs | 6 +++--- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs index e91eed420e..27ad6650d1 100644 --- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -221,11 +221,11 @@ namespace osu.Game.Tournament.Screens.Editors private void load(RulesetStore rulesets) { beatmapId.Value = Model.ID; - beatmapId.BindValueChanged(idInt => + beatmapId.BindValueChanged(id => { - Model.ID = idInt.NewValue ?? 0; + Model.ID = id.NewValue ?? 0; - if (idInt.NewValue != idInt.OldValue) + if (id.NewValue != id.OldValue) Model.BeatmapInfo = null; if (Model.BeatmapInfo != null) diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs index 4b50d74dc8..6418bf97da 100644 --- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs @@ -229,11 +229,11 @@ namespace osu.Game.Tournament.Screens.Editors private void load(RulesetStore rulesets) { beatmapId.Value = Model.ID; - beatmapId.BindValueChanged(idInt => + beatmapId.BindValueChanged(id => { - Model.ID = idInt.NewValue ?? 0; + Model.ID = id.NewValue ?? 0; - if (idInt.NewValue != idInt.OldValue) + if (id.NewValue != id.OldValue) Model.BeatmapInfo = null; if (Model.BeatmapInfo != null) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 22ec2d3398..0d2e64f300 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -279,11 +279,11 @@ namespace osu.Game.Tournament.Screens.Editors private void load() { userId.Value = user.Id; - userId.BindValueChanged(idInt => + userId.BindValueChanged(id => { - user.Id = idInt.NewValue ?? 0; + user.Id = id.NewValue ?? 0; - if (idInt.NewValue != idInt.OldValue) + if (id.NewValue != id.OldValue) user.Username = string.Empty; if (!string.IsNullOrEmpty(user.Username)) From eeb56970ef036f6dedebe72e57d218410d5b7b88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 15:24:55 +0900 Subject: [PATCH 265/457] Make `OutlinedNumberBox` private and nested again --- osu.Game/Overlays/Settings/OutlinedNumberBox.cs | 10 ---------- osu.Game/Overlays/Settings/SettingsNumberBox.cs | 5 +++++ 2 files changed, 5 insertions(+), 10 deletions(-) delete mode 100644 osu.Game/Overlays/Settings/OutlinedNumberBox.cs diff --git a/osu.Game/Overlays/Settings/OutlinedNumberBox.cs b/osu.Game/Overlays/Settings/OutlinedNumberBox.cs deleted file mode 100644 index 6fcadc09e1..0000000000 --- a/osu.Game/Overlays/Settings/OutlinedNumberBox.cs +++ /dev/null @@ -1,10 +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.Overlays.Settings -{ - public class OutlinedNumberBox : OutlinedTextBox - { - protected override bool CanAddCharacter(char character) => char.IsNumber(character); - } -} diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index de3e5a6bb0..2fbe522479 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -58,5 +58,10 @@ namespace osu.Game.Overlays.Settings }); } } + + private class OutlinedNumberBox : OutlinedTextBox + { + protected override bool CanAddCharacter(char character) => char.IsNumber(character); + } } } From e4780d4b06f9eb7b2b420114853f9b99b93481c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 15:29:47 +0900 Subject: [PATCH 266/457] 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 3c4380e355..c845d7f276 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f91620bd25..f047859dbb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 22c4340ba2..304047ad12 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 779a1b322ca177632e6ba76e50cb431c40cf2600 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 09:38:42 +0300 Subject: [PATCH 267/457] Add comment explaining insertion of ruleset skin before default skin Co-authored-by: Dean Herbert --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index d35e9c1ff3..5f0ca27170 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -89,6 +89,9 @@ namespace osu.Game.Skinning int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + // Ruleset resources should be given the ability to override game-wide defaults + // This is achieved by placing them before an instance of DefaultSkin. + // Note that DefaultSkin may not be present in some test scenes. if (defaultSkinIndex >= 0) SkinSources.Insert(defaultSkinIndex, rulesetResourcesSkin); else From 842f033522efc6b90ee88ff8d8684897e09d09c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 16:11:55 +0900 Subject: [PATCH 268/457] Remove no longer necessary exception --- osu.Game/Database/RealmContextFactory.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 2f70d8af22..461444870e 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -44,9 +44,6 @@ namespace osu.Game.Database { get { - if (IsDisposed) - throw new InvalidOperationException($"Attempted to access {nameof(Context)} on a disposed context factory"); - if (context == null) { context = createContext(); From 90f0bc87f5002b0479ec01508584c637d042358f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 16:12:21 +0900 Subject: [PATCH 269/457] Add safety against double disposal --- osu.Game/Database/RealmContextFactory.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 461444870e..0b0263a16b 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -126,22 +126,28 @@ namespace osu.Game.Database protected override void Dispose(bool isDisposing) { - base.Dispose(isDisposing); + if (!IsDisposed) + { + // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal. + BlockAllOperations(); + blockingLock?.Dispose(); + } - // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal. - BlockAllOperations(); - blockingLock?.Dispose(); + base.Dispose(isDisposing); } public IDisposable BlockAllOperations() { + if (IsDisposed) + throw new InvalidOperationException(@"Attempted to block operations after already disposed."); + blockingLock.Wait(); flushContexts(); return new InvokeOnDisposal(this, endBlockingSection); - } - private static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); + static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); + } private void flushContexts() { From f78cedd0e1d799aaec1562bcb1d1677c72bb5536 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 16:14:14 +0900 Subject: [PATCH 270/457] Reorder methods and add xmldoc for `BlockAllOperations` --- osu.Game/Database/RealmContextFactory.cs | 65 ++++++++++++++---------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 0b0263a16b..6214f4ca81 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -76,10 +76,26 @@ namespace osu.Game.Database return new RealmWriteUsage(createContext(), writeComplete); } - private void writeComplete() + /// + /// Flush any active contexts and block any further writes. + /// + /// + /// This should be used in places we need to ensure no ongoing reads/writes are occurring with realm. + /// ie. to move the realm backing file to a new location. + /// + /// An which should be disposed to end the blocking section. + /// Thrown if this context is already disposed. + public IDisposable BlockAllOperations() { - Monitor.Exit(writeLock); - pending_writes.Value--; + if (IsDisposed) + throw new InvalidOperationException(@"Attempted to block operations after already disposed."); + + blockingLock.Wait(); + flushContexts(); + + return new InvokeOnDisposal(this, endBlockingSection); + + static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); } protected override void Update() @@ -113,6 +129,12 @@ namespace osu.Game.Database } } + private void writeComplete() + { + Monitor.Exit(writeLock); + pending_writes.Value--; + } + private void onMigration(Migration migration, ulong lastSchemaVersion) { switch (lastSchemaVersion) @@ -124,31 +146,6 @@ namespace osu.Game.Database } } - protected override void Dispose(bool isDisposing) - { - if (!IsDisposed) - { - // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal. - BlockAllOperations(); - blockingLock?.Dispose(); - } - - base.Dispose(isDisposing); - } - - public IDisposable BlockAllOperations() - { - if (IsDisposed) - throw new InvalidOperationException(@"Attempted to block operations after already disposed."); - - blockingLock.Wait(); - flushContexts(); - - return new InvokeOnDisposal(this, endBlockingSection); - - static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); - } - private void flushContexts() { var previousContext = context; @@ -161,6 +158,18 @@ namespace osu.Game.Database previousContext?.Dispose(); } + protected override void Dispose(bool isDisposing) + { + if (!IsDisposed) + { + // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal. + BlockAllOperations(); + blockingLock?.Dispose(); + } + + base.Dispose(isDisposing); + } + /// /// A usage of realm from an arbitrary thread. /// From d4ea73d727ec94ed118e3279bc8752758b9a73bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 16:17:09 +0900 Subject: [PATCH 271/457] Simplify disposal exceptions --- osu.Game/Database/RealmContextFactory.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 6214f4ca81..71617b258d 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -84,11 +84,10 @@ namespace osu.Game.Database /// ie. to move the realm backing file to a new location. /// /// An which should be disposed to end the blocking section. - /// Thrown if this context is already disposed. public IDisposable BlockAllOperations() { if (IsDisposed) - throw new InvalidOperationException(@"Attempted to block operations after already disposed."); + throw new ObjectDisposedException(nameof(RealmContextFactory)); blockingLock.Wait(); flushContexts(); @@ -111,7 +110,7 @@ namespace osu.Game.Database try { if (IsDisposed) - throw new InvalidOperationException(@"Attempted to retrieve a context after the factor has already been disposed."); + throw new ObjectDisposedException(nameof(RealmContextFactory)); blockingLock.Wait(); From 035fe2ad4919e67be14d4fc2323d0daa10753fa2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 11:29:43 +0300 Subject: [PATCH 272/457] Mark ruleset skin provider test scene as headless --- .../Rulesets/TestSceneRulesetSkinProvidingContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index e060c8578a..28ad7ed6a7 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; +using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Rulesets; using osu.Game.Skinning; @@ -18,6 +19,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.Rulesets { + [HeadlessTest] public class TestSceneRulesetSkinProvidingContainer : OsuTestScene { private SkinRequester requester; From b5c3c9d55099a47d775d51ce5e64ed4d554a3244 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 28 Jun 2021 17:37:58 +0900 Subject: [PATCH 273/457] Disable realm analytics submission --- FodyWeavers.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FodyWeavers.xml b/FodyWeavers.xml index cc07b89533..ea490e3297 100644 --- a/FodyWeavers.xml +++ b/FodyWeavers.xml @@ -1,3 +1,3 @@  - + \ No newline at end of file From 7197998a104d0e25db060cf6adbbb4f14870bf64 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 12:43:12 +0300 Subject: [PATCH 274/457] Remove resolution to `SkinManager` and use pattern matching instead --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index c3d01218d7..26646d87fe 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.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.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -45,15 +46,12 @@ namespace osu.Game.Skinning }; } - private SkinManager skinManager; private ISkinSource parentSource; private ResourceStoreBackedSkin rulesetResourcesSkin; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - skinManager = parent.Get(); - parentSource = parent.Get(); parentSource.SourceChanged += OnSourceChanged; @@ -90,13 +88,13 @@ namespace osu.Game.Skinning } } - int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + int lastDefaultSkinIndex = SkinSources.IndexOf(SkinSources.OfType().Last()); // Ruleset resources should be given the ability to override game-wide defaults - // This is achieved by placing them before an instance of DefaultSkin. + // This is achieved by placing them before the last instance of DefaultSkin. // Note that DefaultSkin may not be present in some test scenes. - if (defaultSkinIndex >= 0) - SkinSources.Insert(defaultSkinIndex, rulesetResourcesSkin); + if (lastDefaultSkinIndex >= 0) + SkinSources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin); else SkinSources.Add(rulesetResourcesSkin); } From 66fc95c1118f74fa9ce78d154862b375428afa9e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 12:43:55 +0300 Subject: [PATCH 275/457] Use `LastOrDefault` instead --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 26646d87fe..19efc66814 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -88,7 +88,7 @@ namespace osu.Game.Skinning } } - int lastDefaultSkinIndex = SkinSources.IndexOf(SkinSources.OfType().Last()); + int lastDefaultSkinIndex = SkinSources.IndexOf(SkinSources.OfType().LastOrDefault()); // Ruleset resources should be given the ability to override game-wide defaults // This is achieved by placing them before the last instance of DefaultSkin. From ab977d91f0458c406c3b44db1fe21c664d3c4721 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 28 Jun 2021 20:40:22 +0700 Subject: [PATCH 276/457] fix image test not load looks like forgot to change this after `CurrentPath` shenanigan --- .../Online/TestSceneWikiMarkdownContainer.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index b6dce2c398..ec43ee2be3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -102,7 +102,7 @@ needs_cleanup: true { AddStep("Add absolute image", () => { - markdownContainer.DocumentUrl = "https://dev.ppy.sh"; + markdownContainer.CurrentPath = "https://dev.ppy.sh"; markdownContainer.Text = "![intro](/wiki/Interface/img/intro-screen.jpg)"; }); } @@ -112,8 +112,7 @@ needs_cleanup: true { AddStep("Add relative image", () => { - markdownContainer.DocumentUrl = "https://dev.ppy.sh"; - markdownContainer.CurrentPath = $"{API.WebsiteRootUrl}/wiki/Interface/"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/"; markdownContainer.Text = "![intro](img/intro-screen.jpg)"; }); } @@ -123,8 +122,7 @@ needs_cleanup: true { AddStep("Add paragraph with block image", () => { - markdownContainer.DocumentUrl = "https://dev.ppy.sh"; - markdownContainer.CurrentPath = $"{API.WebsiteRootUrl}/wiki/Interface/"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/"; markdownContainer.Text = @"Line before image ![play menu](img/play-menu.jpg ""Main Menu in osu!"") @@ -138,7 +136,7 @@ Line after image"; { AddStep("Add inline image", () => { - markdownContainer.DocumentUrl = "https://dev.ppy.sh"; + markdownContainer.CurrentPath = "https://dev.ppy.sh"; markdownContainer.Text = "![osu! mode icon](/wiki/shared/mode/osu.png) osu!"; }); } @@ -148,7 +146,7 @@ Line after image"; { AddStep("Add Table", () => { - markdownContainer.DocumentUrl = "https://dev.ppy.sh"; + markdownContainer.CurrentPath = "https://dev.ppy.sh"; markdownContainer.Text = @" | Image | Name | Effect | | :-: | :-: | :-- | @@ -166,11 +164,6 @@ Line after image"; { public LinkInline Link; - public new string DocumentUrl - { - set => base.DocumentUrl = value; - } - public override MarkdownTextFlowContainer CreateTextFlow() => new TestMarkdownTextFlowContainer { UrlAdded = link => Link = link, From 0e29ae037b8773ce4c88318ca6063998f2979c30 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 28 Jun 2021 20:50:29 +0700 Subject: [PATCH 277/457] add wide image test --- .../Online/TestSceneWikiMarkdownContainer.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index ec43ee2be3..69caa2896b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -160,6 +160,20 @@ Line after image"; }); } + [Test] + public void TestWideImageNotExceedContainer() + { + AddStep("Add image", () => + { + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/osu!_Program_Files/"; + markdownContainer.Text = "![](img/file_structure.jpg \"The file structure of osu!'s installation folder, on Windows and macOS\")"; + }); + AddStep("Change container width", () => + { + markdownContainer.Width = 0.5f; + }); + } + private class TestMarkdownContainer : WikiMarkdownContainer { public LinkInline Link; From 1c074ff0182c90f56b370d37f7511c942bedeaea Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 28 Jun 2021 20:55:40 +0700 Subject: [PATCH 278/457] add `BlockMarkdownImage` change from AutoSize Both to AutoSize Y and RelativeSize X --- .../Wiki/Markdown/WikiMarkdownImageBlock.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs index 179762103a..fbc0b8ee67 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs @@ -32,11 +32,7 @@ namespace osu.Game.Overlays.Wiki.Markdown { Children = new Drawable[] { - new WikiMarkdownImage(linkInline) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }, + new BlockMarkdownImage(linkInline), parentTextComponent.CreateSpriteText().With(t => { t.Text = linkInline.Title; @@ -45,5 +41,15 @@ namespace osu.Game.Overlays.Wiki.Markdown }), }; } + + private class BlockMarkdownImage : WikiMarkdownImage + { + public BlockMarkdownImage(LinkInline linkInline) + : base(linkInline) + { + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + } + } } } From 2b8d41c0452e94cb9577f64771e08e9b18161f0c Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 28 Jun 2021 21:04:56 +0700 Subject: [PATCH 279/457] add `BlockImageContainer` change from AutoSize Both to AutoSize Y and RelativeSize X --- .../Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs index fbc0b8ee67..b321cf328e 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs @@ -50,6 +50,18 @@ namespace osu.Game.Overlays.Wiki.Markdown AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; } + + protected override ImageContainer CreateImageContainer(string url) => new BlockImageContainer(url); + + private class BlockImageContainer : ImageContainer + { + public BlockImageContainer(string url) + : base(url) + { + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + } + } } } } From e58f690210037ed9ddcfa3c3651fc3910442f7fa Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 28 Jun 2021 21:09:00 +0700 Subject: [PATCH 280/457] centering image sprite --- osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs index b321cf328e..8d2eca9d5d 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Sprites; using osuTK; namespace osu.Game.Overlays.Wiki.Markdown @@ -61,6 +62,12 @@ namespace osu.Game.Overlays.Wiki.Markdown AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; } + + protected override Sprite CreateSpriteImage() => base.CreateSpriteImage().With(s => + { + s.Anchor = Anchor.TopCentre; + s.Origin = Anchor.TopCentre; + }); } } } From e4ca6a4266ce8624e189e9409128b97bd02b44bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Jun 2021 01:55:09 +0900 Subject: [PATCH 281/457] Serialise and send ruleset ID as part of score submission --- osu.Game/Scoring/ScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 3944c1d3de..4fd1d00fef 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -46,7 +46,7 @@ namespace osu.Game.Scoring [JsonIgnore] public int Combo { get; set; } // Todo: Shouldn't exist in here - [JsonIgnore] + [JsonProperty("ruleset_id")] public int RulesetID { get; set; } [JsonProperty("passed")] From 006cc331c8991e28fefae250824249bf3f0d569b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 18:51:43 +0300 Subject: [PATCH 282/457] Separate `IMutateApproachCircles` to requiring and hiding --- ...teApproachCircles.cs => IHidesApproachCircles.cs} | 4 ++-- .../Mods/IRequiresApproachCircles.cs | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Osu/Mods/{IMutateApproachCircles.cs => IHidesApproachCircles.cs} (57%) create mode 100644 osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs diff --git a/osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs similarity index 57% rename from osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs rename to osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs index 60a5825241..07ca93126d 100644 --- a/osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs +++ b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs @@ -4,9 +4,9 @@ namespace osu.Game.Rulesets.Osu.Mods { /// - /// Any mod which affects the animation or visibility of approach circles. Should be used for incompatibility purposes. + /// Any mod which completely hides the approach circles. Used for incompatibility with . /// - public interface IMutateApproachCircles + public interface IHidesApproachCircles { } } diff --git a/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs new file mode 100644 index 0000000000..f2bd836835 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Osu.Mods +{ + /// + /// Any mod which requires the approach circles to be visible. Used for incompatibility with . + /// + public interface IRequiresApproachCircles + { + } +} From 6a67a6736a5c248efbeab3e2b151838069950038 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 18:52:39 +0300 Subject: [PATCH 283/457] Mark `IRequireApproachCircles` mods as incompatible with `IHidesApproachCircles` --- osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs index 526e29ad53..d832411104 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObject, IMutateApproachCircles + public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObject, IRequiresApproachCircles { public override string Name => "Approach Different"; public override string Acronym => "AD"; @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle; - public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) }; [SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 0)] public BindableFloat Scale { get; } = new BindableFloat(4) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 84263221a7..a05e4dea03 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Skinning.Default; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModTraceable : ModWithVisibilityAdjustment, IMutateApproachCircles + public class OsuModTraceable : ModWithVisibilityAdjustment, IRequiresApproachCircles { public override string Name => "Traceable"; public override string Acronym => "TC"; @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "Put your faith in the approach circles..."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { From baf736026b1e58578ee10f3fb269ade776a3c35e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 18:54:21 +0300 Subject: [PATCH 284/457] Mark `IHidesApproachCircles` mods as incompatible with `IRequiresApproachCircles` --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 16b38cd0b1..ee591465cd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -15,12 +15,12 @@ using osu.Game.Rulesets.Osu.Skinning; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModHidden : ModHidden, IMutateApproachCircles + public class OsuModHidden : ModHidden, IHidesApproachCircles { public override string Description => @"Play with no approach circles and fading circles/sliders."; public override double ScoreMultiplier => 1.06; - public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles) }; private const double fade_in_duration_multiplier = 0.4; private const double fade_out_duration_multiplier = 0.3; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs index 6dfabed0df..9aafb02fa2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// /// Adjusts the size of hit objects during their fade in animation. /// - public abstract class OsuModObjectScaleTween : ModWithVisibilityAdjustment, IMutateApproachCircles + public abstract class OsuModObjectScaleTween : ModWithVisibilityAdjustment, IHidesApproachCircles { public override ModType Type => ModType.Fun; @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods protected virtual float EndScale => 1; - public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles) }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index d3ca2973f0..1854c20aef 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -12,7 +12,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModSpinIn : ModWithVisibilityAdjustment, IMutateApproachCircles + public class OsuModSpinIn : ModWithVisibilityAdjustment, IHidesApproachCircles { public override string Name => "Spin In"; public override string Acronym => "SI"; @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; // todo: this mod should be able to be compatible with hidden with a bit of further implementation. - public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles) }; private const int rotate_offset = 360; private const float rotate_starting_width = 2; From 69dac018c7c7b7a62dc3aeb08fa08e4956c39051 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 18:55:24 +0300 Subject: [PATCH 285/457] Make `OsuModSpinIn` as incompatible with other `IHidesApproachCircles` mods Respecting the TODO comment above it, mark `OsuModSpinIn` as incompatible with other `IHidesApproachCircles` mods as well. --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index ee591465cd..9c7784a00a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"Play with no approach circles and fading circles/sliders."; public override double ScoreMultiplier => 1.06; - public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) }; private const double fade_in_duration_multiplier = 0.4; private const double fade_out_duration_multiplier = 0.3; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs index 9aafb02fa2..778447e444 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods protected virtual float EndScale => 1; - public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index 1854c20aef..47a771c2f1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; // todo: this mod should be able to be compatible with hidden with a bit of further implementation. - public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(IHidesApproachCircles) }; private const int rotate_offset = 360; private const float rotate_starting_width = 2; From d88d2644918bba27389c2e6ea5308ab965c2f736 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 29 Jun 2021 09:49:45 +0700 Subject: [PATCH 286/457] prevent image sprite exceed its parent width --- .../Wiki/Markdown/WikiMarkdownImageBlock.cs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs index 8d2eca9d5d..4b0490d034 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs @@ -63,11 +63,28 @@ namespace osu.Game.Overlays.Wiki.Markdown RelativeSizeAxes = Axes.X; } - protected override Sprite CreateSpriteImage() => base.CreateSpriteImage().With(s => + protected override Sprite CreateSpriteImage() => new ImageSprite(); + + private class ImageSprite : Sprite { - s.Anchor = Anchor.TopCentre; - s.Origin = Anchor.TopCentre; - }); + public ImageSprite() + { + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + } + + protected override void Update() + { + base.Update(); + + if (Width > Parent.DrawWidth) + { + float ratio = Height / Width; + Width = Parent.DrawWidth; + Height = ratio * Width; + } + } + } } } } From a1fe9df378d88cd5edca44331fb867c59f35d94d Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 29 Jun 2021 10:14:51 +0700 Subject: [PATCH 287/457] add test to check sprite image width --- .../Visual/Online/TestSceneWikiMarkdownContainer.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index 69caa2896b..af2e4fc91a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using Markdig.Syntax.Inlines; using NUnit.Framework; using osu.Framework.Allocation; @@ -9,6 +10,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Graphics.Containers.Markdown; using osu.Game.Overlays; using osu.Game.Overlays.Wiki.Markdown; @@ -168,10 +172,19 @@ Line after image"; markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/osu!_Program_Files/"; markdownContainer.Text = "![](img/file_structure.jpg \"The file structure of osu!'s installation folder, on Windows and macOS\")"; }); + + AddUntilStep("Wait image to load", () => markdownContainer.ChildrenOfType().First().DelayedLoadCompleted); + AddStep("Change container width", () => { markdownContainer.Width = 0.5f; }); + + AddAssert("Image not exceed container width", () => + { + var spriteImage = markdownContainer.ChildrenOfType().First(); + return Precision.DefinitelyBigger(markdownContainer.DrawWidth, spriteImage.DrawWidth); + }); } private class TestMarkdownContainer : WikiMarkdownContainer From a3b1e1d5fc477f54de3e359f96ea4f3ab5a90cda Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 15:18:40 +0900 Subject: [PATCH 288/457] Check for null ruleset in FilterCriteria --- osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 8e59dc8579..5f135a3e90 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -93,7 +93,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { bool matchingFilter = true; - matchingFilter &= r.Room.Playlist.Count == 0 || r.Room.Playlist.Any(i => i.Ruleset.Value.Equals(criteria.Ruleset)); + matchingFilter &= r.Room.Playlist.Count == 0 || criteria.Ruleset == null || r.Room.Playlist.Any(i => i.Ruleset.Value.Equals(criteria.Ruleset)); if (!string.IsNullOrEmpty(criteria.SearchString)) matchingFilter &= r.FilterTerms.Any(term => term.Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); From 06beeee4d8004e6d7608761f3de0ab73a5b9ee07 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 15:39:01 +0900 Subject: [PATCH 289/457] Cleanup match header test --- .../Multiplayer/TestSceneMatchHeader.cs | 66 +++++++++---------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs index 3557bd9127..71ba5db481 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs @@ -17,43 +17,39 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUp] public new void Setup() => Schedule(() => { - SelectedRoom.Value = new Room(); + SelectedRoom.Value = new Room + { + Name = { Value = "A very awesome room" }, + Host = { Value = new User { Id = 2, Username = "peppy" } }, + Playlist = + { + new PlaylistItem + { + Beatmap = + { + Value = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Title = "Title", + Artist = "Artist", + AuthorString = "Author", + }, + Version = "Version", + Ruleset = new OsuRuleset().RulesetInfo + } + }, + RequiredMods = + { + new OsuModDoubleTime(), + new OsuModNoFail(), + new OsuModRelax(), + } + } + } + }; Child = new Header(); }); - - [Test] - public void TestBasicRoom() - { - AddStep("set basic room", () => - { - SelectedRoom.Value.Playlist.Add(new PlaylistItem - { - Beatmap = - { - Value = new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - Title = "Title", - Artist = "Artist", - AuthorString = "Author", - }, - Version = "Version", - Ruleset = new OsuRuleset().RulesetInfo - } - }, - RequiredMods = - { - new OsuModDoubleTime(), - new OsuModNoFail(), - new OsuModRelax(), - } - }); - - SelectedRoom.Value.Name.Value = "A very awesome room"; - SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; - }); - } } } From 9132c42f875576a4d7c9e74cccb1ecc1a9592826 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 15:58:07 +0900 Subject: [PATCH 290/457] Fix actions posted to the wrong channel --- osu.Game/Online/Chat/ChannelManager.cs | 4 ++-- osu.Game/Online/Chat/NowPlayingCommand.cs | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 8507887357..3136a3960d 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -225,7 +225,7 @@ namespace osu.Game.Online.Chat switch (command) { case "np": - AddInternal(new NowPlayingCommand()); + AddInternal(new NowPlayingCommand(target)); break; case "me": @@ -235,7 +235,7 @@ namespace osu.Game.Online.Chat break; } - PostMessage(content, true); + PostMessage(content, true, target); break; case "join": diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs index 926709694b..f522d8a236 100644 --- a/osu.Game/Online/Chat/NowPlayingCommand.cs +++ b/osu.Game/Online/Chat/NowPlayingCommand.cs @@ -21,6 +21,13 @@ namespace osu.Game.Online.Chat [Resolved] private Bindable currentBeatmap { get; set; } + private readonly Channel target; + + public NowPlayingCommand(Channel target) + { + this.target = target; + } + protected override void LoadComplete() { base.LoadComplete(); @@ -48,7 +55,7 @@ namespace osu.Game.Online.Chat var beatmapString = beatmap.OnlineBeatmapID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmap.OnlineBeatmapID} {beatmap}]" : beatmap.ToString(); - channelManager.PostMessage($"is {verb} {beatmapString}", true); + channelManager.PostMessage($"is {verb} {beatmapString}", true, target); Expire(); } } From 7a86686f40836dd58939ebbd00c0e7107f7c099c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 16:30:40 +0900 Subject: [PATCH 291/457] Make nullable --- osu.Game/Online/Chat/NowPlayingCommand.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs index f522d8a236..7756591e03 100644 --- a/osu.Game/Online/Chat/NowPlayingCommand.cs +++ b/osu.Game/Online/Chat/NowPlayingCommand.cs @@ -23,7 +23,11 @@ namespace osu.Game.Online.Chat private readonly Channel target; - public NowPlayingCommand(Channel target) + /// + /// Creates a new to post the currently-playing beatmap to a parenting . + /// + /// The target channel to post to. If null, the currently-selected channel will be posted to. + public NowPlayingCommand(Channel target = null) { this.target = target; } From ca0eaab8e21b743ad5bdc14ec3878e7658d5ca97 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 16:30:46 +0900 Subject: [PATCH 292/457] Add test --- .../Chat/TestSceneChannelManager.cs | 104 ++++++++++++++++++ .../Online/API/Requests/PostMessageRequest.cs | 10 +- 2 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Chat/TestSceneChannelManager.cs diff --git a/osu.Game.Tests/Chat/TestSceneChannelManager.cs b/osu.Game.Tests/Chat/TestSceneChannelManager.cs new file mode 100644 index 0000000000..b81c39933d --- /dev/null +++ b/osu.Game.Tests/Chat/TestSceneChannelManager.cs @@ -0,0 +1,104 @@ +// 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.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Chat; +using osu.Game.Tests.Visual; +using osu.Game.Users; + +namespace osu.Game.Tests.Chat +{ + [HeadlessTest] + public class TestSceneChannelManager : OsuTestScene + { + private ChannelManager channelManager; + private int currentMessageId; + + [SetUp] + public void Setup() => Schedule(() => + { + var container = new ChannelManagerContainer(); + Child = container; + channelManager = container.ChannelManager; + }); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("register request handling", () => + { + currentMessageId = 0; + + ((DummyAPIAccess)API).HandleRequest = req => + { + switch (req) + { + case JoinChannelRequest _: + return true; + + case PostMessageRequest postMessage: + postMessage.TriggerSuccess(new Message(++currentMessageId) + { + IsAction = postMessage.Message.IsAction, + ChannelId = postMessage.Message.ChannelId, + Content = postMessage.Message.Content, + Links = postMessage.Message.Links, + Timestamp = postMessage.Message.Timestamp, + Sender = postMessage.Message.Sender + }); + + return true; + } + + return false; + }; + }); + } + + [Test] + public void TestCommandsPostedToCorrectChannelWhenNotCurrent() + { + Channel channel1 = null; + Channel channel2 = null; + + AddStep("join 2 rooms", () => + { + channelManager.JoinChannel(channel1 = createChannel(1, ChannelType.Public)); + channelManager.JoinChannel(channel2 = createChannel(2, ChannelType.Public)); + }); + + AddStep("select channel 1", () => channelManager.CurrentChannel.Value = channel1); + + AddStep("post /me command to channel 2", () => channelManager.PostCommand("me dances", channel2)); + AddAssert("/me command received by channel 2", () => channel2.Messages.Last().Content == "dances"); + + AddStep("post /np command to channel 2", () => channelManager.PostCommand("np", channel2)); + AddAssert("/np command received by channel 2", () => channel2.Messages.Last().Content.Contains("is listening to")); + } + + private Channel createChannel(int id, ChannelType type) => new Channel(new User()) + { + Id = id, + Name = $"Channel {id}", + Topic = $"Topic of channel {id} with type {type}", + Type = type, + }; + + private class ChannelManagerContainer : CompositeDrawable + { + [Cached] + public ChannelManager ChannelManager { get; } = new ChannelManager(); + + public ChannelManagerContainer() + { + InternalChild = ChannelManager; + } + } + } +} diff --git a/osu.Game/Online/API/Requests/PostMessageRequest.cs b/osu.Game/Online/API/Requests/PostMessageRequest.cs index 84ab873acf..5d508a4cdf 100644 --- a/osu.Game/Online/API/Requests/PostMessageRequest.cs +++ b/osu.Game/Online/API/Requests/PostMessageRequest.cs @@ -9,11 +9,11 @@ namespace osu.Game.Online.API.Requests { public class PostMessageRequest : APIRequest { - private readonly Message message; + public readonly Message Message; public PostMessageRequest(Message message) { - this.message = message; + Message = message; } protected override WebRequest CreateWebRequest() @@ -21,12 +21,12 @@ namespace osu.Game.Online.API.Requests var req = base.CreateWebRequest(); req.Method = HttpMethod.Post; - req.AddParameter(@"is_action", message.IsAction.ToString().ToLowerInvariant()); - req.AddParameter(@"message", message.Content); + req.AddParameter(@"is_action", Message.IsAction.ToString().ToLowerInvariant()); + req.AddParameter(@"message", Message.Content); return req; } - protected override string Target => $@"chat/channels/{message.ChannelId}/messages"; + protected override string Target => $@"chat/channels/{Message.ChannelId}/messages"; } } From 8bcb4d13fb66a5c49101c9d10b424ca0bd3f6fed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Jun 2021 17:21:09 +0900 Subject: [PATCH 293/457] Fix multiple tests eating host exceptions --- osu.Game.Tests/ImportTest.cs | 3 ++- .../NonVisual/CustomTourneyDirectoryTest.cs | 3 ++- osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs index ea351e0d45..e888f51e98 100644 --- a/osu.Game.Tests/ImportTest.cs +++ b/osu.Game.Tests/ImportTest.cs @@ -17,7 +17,8 @@ namespace osu.Game.Tests protected virtual TestOsuGameBase LoadOsuIntoHost(GameHost host, bool withBeatmap = false) { var osu = new TestOsuGameBase(withBeatmap); - Task.Run(() => host.Run(osu)); + Task.Run(() => host.Run(osu)) + .ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted); waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 46c3b8bc3b..61f8511e3c 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -149,7 +149,8 @@ namespace osu.Game.Tournament.Tests.NonVisual private TournamentGameBase loadOsu(GameHost host) { var osu = new TournamentGameBase(); - Task.Run(() => host.Run(osu)); + Task.Run(() => host.Run(osu)) + .ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted); waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); return osu; } diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index 4c5f5a7a1a..e4eb5a36fb 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -55,7 +55,8 @@ namespace osu.Game.Tournament.Tests.NonVisual private TournamentGameBase loadOsu(GameHost host) { var osu = new TournamentGameBase(); - Task.Run(() => host.Run(osu)); + Task.Run(() => host.Run(osu)) + .ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted); waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); return osu; } From 7997d570308d39ea6c3e1c95beba03c59f119976 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 29 Jun 2021 11:25:35 +0300 Subject: [PATCH 294/457] Mention interfaces being "markers" for the time being --- osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs | 2 +- osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs index 07ca93126d..fe0abc453d 100644 --- a/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs +++ b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs @@ -4,7 +4,7 @@ namespace osu.Game.Rulesets.Osu.Mods { /// - /// Any mod which completely hides the approach circles. Used for incompatibility with . + /// Marker interface for any mod which completely hides the approach circles. Used for incompatibility with . /// public interface IHidesApproachCircles { diff --git a/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs index f2bd836835..3220ebf378 100644 --- a/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs +++ b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs @@ -4,7 +4,7 @@ namespace osu.Game.Rulesets.Osu.Mods { /// - /// Any mod which requires the approach circles to be visible. Used for incompatibility with . + /// Marker interface for any mod which requires the approach circles to be visible. Used for incompatibility with . /// public interface IRequiresApproachCircles { From bfdbe3c3fe534fc3b0060f4253635065f30b074b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 17:29:25 +0900 Subject: [PATCH 295/457] Fix clocks getting added a second time --- .../Multiplayer/Spectate/CatchUpSyncManager.cs | 7 ++++++- .../Spectate/MultiSpectatorScreen.cs | 17 ++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index 781123f5bb..94278a47b6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -54,7 +55,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate MasterClock = master; } - public void AddPlayerClock(ISpectatorPlayerClock clock) => playerClocks.Add(clock); + public void AddPlayerClock(ISpectatorPlayerClock clock) + { + Debug.Assert(!playerClocks.Contains(clock)); + playerClocks.Add(clock); + } public void RemovePlayerClock(ISpectatorPlayerClock clock) => playerClocks.Remove(clock); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 013e5551cf..e709cba0ee 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -101,7 +101,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Expanded = { Value = true }, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - }, leaderboardContainer.Add); + }, l => + { + foreach (var instance in instances) + leaderboard.AddClock(instance.UserId, instance.GameplayClock); + + leaderboardContainer.Add(leaderboard); + }); syncManager.ReadyToStart += onReadyToStart; syncManager.MasterState.BindValueChanged(onMasterStateChanged, true); @@ -166,14 +172,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } protected override void StartGameplay(int userId, GameplayState gameplayState) - { - var instance = instances.Single(i => i.UserId == userId); - - instance.LoadScore(gameplayState.Score); - - syncManager.AddPlayerClock(instance.GameplayClock); - leaderboard.AddClock(instance.UserId, instance.GameplayClock); - } + => instances.Single(i => i.UserId == userId).LoadScore(gameplayState.Score); protected override void EndGameplay(int userId) { From dbe3150f88465c25dbba23c2111e97968e50b26a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 29 Jun 2021 11:37:03 +0300 Subject: [PATCH 296/457] Mention about marker interfaces even more explicitly --- osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs | 6 +++++- osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs index fe0abc453d..4a3b187e83 100644 --- a/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs +++ b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs @@ -4,8 +4,12 @@ namespace osu.Game.Rulesets.Osu.Mods { /// - /// Marker interface for any mod which completely hides the approach circles. Used for incompatibility with . + /// Marker interface for any mod which completely hides the approach circles. + /// Used for incompatibility with . /// + /// + /// Note that this is only a marker interface for incompatibility purposes, it does not change any gameplay behaviour. + /// public interface IHidesApproachCircles { } diff --git a/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs index 3220ebf378..1458abfe05 100644 --- a/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs +++ b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs @@ -4,8 +4,12 @@ namespace osu.Game.Rulesets.Osu.Mods { /// - /// Marker interface for any mod which requires the approach circles to be visible. Used for incompatibility with . + /// Marker interface for any mod which requires the approach circles to be visible. + /// Used for incompatibility with . /// + /// + /// Note that this is only a marker interface for incompatibility purposes, it does not change any gameplay behaviour. + /// public interface IRequiresApproachCircles { } From 1b2d00f796735f1fa94234cd5ff91778079087f1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 20:13:39 +0900 Subject: [PATCH 297/457] Trigger successes --- osu.Game.Tests/Chat/TestSceneChannelManager.cs | 3 ++- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Chat/TestSceneChannelManager.cs b/osu.Game.Tests/Chat/TestSceneChannelManager.cs index b81c39933d..0ec21a4c7b 100644 --- a/osu.Game.Tests/Chat/TestSceneChannelManager.cs +++ b/osu.Game.Tests/Chat/TestSceneChannelManager.cs @@ -39,7 +39,8 @@ namespace osu.Game.Tests.Chat { switch (req) { - case JoinChannelRequest _: + case JoinChannelRequest joinChannel: + joinChannel.TriggerSuccess(); return true; case PostMessageRequest postMessage: diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 3971146ff8..a1549dfbce 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -82,7 +82,8 @@ namespace osu.Game.Tests.Visual.Online { switch (req) { - case JoinChannelRequest _: + case JoinChannelRequest joinChannel: + joinChannel.TriggerSuccess(); return true; } From 9acc5e38bb1574fe0ff9226cd8ff4221a3e29d49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Jun 2021 20:16:57 +0900 Subject: [PATCH 298/457] Add basic logging for osu! storage migration When looking into the test failure at https://github.com/ppy/osu/runs/2940065457, it became apparent that we are not showing the migration process anywhere in logs. It's the cause of many issues, and we would want to see this in CI and user logs when occurring. --- osu.Game/OsuGameBase.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index bf1b449292..7954eafdca 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -422,11 +422,15 @@ namespace osu.Game public void Migrate(string path) { + Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""..."); + using (realmFactory.BlockAllOperations()) { contextFactory.FlushConnections(); (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); } + + Logger.Log(@"Migration complete!"); } protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); From 2f1203085b4fc015dbda854510f274564ad18b21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Jun 2021 20:21:31 +0900 Subject: [PATCH 299/457] Also add logging of realm block/flush operations --- osu.Game/Database/RealmContextFactory.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 71617b258d..fb5e2faff8 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -89,12 +89,18 @@ namespace osu.Game.Database if (IsDisposed) throw new ObjectDisposedException(nameof(RealmContextFactory)); + Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); + blockingLock.Wait(); flushContexts(); return new InvokeOnDisposal(this, endBlockingSection); - static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); + static void endBlockingSection(RealmContextFactory factory) + { + factory.blockingLock.Release(); + Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); + } } protected override void Update() @@ -147,6 +153,8 @@ namespace osu.Game.Database private void flushContexts() { + Logger.Log(@"Flushing realm contexts...", LoggingTarget.Database); + var previousContext = context; context = null; @@ -155,6 +163,8 @@ namespace osu.Game.Database Thread.Sleep(50); previousContext?.Dispose(); + + Logger.Log(@"Realm contexts flushed.", LoggingTarget.Database); } protected override void Dispose(bool isDisposing) From 3a002e357a63caa42578c39c8cf6cf8082c43cb9 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 29 Jun 2021 19:28:21 +0700 Subject: [PATCH 300/457] rename method --- osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs index 4b0490d034..1a4f6087c7 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs @@ -63,7 +63,7 @@ namespace osu.Game.Overlays.Wiki.Markdown RelativeSizeAxes = Axes.X; } - protected override Sprite CreateSpriteImage() => new ImageSprite(); + protected override Sprite CreateImageSprite() => new ImageSprite(); private class ImageSprite : Sprite { From 6dd3c6fe9307b5d71c850ef8dbe6d1733d2e8643 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 22:45:18 +0900 Subject: [PATCH 301/457] Make MultiSpectatorScreen and tests more resillient to timing --- .../Multiplayer/TestSceneMultiSpectatorScreen.cs | 12 +++++++++--- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 16 +++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index a308e854c6..8c63fb8924 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -82,8 +82,8 @@ namespace osu.Game.Tests.Visual.Multiplayer start(new[] { PLAYER_1_ID, PLAYER_2_ID }); loadSpectateScreen(); - sendFrames(PLAYER_1_ID, 20); - sendFrames(PLAYER_2_ID, 10); + sendFrames(PLAYER_1_ID, 40); + sendFrames(PLAYER_2_ID, 20); checkPaused(PLAYER_2_ID, true); checkPausedInstant(PLAYER_1_ID, false); @@ -196,6 +196,7 @@ namespace osu.Game.Tests.Visual.Multiplayer sendFrames(PLAYER_1_ID, 10); sendFrames(PLAYER_2_ID, 20); + checkPaused(PLAYER_1_ID, false); assertMuted(PLAYER_1_ID, false); assertMuted(PLAYER_2_ID, true); @@ -297,7 +298,12 @@ namespace osu.Game.Tests.Visual.Multiplayer => AddUntilStep($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); private void checkPausedInstant(int userId, bool state) - => AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); + { + checkPaused(userId, state); + + // Todo: The following should work, but is broken because SpectatorScreen retrieves the WorkingBeatmap via the BeatmapManager, bypassing the test scene clock and running real-time. + // AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); + } private void assertMuted(int userId, bool muted) => AddAssert($"{userId} {(muted ? "is" : "is not")} muted", () => getInstance(userId).Mute == muted); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index e709cba0ee..2a2759e0dd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -43,6 +43,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private PlayerGrid grid; private MultiSpectatorLeaderboard leaderboard; private PlayerArea currentAudioSource; + private bool canStartMasterClock; /// /// Creates a new . @@ -108,17 +109,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate leaderboardContainer.Add(leaderboard); }); - - syncManager.ReadyToStart += onReadyToStart; - syncManager.MasterState.BindValueChanged(onMasterStateChanged, true); } protected override void LoadComplete() { base.LoadComplete(); - masterClockContainer.Stop(); masterClockContainer.Reset(); + masterClockContainer.Stop(); + + syncManager.ReadyToStart += onReadyToStart; + syncManager.MasterState.BindValueChanged(onMasterStateChanged, true); } protected override void Update() @@ -151,6 +152,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate masterClockContainer.Seek(startTime); masterClockContainer.Start(); + + // Although the clock has been started, this flag is set to allow for later synchronisation state changes to also be able to start it. + canStartMasterClock = true; } private void onMasterStateChanged(ValueChangedEvent state) @@ -158,7 +162,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate switch (state.NewValue) { case MasterClockState.Synchronised: - masterClockContainer.Start(); + if (canStartMasterClock) + masterClockContainer.Start(); + break; case MasterClockState.TooFarAhead: From 331b7237ab5f175067a886668d2ef3de34baf664 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 22:53:31 +0900 Subject: [PATCH 302/457] Attempt to fix one more intermittent test failure --- .../Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 8c63fb8924..783db49d36 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -197,8 +197,7 @@ namespace osu.Game.Tests.Visual.Multiplayer sendFrames(PLAYER_1_ID, 10); sendFrames(PLAYER_2_ID, 20); checkPaused(PLAYER_1_ID, false); - assertMuted(PLAYER_1_ID, false); - assertMuted(PLAYER_2_ID, true); + assertOneNotMuted(); checkPaused(PLAYER_1_ID, true); assertMuted(PLAYER_1_ID, true); @@ -305,6 +304,8 @@ namespace osu.Game.Tests.Visual.Multiplayer // AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); } + private void assertOneNotMuted() => AddAssert("one player not muted", () => spectatorScreen.ChildrenOfType().Count(p => !p.Mute) == 1); + private void assertMuted(int userId, bool muted) => AddAssert($"{userId} {(muted ? "is" : "is not")} muted", () => getInstance(userId).Mute == muted); From e9158ccc41828b1a3fc220d5f15e8df03bca09d3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 23:23:21 +0900 Subject: [PATCH 303/457] Fix gameplay tests incorrectly seeking via MusicController --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index e111bb1054..1d500dcc14 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void addSeekStep(double time) { - AddStep($"seek to {time}", () => MusicController.SeekTo(time)); + AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time)); AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 8ff21057b5..9da583a073 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void addSeekStep(double time) { - AddStep($"seek to {time}", () => MusicController.SeekTo(time)); + AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time)); AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } From 43375774487498cf412be541ce8d17c3169ee6b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Jun 2021 14:31:27 +0900 Subject: [PATCH 304/457] Remove unused private methods --- .../Multiplayer/TestSceneMultiSpectatorScreen.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 873f8ca35b..ea8f7813fd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -217,8 +217,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded)); } - private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId); - private void start(int[] userIds, int? beatmapId = null) { AddStep("start play", () => @@ -233,16 +231,6 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } - private void finish(int userId) - { - AddStep("end play", () => - { - SpectatorClient.EndPlay(userId); - playingUserIds.Remove(userId); - nextFrame.Remove(userId); - }); - } - private void sendFrames(int userId, int count = 10) => sendFrames(new[] { userId }, count); private void sendFrames(int[] userIds, int count = 10) From e3344c33ea27d2269d60e06aaab6933f10763e1b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 30 Jun 2021 09:57:15 +0300 Subject: [PATCH 305/457] Better convey reason of "Spin In" incompatibility with "Hidden" --- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index 47a771c2f1..7b2a0c6e0f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -21,8 +21,9 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "Circles spin in. No approach circles."; public override double ScoreMultiplier => 1; - // todo: this mod should be able to be compatible with hidden with a bit of further implementation. - public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(IHidesApproachCircles) }; + // todo: this mod needs to be incompatible with "hidden" due to forcing the circle to remain opaque, + // further implementation will be required for supporting that. + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModObjectScaleTween), typeof(OsuModHidden) }; private const int rotate_offset = 360; private const float rotate_starting_width = 2; @@ -49,10 +50,6 @@ namespace osu.Game.Rulesets.Osu.Mods circle.RotateTo(rotate_offset).Then().RotateTo(0, h.TimePreempt, Easing.InOutSine); circle.ScaleTo(new Vector2(rotate_starting_width, 0)).Then().ScaleTo(1, h.TimePreempt, Easing.InOutSine); - - // bypass fade in. - if (state == ArmedState.Idle) - circle.FadeIn(); } break; From 57a21dfb1ca75d4024060ccfc054a2132237907d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Jun 2021 16:11:18 +0900 Subject: [PATCH 306/457] 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 c845d7f276..481ddc118f 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f047859dbb..589afb86be 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 304047ad12..a8bf0e4ab2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 4b56d94a9806b11e9017ed919cf05cf5a8620c08 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 30 Jun 2021 10:26:27 +0300 Subject: [PATCH 307/457] Revert accidentally pushed part of change --- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index 7b2a0c6e0f..56c246953e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -50,6 +50,10 @@ namespace osu.Game.Rulesets.Osu.Mods circle.RotateTo(rotate_offset).Then().RotateTo(0, h.TimePreempt, Easing.InOutSine); circle.ScaleTo(new Vector2(rotate_starting_width, 0)).Then().ScaleTo(1, h.TimePreempt, Easing.InOutSine); + + // bypass fade in. + if (state == ArmedState.Idle) + circle.FadeIn(); } break; From e23614556eaefc9a579f7a42e6cf8f5f8f4c2dc6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 30 Jun 2021 19:43:34 +0900 Subject: [PATCH 308/457] Fix slider testscene failures --- .../TestSceneSliderSnaking.cs | 102 ++++++++++-------- 1 file changed, 59 insertions(+), 43 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 1d500dcc14..744291ae09 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -37,11 +37,13 @@ namespace osu.Game.Rulesets.Osu.Tests private readonly BindableBool snakingIn = new BindableBool(); private readonly BindableBool snakingOut = new BindableBool(); + private IBeatmap beatmap; + private const double duration_of_span = 3605; private const double fade_in_modifier = -1200; protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); + => new ClockBackedTestWorkingBeatmap(this.beatmap = beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); [BackgroundDependencyLoader] private void load(RulesetConfigCache configCache) @@ -51,8 +53,16 @@ namespace osu.Game.Rulesets.Osu.Tests config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut); } + private Slider slider; private DrawableSlider drawableSlider; + [SetUp] + public void Setup() => Schedule(() => + { + slider = null; + drawableSlider = null; + }); + [SetUpSteps] public override void SetUpSteps() { @@ -67,21 +77,19 @@ namespace osu.Game.Rulesets.Osu.Tests base.SetUpSteps(); AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); - double startTime = hitObjects[sliderIndex].StartTime; - addSeekStep(startTime); - retrieveDrawableSlider((Slider)hitObjects[sliderIndex]); + retrieveSlider(sliderIndex); setSnaking(true); - ensureSnakingIn(startTime + fade_in_modifier); + addEnsureSnakingInSteps(() => slider.StartTime + fade_in_modifier); for (int i = 0; i < sliderIndex; i++) { // non-final repeats should not snake out - ensureNoSnakingOut(startTime, i); + addEnsureNoSnakingOutStep(() => slider.StartTime, i); } // final repeat should snake out - ensureSnakingOut(startTime, sliderIndex); + addEnsureSnakingOutSteps(() => slider.StartTime, sliderIndex); } [TestCase(0)] @@ -93,17 +101,15 @@ namespace osu.Game.Rulesets.Osu.Tests base.SetUpSteps(); AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); - double startTime = hitObjects[sliderIndex].StartTime; - addSeekStep(startTime); - retrieveDrawableSlider((Slider)hitObjects[sliderIndex]); + retrieveSlider(sliderIndex); setSnaking(false); - ensureNoSnakingIn(startTime + fade_in_modifier); + addEnsureNoSnakingInSteps(() => slider.StartTime + fade_in_modifier); for (int i = 0; i <= sliderIndex; i++) { // no snaking out ever, including final repeat - ensureNoSnakingOut(startTime, i); + addEnsureNoSnakingOutStep(() => slider.StartTime, i); } } @@ -116,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests // repeat might have a chance to update its position depending on where in the frame its hit, // so some leniency is allowed here instead of checking strict equality - checkPositionChange(16600, sliderRepeat, positionAlmostSame); + addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionAlmostSame); } [Test] @@ -126,38 +132,46 @@ namespace osu.Game.Rulesets.Osu.Tests setSnaking(true); base.SetUpSteps(); - checkPositionChange(16600, sliderRepeat, positionDecreased); + addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionDecreased); } - private void retrieveDrawableSlider(Slider slider) => AddUntilStep($"retrieve slider @ {slider.StartTime}", () => - (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null); - - private void ensureSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionIncreased); - private void ensureNoSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionRemainsSame); - - private void ensureSnakingOut(double startTime, int repeatIndex) + private void retrieveSlider(int index) { - var repeatTime = timeAtRepeat(startTime, repeatIndex); + AddStep("retrieve slider at index", () => slider = (Slider)beatmap.HitObjects[index]); + addSeekStep(() => slider); + retrieveDrawableSlider(() => slider); + } + private void retrieveDrawableSlider(Func getSliderFunc) + { + AddUntilStep("retrieve drawable slider", () => + (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == getSliderFunc())) != null); + } + + private void addEnsureSnakingInSteps(Func startTime) => addCheckPositionChangeSteps(startTime, getSliderEnd, positionIncreased); + private void addEnsureNoSnakingInSteps(Func startTime) => addCheckPositionChangeSteps(startTime, getSliderEnd, positionRemainsSame); + + private void addEnsureSnakingOutSteps(Func startTime, int repeatIndex) + { if (repeatIndex % 2 == 0) - checkPositionChange(repeatTime, sliderStart, positionIncreased); + addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), getSliderStart, positionIncreased); else - checkPositionChange(repeatTime, sliderEnd, positionDecreased); + addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), getSliderEnd, positionDecreased); } - private void ensureNoSnakingOut(double startTime, int repeatIndex) => - checkPositionChange(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame); + private void addEnsureNoSnakingOutStep(Func startTime, int repeatIndex) + => addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame); - private double timeAtRepeat(double startTime, int repeatIndex) => startTime + 100 + duration_of_span * repeatIndex; - private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func)sliderStart : sliderEnd; + private Func timeAtRepeat(Func startTime, int repeatIndex) => () => startTime() + 100 + duration_of_span * repeatIndex; + private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func)getSliderStart : getSliderEnd; - private List sliderCurve => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve; - private Vector2 sliderStart() => sliderCurve.First(); - private Vector2 sliderEnd() => sliderCurve.Last(); + private List getSliderCurve() => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve; + private Vector2 getSliderStart() => getSliderCurve().First(); + private Vector2 getSliderEnd() => getSliderCurve().Last(); - private Vector2 sliderRepeat() + private Vector2 getSliderRepeat() { - var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == hitObjects[1]); + var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == beatmap.HitObjects[1]); var repeat = drawable.ChildrenOfType>().First().Children.First(); return repeat.Position; } @@ -167,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Tests private bool positionDecreased(Vector2 previous, Vector2 current) => current.X < previous.X && current.Y < previous.Y; private bool positionAlmostSame(Vector2 previous, Vector2 current) => Precision.AlmostEquals(previous, current, 1); - private void checkPositionChange(double startTime, Func positionToCheck, Func positionAssertion) + private void addCheckPositionChangeSteps(Func startTime, Func positionToCheck, Func positionAssertion) { Vector2 previousPosition = Vector2.Zero; @@ -176,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(startTime); AddStep($"save {positionDescription} position", () => previousPosition = positionToCheck.Invoke()); - addSeekStep(startTime + 100); + addSeekStep(() => startTime() + 100); AddAssert($"{positionDescription} {assertionDescription}", () => { var currentPosition = positionToCheck.Invoke(); @@ -193,19 +207,21 @@ namespace osu.Game.Rulesets.Osu.Tests }); } - private void addSeekStep(double time) + private void addSeekStep(Func slider) { - AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time)); - - AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); + AddStep("seek to slider", () => Player.GameplayClockContainer.Seek(slider().StartTime)); + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(slider().StartTime, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap + private void addSeekStep(Func time) { - HitObjects = hitObjects - }; + AddStep("seek to time", () => Player.GameplayClockContainer.Seek(time())); + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time(), Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); + } - private readonly List hitObjects = new List + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { HitObjects = createHitObjects() }; + + private List createHitObjects() => new List { new Slider { From b94d88be5078457279ac09df1086950a335ee8b7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 30 Jun 2021 19:49:51 +0900 Subject: [PATCH 309/457] Make method static to better define its usage --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 744291ae09..7dafecfced 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -221,7 +221,7 @@ namespace osu.Game.Rulesets.Osu.Tests protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { HitObjects = createHitObjects() }; - private List createHitObjects() => new List + private static List createHitObjects() => new List { new Slider { From 2c1f788f2de20f495baf0e2631e54163824a408c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 30 Jun 2021 19:52:25 +0900 Subject: [PATCH 310/457] Merge methods --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 7dafecfced..3252e6d912 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -139,13 +139,8 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep("retrieve slider at index", () => slider = (Slider)beatmap.HitObjects[index]); addSeekStep(() => slider); - retrieveDrawableSlider(() => slider); - } - - private void retrieveDrawableSlider(Func getSliderFunc) - { AddUntilStep("retrieve drawable slider", () => - (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == getSliderFunc())) != null); + (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null); } private void addEnsureSnakingInSteps(Func startTime) => addCheckPositionChangeSteps(startTime, getSliderEnd, positionIncreased); From aa7405afa1f8c467c0534cd20101e7e00fa752ab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 30 Jun 2021 20:16:57 +0900 Subject: [PATCH 311/457] Increase number of sent frames to prevent timing issues --- .../Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 5112029873..b8db4067fb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -168,7 +168,7 @@ namespace osu.Game.Tests.Visual.Multiplayer // Send initial frames for both players. A few more for player 1. sendFrames(PLAYER_1_ID, 1000); - sendFrames(PLAYER_2_ID, 10); + sendFrames(PLAYER_2_ID, 30); checkPausedInstant(PLAYER_1_ID, false); checkPausedInstant(PLAYER_2_ID, false); From 4a54e7cdb83db4eb7f86d9af3f2e077d7668088f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 18:30:12 +0900 Subject: [PATCH 312/457] Add tests covering score preparation flow --- .../TestScenePlayerScorePreparation.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestScenePlayerScorePreparation.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScorePreparation.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScorePreparation.cs new file mode 100644 index 0000000000..8ea5f3f9ee --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScorePreparation.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestScenePlayerScorePreparation : OsuPlayerTestScene + { + protected override bool AllowFail => false; + + protected new PreparingPlayer Player => (PreparingPlayer)base.Player; + + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + + // Ensure track has actually running before attempting to seek + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + } + + [Test] + public void TestPreparationOnResults() + { + AddUntilStep("wait for preparation", () => Player.PreparationCompleted); + } + + [Test] + public void TestPreparationOnExit() + { + AddStep("exit", () => Player.Exit()); + AddUntilStep("wait for preparation", () => Player.PreparationCompleted); + } + + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new PreparingPlayer(); + + public class PreparingPlayer : TestPlayer + { + public bool PreparationCompleted { get; private set; } + + public bool ResultsCreated { get; private set; } + + public PreparingPlayer() + : base(true, true) + { + } + + protected override ResultsScreen CreateResults(ScoreInfo score) + { + var results = base.CreateResults(score); + ResultsCreated = true; + return results; + } + + protected override Task PrepareScoreForResultsAsync(Score score) + { + PreparationCompleted = true; + return base.PrepareScoreForResultsAsync(score); + } + } + } +} From 00d3baef1191f67dc062ee5205804372e1bf4ca5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Jun 2021 20:23:48 +0900 Subject: [PATCH 313/457] Exit handling flow --- osu.Game/Screens/Play/SubmittingPlayer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index b843915a7c..2ee116a6a3 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -102,6 +102,11 @@ namespace osu.Game.Screens.Play /// Whether gameplay should be immediately exited as a result. Returning false allows the gameplay session to continue. Defaults to true. protected virtual bool HandleTokenRetrievalFailure(Exception exception) => true; + public override bool OnExiting(IScreen next) + { + return base.OnExiting(next); + } + protected override async Task PrepareScoreForResultsAsync(Score score) { await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); From 285c49be7ec8ac465081d91294bada9b83ba97d0 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 30 Jun 2021 20:28:27 +0200 Subject: [PATCH 314/457] Localise dashboard overlay header. --- .../Dashboard/DashboardOverlayHeader.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs index 3314ed957a..ce4ec27225 100644 --- a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs +++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs @@ -1,7 +1,10 @@ // 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.ComponentModel; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Dashboard { @@ -13,13 +16,14 @@ namespace osu.Game.Overlays.Dashboard { public DashboardTitle() { - Title = "dashboard"; + Title = HomeStrings.UserTitle; Description = "view your friends and other information"; IconTexture = "Icons/Hexacons/social"; } } } + [LocalisableEnum(typeof(DashboardOverlayTabsEnumLocalisationMapper))] public enum DashboardOverlayTabs { Friends, @@ -27,4 +31,22 @@ namespace osu.Game.Overlays.Dashboard [Description("Currently Playing")] CurrentlyPlaying } + + public class DashboardOverlayTabsEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(DashboardOverlayTabs value) + { + switch (value) + { + case DashboardOverlayTabs.Friends: + return FriendsStrings.TitleCompact; + + case DashboardOverlayTabs.CurrentlyPlaying: + return @"Currently Playing"; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } From f2287ba0224badf454d491370d3ad49a894eeecf Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 30 Jun 2021 21:16:21 +0200 Subject: [PATCH 315/457] Localise friends online status stream control. --- .../Changelog/ChangelogUpdateStreamItem.cs | 7 ++--- .../Friends/FriendsOnlineStatusItem.cs | 6 +++-- .../Dashboard/Friends/OnlineStatus.cs | 26 +++++++++++++++++++ osu.Game/Overlays/OverlayStreamItem.cs | 7 ++--- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamItem.cs b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamItem.cs index f8e1ac0c84..cb144defbf 100644 --- a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamItem.cs +++ b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamItem.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using Humanizer; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Online.API.Requests.Responses; using osuTK.Graphics; @@ -17,11 +18,11 @@ namespace osu.Game.Overlays.Changelog Width *= 2; } - protected override string MainText => Value.DisplayName; + protected override LocalisableString MainText => Value.DisplayName; - protected override string AdditionalText => Value.LatestBuild.DisplayVersion; + protected override LocalisableString AdditionalText => Value.LatestBuild.DisplayVersion; - protected override string InfoText => Value.LatestBuild.Users > 0 ? $"{"user".ToQuantity(Value.LatestBuild.Users, "N0")} online" : null; + protected override LocalisableString InfoText => Value.LatestBuild.Users > 0 ? $"{"user".ToQuantity(Value.LatestBuild.Users, "N0")} online" : null; protected override Color4 GetBarColour(OsuColour colours) => Value.Colour; } diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs b/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs index 7e902203f8..11dcb93e6f 100644 --- a/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs +++ b/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Extensions; +using osu.Framework.Localisation; using osu.Game.Graphics; using osuTK.Graphics; @@ -14,9 +16,9 @@ namespace osu.Game.Overlays.Dashboard.Friends { } - protected override string MainText => Value.Status.ToString(); + protected override LocalisableString MainText => Value.Status.GetLocalisableDescription(); - protected override string AdditionalText => Value.Count.ToString(); + protected override LocalisableString AdditionalText => Value.Count.ToString(); protected override Color4 GetBarColour(OsuColour colours) { diff --git a/osu.Game/Overlays/Dashboard/Friends/OnlineStatus.cs b/osu.Game/Overlays/Dashboard/Friends/OnlineStatus.cs index 6f2f55a6ed..4b5a7ef066 100644 --- a/osu.Game/Overlays/Dashboard/Friends/OnlineStatus.cs +++ b/osu.Game/Overlays/Dashboard/Friends/OnlineStatus.cs @@ -1,12 +1,38 @@ // 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.Localisation; +using osu.Game.Resources.Localisation.Web; + namespace osu.Game.Overlays.Dashboard.Friends { + [LocalisableEnum(typeof(OnlineStatusEnumLocalisationMapper))] public enum OnlineStatus { All, Online, Offline } + + public class OnlineStatusEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(OnlineStatus value) + { + switch (value) + { + case OnlineStatus.All: + return SortStrings.All; + + case OnlineStatus.Online: + return UsersStrings.StatusOnline; + + case OnlineStatus.Offline: + return UsersStrings.StatusOffline; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/OverlayStreamItem.cs b/osu.Game/Overlays/OverlayStreamItem.cs index cd1391a3d8..56502ff70f 100644 --- a/osu.Game/Overlays/OverlayStreamItem.cs +++ b/osu.Game/Overlays/OverlayStreamItem.cs @@ -12,6 +12,7 @@ using osu.Framework.Allocation; using osu.Game.Graphics.Sprites; using osu.Game.Graphics; using osuTK.Graphics; +using osu.Framework.Localisation; namespace osu.Game.Overlays { @@ -88,11 +89,11 @@ namespace osu.Game.Overlays SelectedItem.BindValueChanged(_ => updateState(), true); } - protected abstract string MainText { get; } + protected abstract LocalisableString MainText { get; } - protected abstract string AdditionalText { get; } + protected abstract LocalisableString AdditionalText { get; } - protected virtual string InfoText => string.Empty; + protected virtual LocalisableString InfoText => string.Empty; protected abstract Color4 GetBarColour(OsuColour colours); From 8da6ecf6a9dd40ee211d349dd9dba6f64e1a6501 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 30 Jun 2021 21:28:07 +0200 Subject: [PATCH 316/457] Localise OverlayPanelDisplayStyle tab control. --- .../Dashboard/Friends/UserSortTabControl.cs | 25 +++++++++++++++++ .../OverlayPanelDisplayStyleControl.cs | 27 ++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs b/osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs index 3a5f65212d..dc756e2957 100644 --- a/osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs +++ b/osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs @@ -1,7 +1,10 @@ // 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.ComponentModel; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Dashboard.Friends { @@ -9,6 +12,7 @@ namespace osu.Game.Overlays.Dashboard.Friends { } + [LocalisableEnum(typeof(UserSortCriteriaEnumLocalisationMappper))] public enum UserSortCriteria { [Description(@"Recently Active")] @@ -16,4 +20,25 @@ namespace osu.Game.Overlays.Dashboard.Friends Rank, Username } + + public class UserSortCriteriaEnumLocalisationMappper : EnumLocalisationMapper + { + public override LocalisableString Map(UserSortCriteria value) + { + switch (value) + { + case UserSortCriteria.LastVisit: + return SortStrings.LastVisit; + + case UserSortCriteria.Rank: + return SortStrings.Rank; + + case UserSortCriteria.Username: + return SortStrings.Username; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs index 0ece96b56c..c2268ff43c 100644 --- a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs +++ b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs @@ -12,6 +12,9 @@ using osu.Framework.Allocation; using osuTK.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; +using System; +using osu.Game.Resources.Localisation.Web; +using osu.Framework.Extensions; namespace osu.Game.Overlays { @@ -57,7 +60,7 @@ namespace osu.Game.Overlays [Resolved] private OverlayColourProvider colourProvider { get; set; } - public LocalisableString TooltipText => $@"{Value} view"; + public LocalisableString TooltipText => Value.GetLocalisableDescription(); private readonly SpriteIcon icon; @@ -98,10 +101,32 @@ namespace osu.Game.Overlays } } + [LocalisableEnum(typeof(OverlayPanelDisplayStyleEnumLocalisationMapper))] public enum OverlayPanelDisplayStyle { Card, List, Brick } + + public class OverlayPanelDisplayStyleEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(OverlayPanelDisplayStyle value) + { + switch (value) + { + case OverlayPanelDisplayStyle.Card: + return UsersStrings.ViewModeCard; + + case OverlayPanelDisplayStyle.List: + return UsersStrings.ViewModeList; + + case OverlayPanelDisplayStyle.Brick: + return UsersStrings.ViewModeBrick; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } From 939b2baafecde49976175bd30f977618ef52c1e5 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 30 Jun 2021 22:19:04 +0200 Subject: [PATCH 317/457] Localise overlay "Scroll to top" button. --- osu.Game/Overlays/OverlayScrollContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index c5b4cc3645..ca5fc90027 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Resources.Localisation.Web; using osuTK; using osuTK.Graphics; @@ -118,7 +119,7 @@ namespace osu.Game.Overlays } }); - TooltipText = "Scroll to top"; + TooltipText = CommonStrings.ButtonsBackToTop; } [BackgroundDependencyLoader] From 583242d96d343bb342f524e3bc0275d111d945af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Jun 2021 13:05:26 +0200 Subject: [PATCH 318/457] Add osu!-styled colour picker control --- .../UserInterface/TestSceneColourPicker.cs | 82 +++++++++++++ .../UserInterfaceV2/OsuColourPicker.cs | 19 +++ .../UserInterfaceV2/OsuHSVColourPicker.cs | 109 ++++++++++++++++++ .../UserInterfaceV2/OsuHexColourPicker.cs | 57 +++++++++ 4 files changed, 267 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuColourPicker.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuHexColourPicker.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs new file mode 100644 index 0000000000..634e45e7a9 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs @@ -0,0 +1,82 @@ +// 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.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneColourPicker : OsuTestScene + { + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create pickers", () => Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = @"No OverlayColourProvider", + Font = OsuFont.Default.With(size: 40) + }, + new OsuColourPicker + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + } + }, + new ColourProvidingContainer(OverlayColourScheme.Blue) + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = @"With blue OverlayColourProvider", + Font = OsuFont.Default.With(size: 40) + }, + new OsuColourPicker + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + } + } + } + } + }); + } + + private class ColourProvidingContainer : Container + { + [Cached] + private OverlayColourProvider provider { get; } + + public ColourProvidingContainer(OverlayColourScheme colourScheme) + { + provider = new OverlayColourProvider(colourScheme); + } + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuColourPicker.cs b/osu.Game/Graphics/UserInterfaceV2/OsuColourPicker.cs new file mode 100644 index 0000000000..5394e5d0aa --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuColourPicker.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.Framework.Graphics.UserInterface; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class OsuColourPicker : ColourPicker + { + public OsuColourPicker() + { + CornerRadius = 10; + Masking = true; + } + + protected override HSVColourPicker CreateHSVColourPicker() => new OsuHSVColourPicker(); + protected override HexColourPicker CreateHexColourPicker() => new OsuHexColourPicker(); + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs b/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs new file mode 100644 index 0000000000..2a399cfaf8 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs @@ -0,0 +1,109 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class OsuHSVColourPicker : HSVColourPicker + { + protected override HueSelector CreateHueSelector() => new OsuHueSelector(); + protected override SaturationValueSelector CreateSaturationValueSelector() => new OsuSaturationValueSelector(); + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour osuColour) + { + Background.Colour = colourProvider?.Dark5 ?? osuColour.GreySeafoamDark; + + Content.Padding = new MarginPadding(10); + Content.Spacing = new Vector2(0, 10); + } + + private class OsuHueSelector : HueSelector + { + public OsuHueSelector() + { + Margin = new MarginPadding + { + Bottom = 15 + }; + + SliderBar.CornerRadius = SliderBar.Height / 2; + SliderBar.Masking = true; + } + + protected override Drawable CreateSliderNub() => new SliderNub(); + + private class SliderNub : CompositeDrawable + { + public SliderNub() + { + InternalChild = new Triangle + { + Width = 20, + Height = 15, + Anchor = Anchor.BottomCentre, + Origin = Anchor.TopCentre + }; + } + } + } + + private class OsuSaturationValueSelector : SaturationValueSelector + { + public OsuSaturationValueSelector() + { + SelectionArea.CornerRadius = 10; + SelectionArea.Masking = true; + // purposefully use hard non-AA'd masking to avoid edge artifacts. + SelectionArea.MaskingSmoothness = 0; + } + + protected override Marker CreateMarker() => new OsuMarker(); + + private class OsuMarker : Marker + { + private readonly Box previewBox; + + public OsuMarker() + { + AutoSizeAxes = Axes.Both; + + InternalChild = new CircularContainer + { + Size = new Vector2(20), + Masking = true, + BorderColour = Colour4.White, + BorderThickness = 3, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0, 1), + Radius = 3, + Colour = Colour4.Black.Opacity(0.3f) + }, + Child = previewBox = new Box + { + RelativeSizeAxes = Axes.Both + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(colour => previewBox.Colour = colour.NewValue, true); + } + } + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuHexColourPicker.cs b/osu.Game/Graphics/UserInterfaceV2/OsuHexColourPicker.cs new file mode 100644 index 0000000000..331a1b67c9 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuHexColourPicker.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 JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class OsuHexColourPicker : HexColourPicker + { + public OsuHexColourPicker() + { + Padding = new MarginPadding(20); + Spacing = 20; + } + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour osuColour) + { + Background.Colour = overlayColourProvider?.Dark6 ?? osuColour.GreySeafoamDarker; + } + + protected override TextBox CreateHexCodeTextBox() => new OsuTextBox(); + protected override ColourPreview CreateColourPreview() => new OsuColourPreview(); + + private class OsuColourPreview : ColourPreview + { + private readonly Box preview; + + public OsuColourPreview() + { + InternalChild = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = preview = new Box + { + RelativeSizeAxes = Axes.Both + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(colour => preview.Colour = colour.NewValue, true); + } + } + } +} From 30a7b034be65e7c7c83ad85518004cd3372d0583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 1 Jul 2021 00:30:43 +0200 Subject: [PATCH 319/457] Add HSV abbreviation to team-shared collection --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 62751cebb1..d2c5b1223c 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -308,6 +308,7 @@ GL GLSL HID + HSV HTML HUD ID From 719852435f1bc52381f6e4e6ca03e97b6e05460e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 15:10:19 +0900 Subject: [PATCH 320/457] Fix intermittent `PerformFromScreen` test failures due to incorrect screen sequence These tests were manually pushing the `PlayerLoader` / `Player` instances to `SongSelect`, which bypasses safeties in place which avoid the exact issue that came up in https://github.com/ppy/osu/runs/2951759236 (see `AllowSelection` flag specifically). --- .../Visual/Navigation/TestScenePerformFromScreen.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 3cedaf9d45..92152bce18 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -11,6 +11,7 @@ using osu.Game.Overlays; using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps.IO; using osuTK.Input; using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation; @@ -57,8 +58,11 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestPerformAtSongSelectFromPlayerLoader() { + AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); PushAndConfirm(() => new TestPlaySongSelect()); - PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer())); + + AddStep("Press enter", () => InputManager.Key(Key.Enter)); + AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader); AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(TestPlaySongSelect) })); AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is TestPlaySongSelect); @@ -68,8 +72,11 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestPerformAtMenuFromPlayerLoader() { + AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); PushAndConfirm(() => new TestPlaySongSelect()); - PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer())); + + AddStep("Press enter", () => InputManager.Key(Key.Enter)); + AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader); AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true)); AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is MainMenu); From 5bc970af0d5d26f655391ed43275e9869dbc15c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 16:55:33 +0900 Subject: [PATCH 321/457] Make `TestPlayer` inherit from `SoloPlayer` for more flexibility in testing --- osu.Game/Screens/Play/SoloPlayer.cs | 10 ++++++++++ osu.Game/Tests/Visual/TestPlayer.cs | 23 ++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index d0ef4131dc..0099b4c87c 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -12,6 +12,16 @@ namespace osu.Game.Screens.Play { public class SoloPlayer : SubmittingPlayer { + public SoloPlayer() + : this(null) + { + } + + protected SoloPlayer(PlayerConfiguration configuration = null) + : base(configuration) + { + } + protected override APIRequest CreateTokenRequest() { if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId)) diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 09da4db952..52eb06bdc6 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -1,14 +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 System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual @@ -16,7 +20,7 @@ namespace osu.Game.Tests.Visual /// /// A player that exposes many components that would otherwise not be available, for testing purposes. /// - public class TestPlayer : Player + public class TestPlayer : SoloPlayer { protected override bool PauseOnFocusLost { get; } @@ -35,6 +39,9 @@ namespace osu.Game.Tests.Visual public new HealthProcessor HealthProcessor => base.HealthProcessor; + public bool TokenCreationRequested { get; private set; } + public bool SubmissionRequested { get; private set; } + public new bool PauseCooldownActive => base.PauseCooldownActive; public readonly List Results = new List(); @@ -49,6 +56,20 @@ namespace osu.Game.Tests.Visual PauseOnFocusLost = pauseOnFocusLost; } + protected override bool HandleTokenRetrievalFailure(Exception exception) => false; + + protected override APIRequest CreateTokenRequest() + { + TokenCreationRequested = true; + return base.CreateTokenRequest(); + } + + protected override APIRequest CreateSubmissionRequest(Score score, long token) + { + SubmissionRequested = true; + return base.CreateSubmissionRequest(score, token); + } + protected override void PrepareReplay() { // Generally, replay generation is handled by whatever is constructing the player. From 397d2491b3e2b1f74b99bab0549c17ba96a2664d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 16:55:44 +0900 Subject: [PATCH 322/457] Update test scenes to actually cover submission logic --- .../TestScenePlayerScorePreparation.cs | 69 ----------------- .../TestScenePlayerScoreSubmission.cs | 74 +++++++++++++++++++ 2 files changed, 74 insertions(+), 69 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Gameplay/TestScenePlayerScorePreparation.cs create mode 100644 osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScorePreparation.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScorePreparation.cs deleted file mode 100644 index 8ea5f3f9ee..0000000000 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScorePreparation.cs +++ /dev/null @@ -1,69 +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.Threading.Tasks; -using NUnit.Framework; -using osu.Framework.Screens; -using osu.Framework.Testing; -using osu.Game.Rulesets; -using osu.Game.Scoring; -using osu.Game.Screens.Ranking; - -namespace osu.Game.Tests.Visual.Gameplay -{ - public class TestScenePlayerScorePreparation : OsuPlayerTestScene - { - protected override bool AllowFail => false; - - protected new PreparingPlayer Player => (PreparingPlayer)base.Player; - - [SetUpSteps] - public override void SetUpSteps() - { - base.SetUpSteps(); - - // Ensure track has actually running before attempting to seek - AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); - } - - [Test] - public void TestPreparationOnResults() - { - AddUntilStep("wait for preparation", () => Player.PreparationCompleted); - } - - [Test] - public void TestPreparationOnExit() - { - AddStep("exit", () => Player.Exit()); - AddUntilStep("wait for preparation", () => Player.PreparationCompleted); - } - - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new PreparingPlayer(); - - public class PreparingPlayer : TestPlayer - { - public bool PreparationCompleted { get; private set; } - - public bool ResultsCreated { get; private set; } - - public PreparingPlayer() - : base(true, true) - { - } - - protected override ResultsScreen CreateResults(ScoreInfo score) - { - var results = base.CreateResults(score); - ResultsCreated = true; - return results; - } - - protected override Task PrepareScoreForResultsAsync(Score score) - { - PreparationCompleted = true; - return base.PrepareScoreForResultsAsync(score); - } - } - } -} diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs new file mode 100644 index 0000000000..24f8227148 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -0,0 +1,74 @@ +// 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.Screens; +using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Online.Solo; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Ranking; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestScenePlayerScoreSubmission : OsuPlayerTestScene + { + protected override bool AllowFail => false; + + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + protected override TestPlayer CreatePlayer(Ruleset ruleset) + { + SelectedMods.Value = new[] { ruleset.GetAllMods().OfType().First() }; + return new TestPlayer(false); + } + + [SetUpSteps] + public override void SetUpSteps() + { + AddStep("Prepare test API", () => + { + dummyAPI.HandleRequest = request => + { + switch (request) + { + case CreateSoloScoreRequest tokenRequest: + tokenRequest.TriggerSuccess(new APIScoreToken { ID = 1234 }); + return true; + } + + return false; + }; + }); + + base.SetUpSteps(); + + // Ensure track has actually running before attempting to seek + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + } + + [Test] + public void TestSubmissionOnResults() + { + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); + + AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); + + AddUntilStep("wait for submission", () => Player.SubmissionRequested); + } + + [Test] + public void TestSubmissionOnExit() + { + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + AddStep("exit", () => Player.Exit()); + AddUntilStep("wait for submission", () => Player.SubmissionRequested); + } + } +} From 04b874bb009a85b67c5783f30aa4a6f2bfb6862a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 17:02:33 +0900 Subject: [PATCH 323/457] Add flow for submitting score on exiting `SubmittingPlayer` --- osu.Game/Screens/Play/SubmittingPlayer.cs | 60 ++++++++++++++--------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 2ee116a6a3..bbb6a21f39 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -27,6 +27,8 @@ namespace osu.Game.Screens.Play [Resolved] private IAPIProvider api { get; set; } + private TaskCompletionSource scoreSubmissionSource; + protected SubmittingPlayer(PlayerConfiguration configuration = null) : base(configuration) { @@ -102,36 +104,18 @@ namespace osu.Game.Screens.Play /// Whether gameplay should be immediately exited as a result. Returning false allows the gameplay session to continue. Defaults to true. protected virtual bool HandleTokenRetrievalFailure(Exception exception) => true; - public override bool OnExiting(IScreen next) - { - return base.OnExiting(next); - } - protected override async Task PrepareScoreForResultsAsync(Score score) { await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); - // token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure). - if (token == null) - return; + await submitScore(score).ConfigureAwait(false); + } - var tcs = new TaskCompletionSource(); - var request = CreateSubmissionRequest(score, token.Value); + public override bool OnExiting(IScreen next) + { + submitScore(Score); - request.Success += s => - { - score.ScoreInfo.OnlineScoreID = s.ID; - tcs.SetResult(true); - }; - - request.Failure += e => - { - Logger.Error(e, "Failed to submit score"); - tcs.SetResult(false); - }; - - api.Queue(request); - await tcs.Task.ConfigureAwait(false); + return base.OnExiting(next); } /// @@ -148,5 +132,33 @@ namespace osu.Game.Screens.Play /// The score to be submitted. /// The submission token. protected abstract APIRequest CreateSubmissionRequest(Score score, long token); + + private Task submitScore(Score score) + { + // token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure). + if (token == null) + return Task.CompletedTask; + + if (scoreSubmissionSource != null) + return scoreSubmissionSource.Task; + + scoreSubmissionSource = new TaskCompletionSource(); + var request = CreateSubmissionRequest(score, token.Value); + + request.Success += s => + { + score.ScoreInfo.OnlineScoreID = s.ID; + scoreSubmissionSource.SetResult(true); + }; + + request.Failure += e => + { + Logger.Error(e, "Failed to submit score"); + scoreSubmissionSource.SetResult(false); + }; + + api.Queue(request); + return scoreSubmissionSource.Task; + } } } From 6e8d4e382ebf3597181cee171b20ed175dab9fc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 17:20:40 +0900 Subject: [PATCH 324/457] Add test coverage of token failure scenarios --- .../TestScenePlayerScoreSubmission.cs | 96 +++++++++++++------ 1 file changed, 67 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 24f8227148..4088fc855d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -1,10 +1,10 @@ // 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 NUnit.Framework; using osu.Framework.Screens; -using osu.Framework.Testing; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Online.Solo; @@ -21,14 +21,74 @@ namespace osu.Game.Tests.Visual.Gameplay private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + protected override bool HasCustomSteps => true; + protected override TestPlayer CreatePlayer(Ruleset ruleset) { SelectedMods.Value = new[] { ruleset.GetAllMods().OfType().First() }; return new TestPlayer(false); } - [SetUpSteps] - public override void SetUpSteps() + [Test] + public void TestNoSubmissionOnResultsWithNoToken() + { + prepareTokenResponse(false); + + CreateTest(() => { }); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); + + AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); + + AddAssert("ensure no submission", () => !Player.SubmissionRequested); + } + + [Test] + public void TestSubmissionOnResults() + { + prepareTokenResponse(true); + + CreateTest(() => { }); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); + + AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); + + AddAssert("ensure submission", () => Player.SubmissionRequested); + } + + [Test] + public void TestNoSubmissionOnExitWithNoToken() + { + prepareTokenResponse(false); + + CreateTest(() => { }); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + AddStep("exit", () => Player.Exit()); + + AddAssert("ensure no submission", () => !Player.SubmissionRequested); + } + + [Test] + public void TestSubmissionOnExit() + { + prepareTokenResponse(true); + + CreateTest(() => { }); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + AddStep("exit", () => Player.Exit()); + AddUntilStep("wait for submission", () => Player.SubmissionRequested); + } + + private void prepareTokenResponse(bool validToken) { AddStep("Prepare test API", () => { @@ -37,38 +97,16 @@ namespace osu.Game.Tests.Visual.Gameplay switch (request) { case CreateSoloScoreRequest tokenRequest: - tokenRequest.TriggerSuccess(new APIScoreToken { ID = 1234 }); + if (validToken) + tokenRequest.TriggerSuccess(new APIScoreToken { ID = 1234 }); + else + tokenRequest.TriggerFailure(new Exception()); return true; } return false; }; }); - - base.SetUpSteps(); - - // Ensure track has actually running before attempting to seek - AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); - } - - [Test] - public void TestSubmissionOnResults() - { - AddUntilStep("wait for token request", () => Player.TokenCreationRequested); - - AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); - - AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); - - AddUntilStep("wait for submission", () => Player.SubmissionRequested); - } - - [Test] - public void TestSubmissionOnExit() - { - AddUntilStep("wait for token request", () => Player.TokenCreationRequested); - AddStep("exit", () => Player.Exit()); - AddUntilStep("wait for submission", () => Player.SubmissionRequested); } } } From b00ee67895cba2bdbe64c49c9b698f87413bb2fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 17:25:47 +0900 Subject: [PATCH 325/457] Remove excess whitespace --- osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs index ce4ec27225..056d4ad6f7 100644 --- a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs +++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Dashboard switch (value) { case DashboardOverlayTabs.Friends: - return FriendsStrings.TitleCompact; + return FriendsStrings.TitleCompact; case DashboardOverlayTabs.CurrentlyPlaying: return @"Currently Playing"; From 74c63e15bef6d50aa8e4a43a44a7f8a449d0984c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 17:38:28 +0900 Subject: [PATCH 326/457] Mark score failed on fail and exit --- .../TestScenePlayerScoreSubmission.cs | 38 ++++++++++++++----- osu.Game/Screens/Play/Player.cs | 5 +++ osu.Game/Screens/Play/SubmittingPlayer.cs | 4 +- osu.Game/Tests/Visual/TestPlayer.cs | 5 ++- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 4088fc855d..ada50fbcf4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -23,9 +23,14 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool HasCustomSteps => true; + private bool allowFail; + protected override TestPlayer CreatePlayer(Ruleset ruleset) { - SelectedMods.Value = new[] { ruleset.GetAllMods().OfType().First() }; + SelectedMods.Value = !allowFail + ? new[] { ruleset.GetAllMods().OfType().First() } + : Array.Empty(); + return new TestPlayer(false); } @@ -34,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(false); - CreateTest(() => { }); + CreateTest(() => allowFail = false); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -43,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); - AddAssert("ensure no submission", () => !Player.SubmissionRequested); + AddAssert("ensure no submission", () => Player.SubmittedScore == null); } [Test] @@ -51,7 +56,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(true); - CreateTest(() => { }); + CreateTest(() => allowFail = false); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -59,8 +64,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); - - AddAssert("ensure submission", () => Player.SubmissionRequested); + AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true); } [Test] @@ -68,12 +72,26 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(false); - CreateTest(() => { }); + CreateTest(() => allowFail = false); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + AddStep("exit", () => Player.Exit()); + AddAssert("ensure no submission", () => Player.SubmittedScore == null); + } + + [Test] + public void TestSubmissionOnFail() + { + prepareTokenResponse(true); + + CreateTest(() => allowFail = true); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + AddUntilStep("wait for fail", () => Player.HasFailed); AddStep("exit", () => Player.Exit()); - AddAssert("ensure no submission", () => !Player.SubmissionRequested); + AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false); } [Test] @@ -81,11 +99,11 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(true); - CreateTest(() => { }); + CreateTest(() => allowFail = false); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); AddStep("exit", () => Player.Exit()); - AddUntilStep("wait for submission", () => Player.SubmissionRequested); + AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false); } private void prepareTokenResponse(bool validToken) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 58f60d14cf..97854ee12f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -768,6 +768,7 @@ namespace osu.Game.Screens.Play return false; HasFailed = true; + Score.ScoreInfo.Passed = false; // There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer) // could process an extra frame after the GameplayClock is stopped. @@ -950,6 +951,10 @@ namespace osu.Game.Screens.Play { screenSuspension?.Expire(); + // if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap. + if (prepareScoreForDisplayTask == null) + Score.ScoreInfo.Passed = false; + // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. // To resolve test failures, forcefully end playing synchronously when this screen exits. // Todo: Replace this with a more permanent solution once osu-framework has a synchronous cleanup method. diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index bbb6a21f39..7c5a06707d 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -113,9 +113,11 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { + var exiting = base.OnExiting(next); + submitScore(Score); - return base.OnExiting(next); + return exiting; } /// diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 52eb06bdc6..5e5f20b307 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -40,7 +40,8 @@ namespace osu.Game.Tests.Visual public new HealthProcessor HealthProcessor => base.HealthProcessor; public bool TokenCreationRequested { get; private set; } - public bool SubmissionRequested { get; private set; } + + public Score SubmittedScore { get; private set; } public new bool PauseCooldownActive => base.PauseCooldownActive; @@ -66,7 +67,7 @@ namespace osu.Game.Tests.Visual protected override APIRequest CreateSubmissionRequest(Score score, long token) { - SubmissionRequested = true; + SubmittedScore = score; return base.CreateSubmissionRequest(score, token); } From a6323b7d87fba2a0adad4a750a9155fcde16bf49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 17:54:59 +0900 Subject: [PATCH 327/457] Use `APIException` --- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index ada50fbcf4..27f9fc85cf 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Gameplay if (validToken) tokenRequest.TriggerSuccess(new APIScoreToken { ID = 1234 }); else - tokenRequest.TriggerFailure(new Exception()); + tokenRequest.TriggerFailure(new APIException("something went wrong!", null)); return true; } From 3816c486d5fb5f13c226f8b1cd72f457cef4a51c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 19:03:55 +0900 Subject: [PATCH 328/457] Guard against a potential startup crash if user's preferred ruleset has a compatibility issue Resolves this issue seen at https://github.com/ppy/osu/issues/13722#issuecomment-872088071. --- osu.Game/OsuGame.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 32136b8789..14309e2296 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -223,7 +223,20 @@ namespace osu.Game // bind config int to database RulesetInfo configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); - Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); + + var preferredRuleset = RulesetStore.GetRuleset(configRuleset.Value); + + try + { + Ruleset.Value = preferredRuleset ?? RulesetStore.AvailableRulesets.First(); + } + catch (Exception e) + { + // on startup, a ruleset may be selected which has compatibility issues. + Logger.Error(e, $@"Failed to switch to preferred ruleset {preferredRuleset}."); + Ruleset.Value = RulesetStore.AvailableRulesets.First(); + } + Ruleset.ValueChanged += r => configRuleset.Value = r.NewValue.ID ?? 0; // bind config int to database SkinInfo From a78d1b4c2ee283e8e83f9013bc4bacd762bbacea Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Jul 2021 19:37:21 +0900 Subject: [PATCH 329/457] Update SR colours to match osu-web --- osu.Game/Graphics/OsuColour.cs | 4 ++-- osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 15967c37c2..a44c28eaa6 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -32,10 +32,10 @@ namespace osu.Game.Graphics return Pink; case DifficultyRating.Expert: - return useLighterColour ? PurpleLight : Purple; + return PurpleLight; case DifficultyRating.ExpertPlus: - return useLighterColour ? Gray9 : Gray0; + return useLighterColour ? Gray9 : Color4Extensions.FromHex("#121415"); } } diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index 7aba699216..e59a0de316 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -4,9 +4,7 @@ using System.Globalization; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; @@ -111,12 +109,9 @@ namespace osu.Game.Screens.Ranking.Expanded var rating = Current.Value.DifficultyRating; - background.Colour = rating == DifficultyRating.ExpertPlus - ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) - : (ColourInfo)colours.ForDifficultyRating(rating); + background.Colour = colours.ForDifficultyRating(rating, true); textFlow.Clear(); - textFlow.AddText($"{wholePart}", s => { s.Colour = Color4.Black; From 0522500a572a8125529ad399cc628fc558b407b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 19:45:17 +0900 Subject: [PATCH 330/457] Fix a couple of merge oversights --- osu.Game/Overlays/BeatmapListingOverlay.cs | 6 +++++- osu.Game/Screens/Select/BeatmapDetails.cs | 3 --- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 4552c8d11b..6861d17f26 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -89,7 +89,11 @@ namespace osu.Game.Overlays }; } - public void ShowWithSearch(string query) => filterControl.Search(query); + public void ShowWithSearch(string query) + { + filterControl.Search(query); + Show(); + } protected override BeatmapListingHeader CreateHeader() => new BeatmapListingHeader(); diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 38478ce461..d7dc73cb37 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -149,9 +149,6 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - description = new MetadataSection(MetadataType.Description), - source = new MetadataSection(MetadataType.Source), - tags = new MetadataSection(MetadataType.Tags), new OsuSpriteText { Text = "Points of Failure", From 4c1b8bc42726a9cd352a57388ffdd3c15dee99ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 20:23:12 +0900 Subject: [PATCH 331/457] Update disclaimer --- osu.Game/Screens/Menu/Disclaimer.cs | 63 ++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 72eb9c7c0c..78dda05a19 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.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.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -36,6 +37,8 @@ namespace osu.Game.Screens.Menu private readonly Bindable currentUser = new Bindable(); private FillFlowContainer fill; + private readonly List expendableText = new List(); + public Disclaimer(OsuScreen nextScreen = null) { this.nextScreen = nextScreen; @@ -54,7 +57,7 @@ namespace osu.Game.Screens.Menu { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.Solid.Flask, + Icon = OsuIcon.Logo, Size = new Vector2(icon_size), Y = icon_y, }, @@ -76,37 +79,53 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Spacing = new Vector2(0, 2), - LayoutDuration = 2000, - LayoutEasing = Easing.OutQuint - }, - supportFlow = new LinkFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - TextAnchor = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Alpha = 0, - Spacing = new Vector2(0, 2), }, } - } + }, + supportFlow = new LinkFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Padding = new MarginPadding(20), + Alpha = 0, + Spacing = new Vector2(0, 2), + }, }; - textFlow.AddText("This project is an ongoing ", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Light)); - textFlow.AddText("work in progress", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.SemiBold)); + textFlow.AddText("This is osu!", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular)); + + expendableText.AddRange(textFlow.AddText("(lazer)", t => + { + t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular); + t.Colour = colours.PinkLight; + })); textFlow.NewParagraph(); - static void format(SpriteText t) => t.Font = OsuFont.GetFont(size: 15, weight: FontWeight.SemiBold); + textFlow.AddText("the next ", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular)); + textFlow.AddText("major update", t => + { + t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold); + t.Colour = colours.Pink; + }); + expendableText.AddRange(textFlow.AddText(" coming to osu!", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular))); + textFlow.AddText(".", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular)); - textFlow.AddParagraph(getRandomTip(), t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold)); + textFlow.NewParagraph(); + + textFlow.AddParagraph("Today's Tip: ", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold)); + textFlow.AddText(getRandomTip(), t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular)); textFlow.NewParagraph(); textFlow.NewParagraph(); iconColour = colours.Yellow; + static void format(SpriteText t) => t.Font = OsuFont.GetFont(size: 15, weight: FontWeight.SemiBold); + // manually transfer the user once, but only do the final bind in LoadComplete to avoid thread woes (API scheduler could run while this screen is still loading). // the manual transfer is here to ensure all text content is loaded ahead of time as this is very early in the game load process and we want to avoid stutters. currentUser.Value = api.LocalUser.Value; @@ -122,7 +141,7 @@ namespace osu.Game.Screens.Menu { supportFlow.AddText("Consider becoming an ", format); supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", creationParameters: format); - supportFlow.AddText(" to help support the game", format); + supportFlow.AddText(" to help support osu!'s development", format); } heart = supportFlow.AddIcon(FontAwesome.Solid.Heart, t => @@ -169,7 +188,11 @@ namespace osu.Game.Screens.Menu .MoveToY(icon_y, 160, Easing.InQuart) .FadeColour(Color4.White, 160); - fill.Delay(520 + 160).MoveToOffset(new Vector2(0, 15), 160, Easing.OutQuart); + using (BeginDelayedSequence(520 + 160)) + { + fill.MoveToOffset(new Vector2(0, 15), 160, Easing.OutQuart); + Schedule(() => expendableText.ForEach(t => t.Expire())); + } } supportFlow.FadeOut().Delay(2000).FadeIn(500); From 4c95af4b16a961743639de95451104cd604b8de8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Jul 2021 20:35:31 +0900 Subject: [PATCH 332/457] Add star rating range display --- .../TestSceneStarRatingRangeDisplay.cs | 40 ++++++++ .../Components/StarRatingRangeDisplay.cs | 93 +++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs create mode 100644 osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs new file mode 100644 index 0000000000..cdeafdc9a3 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs @@ -0,0 +1,40 @@ +// 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.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Tests.Visual.OnlinePlay; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneStarRatingRangeDisplay : OnlinePlayTestScene + { + [SetUp] + public new void Setup() => Schedule(() => + { + SelectedRoom.Value = new Room(); + + Child = new StarRatingRangeDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + }); + + [Test] + public void TestRange([Values(0, 2, 3, 4, 6, 7)] double min, [Values(0, 2, 3, 4, 6, 7)] double max) + { + AddStep("set playlist", () => + { + SelectedRoom.Value.Playlist.AddRange(new[] + { + new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarDifficulty = min } } }, + new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarDifficulty = max } } }, + }); + }); + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs new file mode 100644 index 0000000000..b2e35d7020 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -0,0 +1,93 @@ +// 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.Specialized; +using System.Linq; +using osu.Framework.Allocation; +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.Screens.Ranking.Expanded; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.Components +{ + public class StarRatingRangeDisplay : OnlinePlayComposite + { + [Resolved] + private OsuColour colours { get; set; } + + private StarRatingDisplay minDisplay; + private Drawable minBackground; + private StarRatingDisplay maxDisplay; + private Drawable maxBackground; + + public StarRatingRangeDisplay() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 1, + Children = new[] + { + minBackground = new Box + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.5f), + }, + maxBackground = new Box + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.5f), + }, + } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + minDisplay = new StarRatingDisplay(default), + maxDisplay = new StarRatingDisplay(default) + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Playlist.BindCollectionChanged(updateRange, true); + } + + private void updateRange(object sender, NotifyCollectionChangedEventArgs e) + { + var orderedDifficulties = Playlist.Select(p => p.Beatmap.Value).OrderBy(b => b.StarDifficulty).ToArray(); + + StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarDifficulty : 0, 0); + StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarDifficulty : 0, 0); + + minDisplay.Current.Value = minDifficulty; + maxDisplay.Current.Value = maxDifficulty; + + minBackground.Colour = colours.ForDifficultyRating(minDifficulty.DifficultyRating, true); + maxBackground.Colour = colours.ForDifficultyRating(maxDifficulty.DifficultyRating, true); + } + } +} From 68c5e6a4311080716624c1114e1b63a90c2dd74f Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 1 Jul 2021 20:41:30 +0900 Subject: [PATCH 333/457] Add audio feedback to changing volume --- osu.Game/Overlays/Volume/VolumeMeter.cs | 35 ++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index a15076581e..2251be03c6 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -4,6 +4,8 @@ using System; using System.Globalization; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -36,6 +38,10 @@ namespace osu.Game.Overlays.Volume private OsuSpriteText text; private BufferedContainer maxGlow; + private bool firstUpdate = true; + private Sample sample; + private double sampleLastPlaybackTime; + public VolumeMeter(string name, float circleSize, Color4 meterColour) { this.circleSize = circleSize; @@ -46,8 +52,11 @@ namespace osu.Game.Overlays.Volume } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, AudioManager audio) { + sample = audio.Samples.Get(@"UI/notch-tick"); + sampleLastPlaybackTime = Time.Current; + Color4 backgroundColour = colours.Gray1; CircularProgress bgProgress; @@ -202,6 +211,7 @@ namespace osu.Game.Overlays.Volume get => displayVolume; set { + bool displayVolumeChanged = Math.Round(displayVolume * 100) != Math.Round(value * 100); displayVolume = value; if (displayVolume >= 0.995f) @@ -217,6 +227,29 @@ namespace osu.Game.Overlays.Volume volumeCircle.Current.Value = displayVolume * 0.75f; volumeCircleGlow.Current.Value = displayVolume * 0.75f; + + Schedule(() => + { + const int sfx_debounce_time = 30; + + if (firstUpdate || + !displayVolumeChanged || + Time.Current - sampleLastPlaybackTime <= sfx_debounce_time) + { + firstUpdate = false; + return; + } + + var channel = sample.Play(); + + channel.Frequency.Value = 1 + displayVolume * 0.1f + RNG.NextDouble(0.02f); + if (displayVolume < 0.005f) + channel.Frequency.Value -= 0.5f; + else if (displayVolume > 0.995f) + channel.Frequency.Value -= 0.5f; + + sampleLastPlaybackTime = Time.Current; + }); } } From dad28b28265ddec8f6c478041b8aee4317f43c22 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 1 Jul 2021 20:45:29 +0900 Subject: [PATCH 334/457] Update OsuSliderBar to use new notch-tick sample and tweak pitch ramping --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index ae16169123..1433f0c38b 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Framework.Utils; namespace osu.Game.Graphics.UserInterface { @@ -99,7 +100,7 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) { - sample = audio.Samples.Get(@"UI/sliderbar-notch"); + sample = audio.Samples.Get(@"UI/notch-tick"); AccentColour = colours.Pink; } @@ -149,7 +150,7 @@ namespace osu.Game.Graphics.UserInterface private void playSample(T value) { - if (Clock == null || Clock.CurrentTime - lastSampleTime <= 50) + if (Clock == null || Clock.CurrentTime - lastSampleTime <= 30) return; if (value.Equals(lastSampleValue)) @@ -160,11 +161,11 @@ namespace osu.Game.Graphics.UserInterface var channel = sample.Play(); - channel.Frequency.Value = 1 + NormalizedValue * 0.2f; + channel.Frequency.Value = 1 + NormalizedValue * 0.2f + RNG.NextDouble(0.02f); if (NormalizedValue == 0) - channel.Frequency.Value -= 0.4f; + channel.Frequency.Value -= 0.5f; else if (NormalizedValue == 1) - channel.Frequency.Value += 0.4f; + channel.Frequency.Value -= 0.5f; } private void updateTooltipText(T value) From f1c11243e92be087d5977bbd5e8a10d2a19a42b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 22:06:59 +0900 Subject: [PATCH 335/457] 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 481ddc118f..b2b48f5de8 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 589afb86be..42eb2ad1d8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index a8bf0e4ab2..67993049f2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 8ab9ea992635622ed9e3b043e23ca7934f418ad3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:03 +0000 Subject: [PATCH 336/457] Bump Realm from 10.2.0 to 10.2.1 Bumps [Realm](https://github.com/realm/realm-dotnet) from 10.2.0 to 10.2.1. - [Release notes](https://github.com/realm/realm-dotnet/releases) - [Changelog](https://github.com/realm/realm-dotnet/blob/master/CHANGELOG.md) - [Commits](https://github.com/realm/realm-dotnet/compare/10.2.0...10.2.1) --- updated-dependencies: - dependency-name: Realm dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- 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 481ddc118f..2e388b8626 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -56,6 +56,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 589afb86be..24779fc4d1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index a8bf0e4ab2..33ea2261ca 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -99,6 +99,6 @@ - + From c976854e2449b8f659cebe2be22e2ec9da0bc45c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:08 +0000 Subject: [PATCH 337/457] Bump Sentry from 3.4.0 to 3.6.0 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 3.4.0 to 3.6.0. - [Release notes](https://github.com/getsentry/sentry-dotnet/releases) - [Changelog](https://github.com/getsentry/sentry-dotnet/blob/main/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-dotnet/compare/3.4.0...3.6.0) --- updated-dependencies: - dependency-name: Sentry dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[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 589afb86be..0ab6bb9ba6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -38,7 +38,7 @@ - + From e5e468e38787da508cd291a205e7ce406c34be3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:11 +0000 Subject: [PATCH 338/457] Bump SharpCompress from 0.28.2 to 0.28.3 Bumps [SharpCompress](https://github.com/adamhathcock/sharpcompress) from 0.28.2 to 0.28.3. - [Release notes](https://github.com/adamhathcock/sharpcompress/releases) - [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.28.2...0.28.3) --- updated-dependencies: - dependency-name: SharpCompress dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 589afb86be..f857d155ec 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -39,7 +39,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index a8bf0e4ab2..f1ee770688 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -94,7 +94,7 @@ - + From 71867337b6c4e00310d3232b3ca88ccaebc9f3d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:15 +0000 Subject: [PATCH 339/457] Bump HtmlAgilityPack from 1.11.33 to 1.11.34 Bumps [HtmlAgilityPack](https://github.com/zzzprojects/html-agility-pack) from 1.11.33 to 1.11.34. - [Release notes](https://github.com/zzzprojects/html-agility-pack/releases) - [Commits](https://github.com/zzzprojects/html-agility-pack/compare/v1.11.33...v1.11.34) --- updated-dependencies: - dependency-name: HtmlAgilityPack dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[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 589afb86be..f5838f5109 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,7 +20,7 @@ - + From 0d3de488de65aeb6310d924fadc67e8433b0151c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:17 +0000 Subject: [PATCH 340/457] Bump NUnit3TestAdapter from 3.17.0 to 4.0.0 Bumps [NUnit3TestAdapter](https://github.com/nunit/nunit3-vs-adapter) from 3.17.0 to 4.0.0. - [Release notes](https://github.com/nunit/nunit3-vs-adapter/releases) - [Commits](https://github.com/nunit/nunit3-vs-adapter/compare/V3.17...V4.0.0) --- updated-dependencies: - dependency-name: NUnit3TestAdapter dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index 5eb5efa54c..3dd6be7307 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index d7c116411a..0c4bfe0ed7 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index 89b551286b..bb0a487274 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index d7c116411a..0c4bfe0ed7 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 7a74563b2b..da8a0540f4 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -9,7 +9,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 83d0744588..484da8e22e 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index b2a0912d19..6df555617b 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 1efd19f49d..68be34d153 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 8fb167ba10..532fdc5cb0 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 35d3c7f202..161e248d96 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 2084be765a..ba096abd36 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe From b320528eb59b72b50a53bf33e1706f7c6a428525 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:22 +0000 Subject: [PATCH 341/457] Bump Humanizer from 2.10.1 to 2.11.10 Bumps [Humanizer](https://github.com/Humanizr/Humanizer) from 2.10.1 to 2.11.10. - [Release notes](https://github.com/Humanizr/Humanizer/releases) - [Changelog](https://github.com/Humanizr/Humanizer/blob/main/release_notes.md) - [Commits](https://github.com/Humanizr/Humanizer/compare/v2.10.1...v2.11.10) --- updated-dependencies: - dependency-name: Humanizer dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 589afb86be..74ed0b8299 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,7 +21,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index a8bf0e4ab2..8226d23e0a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -89,7 +89,7 @@ - + From e7c7b8512ac012bb77852603c079893a9471a147 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:25 +0000 Subject: [PATCH 342/457] Bump Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson Bumps [Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson](https://github.com/dotnet/aspnetcore) from 5.0.6 to 5.0.7. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.6...v5.0.7) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[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 589afb86be..2709f25bb4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -25,7 +25,7 @@ - + From fb2d08b9ee2a18501464c9c3583c71a349e969d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:29 +0000 Subject: [PATCH 343/457] Bump ppy.LocalisationAnalyser from 2021.608.0 to 2021.614.0 Bumps [ppy.LocalisationAnalyser](https://github.com/ppy/osu-localisation-analyser) from 2021.608.0 to 2021.614.0. - [Release notes](https://github.com/ppy/osu-localisation-analyser/releases) - [Commits](https://github.com/ppy/osu-localisation-analyser/compare/2021.608.0...2021.614.0) --- updated-dependencies: - dependency-name: ppy.LocalisationAnalyser dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[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 589afb86be..b8502216f3 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -31,7 +31,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From f6234864cc8cfe11d678d2a4ed3226003ff06ff9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:33 +0000 Subject: [PATCH 344/457] Bump Microsoft.AspNetCore.SignalR.Client from 5.0.6 to 5.0.7 Bumps [Microsoft.AspNetCore.SignalR.Client](https://github.com/dotnet/aspnetcore) from 5.0.6 to 5.0.7. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.6...v5.0.7) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.SignalR.Client dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[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 589afb86be..44e7a00839 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + From d0ae946cb6d70a81e4b4b453db31fc480fa16f2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:38 +0000 Subject: [PATCH 345/457] Bump Microsoft.AspNetCore.SignalR.Protocols.MessagePack Bumps [Microsoft.AspNetCore.SignalR.Protocols.MessagePack](https://github.com/dotnet/aspnetcore) from 5.0.6 to 5.0.7. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.6...v5.0.7) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.SignalR.Protocols.MessagePack dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[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 589afb86be..470f7f48d9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + From 9b0fa6d3fcb66536b1b2b8fe62c54aa005cc773f Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Thu, 1 Jul 2021 15:38:38 -0700 Subject: [PATCH 346/457] Make flipping reflect across the axes of the selection box --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 23f36ffe5b..7a35c8600d 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -127,6 +127,7 @@ namespace osu.Game.Skinning.Editor public override bool HandleFlip(Direction direction) { var selectionQuad = getSelectionQuad(); + Vector2 scaleFactor = direction == Direction.Horizontal ? new Vector2(-1, 1) : new Vector2(1, -1); foreach (var b in SelectedBlueprints) { @@ -136,10 +137,8 @@ namespace osu.Game.Skinning.Editor updateDrawablePosition(drawableItem, flippedPosition); - drawableItem.Scale *= new Vector2( - direction == Direction.Horizontal ? -1 : 1, - direction == Direction.Vertical ? -1 : 1 - ); + drawableItem.Scale *= scaleFactor; + drawableItem.Rotation -= drawableItem.Rotation % 180 * 2; } return true; From 083e463afd8065f19d082e9e158ab6ad31afdae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 2 Jul 2021 01:02:36 +0200 Subject: [PATCH 347/457] Use alternative hue slider nub design --- .../UserInterfaceV2/OsuHSVColourPicker.cs | 68 ++++++++++++------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs b/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs index 2a399cfaf8..06056f239b 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; @@ -15,6 +16,10 @@ namespace osu.Game.Graphics.UserInterfaceV2 { public class OsuHSVColourPicker : HSVColourPicker { + private const float spacing = 10; + private const float corner_radius = 10; + private const float control_border_thickness = 3; + protected override HueSelector CreateHueSelector() => new OsuHueSelector(); protected override SaturationValueSelector CreateSaturationValueSelector() => new OsuSaturationValueSelector(); @@ -23,37 +28,58 @@ namespace osu.Game.Graphics.UserInterfaceV2 { Background.Colour = colourProvider?.Dark5 ?? osuColour.GreySeafoamDark; - Content.Padding = new MarginPadding(10); - Content.Spacing = new Vector2(0, 10); + Content.Padding = new MarginPadding(spacing); + Content.Spacing = new Vector2(0, spacing); } + private static EdgeEffectParameters createShadowParameters() => new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0, 1), + Radius = 3, + Colour = Colour4.Black.Opacity(0.3f) + }; + private class OsuHueSelector : HueSelector { public OsuHueSelector() { - Margin = new MarginPadding - { - Bottom = 15 - }; - - SliderBar.CornerRadius = SliderBar.Height / 2; + SliderBar.CornerRadius = corner_radius; SliderBar.Masking = true; } - protected override Drawable CreateSliderNub() => new SliderNub(); + protected override Drawable CreateSliderNub() => new SliderNub(this); private class SliderNub : CompositeDrawable { - public SliderNub() + private readonly Bindable hue; + private readonly Box fill; + + public SliderNub(OsuHueSelector osuHueSelector) { - InternalChild = new Triangle + hue = osuHueSelector.Hue.GetBoundCopy(); + + InternalChild = new CircularContainer { - Width = 20, - Height = 15, - Anchor = Anchor.BottomCentre, - Origin = Anchor.TopCentre + Height = 35, + Width = 10, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Masking = true, + BorderColour = Colour4.White, + BorderThickness = control_border_thickness, + EdgeEffect = createShadowParameters(), + Child = fill = new Box + { + RelativeSizeAxes = Axes.Both + } }; } + + protected override void LoadComplete() + { + hue.BindValueChanged(h => fill.Colour = Colour4.FromHSV(h.NewValue, 1, 1), true); + } } } @@ -61,7 +87,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 { public OsuSaturationValueSelector() { - SelectionArea.CornerRadius = 10; + SelectionArea.CornerRadius = corner_radius; SelectionArea.Masking = true; // purposefully use hard non-AA'd masking to avoid edge artifacts. SelectionArea.MaskingSmoothness = 0; @@ -82,14 +108,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 Size = new Vector2(20), Masking = true, BorderColour = Colour4.White, - BorderThickness = 3, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(0, 1), - Radius = 3, - Colour = Colour4.Black.Opacity(0.3f) - }, + BorderThickness = control_border_thickness, + EdgeEffect = createShadowParameters(), Child = previewBox = new Box { RelativeSizeAxes = Axes.Both From a1eaf396453b770712b645d096e52e90330f571f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 12:15:16 +0900 Subject: [PATCH 348/457] Slightly change wording to hopefully read better --- osu.Game/Screens/Menu/Disclaimer.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 78dda05a19..6ae7142814 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -95,9 +95,9 @@ namespace osu.Game.Screens.Menu }, }; - textFlow.AddText("This is osu!", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular)); + textFlow.AddText("this is osu!", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular)); - expendableText.AddRange(textFlow.AddText("(lazer)", t => + expendableText.AddRange(textFlow.AddText("lazer", t => { t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular); t.Colour = colours.PinkLight; @@ -116,7 +116,7 @@ namespace osu.Game.Screens.Menu textFlow.NewParagraph(); - textFlow.AddParagraph("Today's Tip: ", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold)); + textFlow.AddParagraph("today's tip: ", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold)); textFlow.AddText(getRandomTip(), t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular)); textFlow.NewParagraph(); @@ -191,7 +191,11 @@ namespace osu.Game.Screens.Menu using (BeginDelayedSequence(520 + 160)) { fill.MoveToOffset(new Vector2(0, 15), 160, Easing.OutQuart); - Schedule(() => expendableText.ForEach(t => t.Expire())); + Schedule(() => expendableText.ForEach(t => + { + t.FadeOut(100); + t.ScaleTo(new Vector2(0, 1), 100, Easing.OutQuart); + })); } } From f0727a63bebe1c581db6da303f34c46dd043aa57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 12:16:45 +0900 Subject: [PATCH 349/457] Reduce usable width --- osu.Game/Screens/Menu/Disclaimer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 6ae7142814..59f0f26b94 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Menu { textFlow = new LinkFlowContainer { - RelativeSizeAxes = Axes.X, + Width = 680, AutoSizeAxes = Axes.Y, TextAnchor = Anchor.TopCentre, Anchor = Anchor.TopCentre, From 62c125d7a2d76cab5f756a4b57336158dea4a31a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 12:21:28 +0900 Subject: [PATCH 350/457] Tidy up font formatting methods --- osu.Game/Screens/Menu/Disclaimer.cs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 59f0f26b94..b0a2fad813 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -103,29 +103,31 @@ namespace osu.Game.Screens.Menu t.Colour = colours.PinkLight; })); + static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular); + static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold); + textFlow.NewParagraph(); - textFlow.AddText("the next ", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular)); + textFlow.AddText("the next ", formatRegular); textFlow.AddText("major update", t => { t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold); t.Colour = colours.Pink; }); - expendableText.AddRange(textFlow.AddText(" coming to osu!", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular))); - textFlow.AddText(".", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular)); + expendableText.AddRange(textFlow.AddText(" coming to osu!", formatRegular)); + textFlow.AddText(".", formatRegular); + textFlow.NewParagraph(); textFlow.NewParagraph(); - textFlow.AddParagraph("today's tip: ", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold)); - textFlow.AddText(getRandomTip(), t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular)); + textFlow.AddParagraph("today's tip:", formatSemiBold); + textFlow.AddParagraph(getRandomTip(), formatRegular); textFlow.NewParagraph(); textFlow.NewParagraph(); iconColour = colours.Yellow; - static void format(SpriteText t) => t.Font = OsuFont.GetFont(size: 15, weight: FontWeight.SemiBold); - // manually transfer the user once, but only do the final bind in LoadComplete to avoid thread woes (API scheduler could run while this screen is still loading). // the manual transfer is here to ensure all text content is loaded ahead of time as this is very early in the game load process and we want to avoid stutters. currentUser.Value = api.LocalUser.Value; @@ -135,19 +137,19 @@ namespace osu.Game.Screens.Menu if (e.NewValue.IsSupporter) { - supportFlow.AddText("Eternal thanks to you for supporting osu!", format); + supportFlow.AddText("Eternal thanks to you for supporting osu!", formatSemiBold); } else { - supportFlow.AddText("Consider becoming an ", format); - supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", creationParameters: format); - supportFlow.AddText(" to help support osu!'s development", format); + supportFlow.AddText("Consider becoming an ", formatSemiBold); + supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", formatSemiBold); + supportFlow.AddText(" to help support osu!'s development", formatSemiBold); } heart = supportFlow.AddIcon(FontAwesome.Solid.Heart, t => { t.Padding = new MarginPadding { Left = 5, Top = 3 }; - t.Font = t.Font.With(size: 12); + t.Font = t.Font.With(size: 20); t.Origin = Anchor.Centre; t.Colour = colours.Pink; }).First(); From 0396a03bbc7f9e041a7d0feb10aa906b2f614355 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 12:50:53 +0900 Subject: [PATCH 351/457] Rename `Mod.Ranked` back to avoid breaking ruleset API --- osu.Game/Rulesets/Mods/Mod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 79d16013e3..40c3761768 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Mods public virtual bool UserPlayable => true; [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to true.")] // Can be removed 20211009 - public virtual bool IsRanked => false; + public virtual bool Ranked => false; /// /// Whether this mod requires configuration to apply changes to the game. From 30467191b2137534aa3aaacb4973938bf20104be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 14:21:48 +0900 Subject: [PATCH 352/457] Remove local handling of `NoFail` addition --- .../Gameplay/TestScenePlayerScoreSubmission.cs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 27f9fc85cf..d80fbfe309 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.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 System.Linq; using NUnit.Framework; using osu.Framework.Screens; @@ -9,7 +8,6 @@ using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Online.Solo; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Ranking; @@ -17,22 +15,15 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestScenePlayerScoreSubmission : OsuPlayerTestScene { - protected override bool AllowFail => false; + protected override bool AllowFail => allowFail; + + private bool allowFail; private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; protected override bool HasCustomSteps => true; - private bool allowFail; - - protected override TestPlayer CreatePlayer(Ruleset ruleset) - { - SelectedMods.Value = !allowFail - ? new[] { ruleset.GetAllMods().OfType().First() } - : Array.Empty(); - - return new TestPlayer(false); - } + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false); [Test] public void TestNoSubmissionOnResultsWithNoToken() From f689e788c9d363136dfc0900acd179a801754b6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 14:24:27 +0900 Subject: [PATCH 353/457] Avoid using game-wide beatmap during score submission --- osu.Game/Screens/Play/SoloPlayer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index 0099b4c87c..ef1087dd62 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -37,9 +37,11 @@ namespace osu.Game.Screens.Play protected override APIRequest CreateSubmissionRequest(Score score, long token) { - Debug.Assert(Beatmap.Value.BeatmapInfo.OnlineBeatmapID != null); + var beatmap = score.ScoreInfo.Beatmap; - int beatmapId = Beatmap.Value.BeatmapInfo.OnlineBeatmapID.Value; + Debug.Assert(beatmap.OnlineBeatmapID != null); + + int beatmapId = beatmap.OnlineBeatmapID.Value; return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo); } From f2d9d78455d2de5495c522cde54b6209dcbbd446 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 14:43:48 +0900 Subject: [PATCH 354/457] Fix some incorrectly invoked `async` calls --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 4 ++-- .../Multiplayer/TestSceneMultiplayerReadyButton.cs | 5 +++-- .../Multiplayer/TestSceneMultiplayerSpectateButton.cs | 9 +++++---- osu.Game/OsuGame.cs | 4 ++++ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 47cd39dc5a..58d67c11d9 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -116,7 +116,7 @@ namespace osu.Desktop.Updater if (scheduleRecheck) { // check again in 30 minutes. - Scheduler.AddDelayed(async () => await checkForUpdateAsync().ConfigureAwait(false), 60000 * 30); + Scheduler.AddDelayed(() => Task.Run(async () => await checkForUpdateAsync().ConfigureAwait(false)), 60000 * 30); } } @@ -141,7 +141,7 @@ namespace osu.Desktop.Updater Activated = () => { updateManager.PrepareUpdateAsync() - .ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit())); + .ContinueWith(_ => updateManager.Schedule(() => game?.GracefullyExit())); return true; }; } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 4f2ca34fb0..fd9aa922b0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -65,7 +66,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), - OnReadyClick = async () => + OnReadyClick = () => Task.Run(async () => { readyClickOperation = OngoingOperationTracker.BeginOperation(); @@ -77,7 +78,7 @@ namespace osu.Game.Tests.Visual.Multiplayer await Client.ToggleReady(); readyClickOperation.Dispose(); - } + }) }); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 070158f552..a4b0195b00 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -69,19 +70,19 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), - OnSpectateClick = async () => + OnSpectateClick = () => Task.Run(async () => { readyClickOperation = OngoingOperationTracker.BeginOperation(); await Client.ToggleSpectate(); readyClickOperation.Dispose(); - } + }) }, readyButton = new MultiplayerReadyButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), - OnReadyClick = async () => + OnReadyClick = () => Task.Run(async () => { readyClickOperation = OngoingOperationTracker.BeginOperation(); @@ -93,7 +94,7 @@ namespace osu.Game.Tests.Visual.Multiplayer await Client.ToggleReady(); readyClickOperation.Dispose(); - } + }) } } }; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 14309e2296..dcd2d68b43 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -491,6 +491,10 @@ namespace osu.Game public override Task Import(params ImportTask[] imports) { // encapsulate task as we don't want to begin the import process until in a ready state. + + // ReSharper disable once AsyncVoidLambda + // TODO: This is bad because `new Task` doesn't have a Func override. + // Only used for android imports and a bit of a mess. Probably needs rethinking overall. var importTask = new Task(async () => await base.Import(imports).ConfigureAwait(false)); waitForReady(() => this, _ => importTask.Start()); From 69b13477304ce77d9003cbceac394cf3f571dd88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 15:29:51 +0900 Subject: [PATCH 355/457] Tidy up weird bind logic --- osu.Game/Overlays/Volume/VolumeMeter.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 2251be03c6..6a26de204c 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -187,13 +187,7 @@ namespace osu.Game.Overlays.Volume } }; - Bindable.ValueChanged += volume => - { - this.TransformTo("DisplayVolume", - volume.NewValue, - 400, - Easing.OutQuint); - }; + Bindable.ValueChanged += volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }; bgProgress.Current.Value = 0.75f; } From 13254d51fc93a33dbe67921d4a62b112bd4ae2ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 15:44:35 +0900 Subject: [PATCH 356/457] Remove usage of `bool` for initial playback Also refactors the whole method to generally clean things up. One more important fix is setting the frequency on the channel before starting playback, so avoid the frequency potentially being adjusted after the playback is already started. --- osu.Game/Overlays/Volume/VolumeMeter.cs | 63 +++++++++++++------------ 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 6a26de204c..3321dcf329 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -38,7 +38,6 @@ namespace osu.Game.Overlays.Volume private OsuSpriteText text; private BufferedContainer maxGlow; - private bool firstUpdate = true; private Sample sample; private double sampleLastPlaybackTime; @@ -187,16 +186,12 @@ namespace osu.Game.Overlays.Volume } }; - Bindable.ValueChanged += volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }; + Bindable.BindValueChanged(volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }, true); bgProgress.Current.Value = 0.75f; } - protected override void LoadComplete() - { - base.LoadComplete(); - Bindable.TriggerChange(); - } + private int displayVolumeInt; private double displayVolume; @@ -205,9 +200,16 @@ namespace osu.Game.Overlays.Volume get => displayVolume; set { - bool displayVolumeChanged = Math.Round(displayVolume * 100) != Math.Round(value * 100); + if (value == displayVolume) + return; + displayVolume = value; + int intValue = (int)Math.Round(displayVolume * 100); + bool intVolumeChanged = intValue != displayVolumeInt; + + displayVolumeInt = intValue; + if (displayVolume >= 0.995f) { text.Text = "MAX"; @@ -216,37 +218,36 @@ namespace osu.Game.Overlays.Volume else { maxGlow.EffectColour = Color4.Transparent; - text.Text = Math.Round(displayVolume * 100).ToString(CultureInfo.CurrentCulture); + text.Text = displayVolumeInt.ToString(CultureInfo.CurrentCulture); } volumeCircle.Current.Value = displayVolume * 0.75f; volumeCircleGlow.Current.Value = displayVolume * 0.75f; - Schedule(() => - { - const int sfx_debounce_time = 30; - - if (firstUpdate || - !displayVolumeChanged || - Time.Current - sampleLastPlaybackTime <= sfx_debounce_time) - { - firstUpdate = false; - return; - } - - var channel = sample.Play(); - - channel.Frequency.Value = 1 + displayVolume * 0.1f + RNG.NextDouble(0.02f); - if (displayVolume < 0.005f) - channel.Frequency.Value -= 0.5f; - else if (displayVolume > 0.995f) - channel.Frequency.Value -= 0.5f; - - sampleLastPlaybackTime = Time.Current; - }); + if (intVolumeChanged && IsLoaded) + Scheduler.AddOnce(playTickSound); } } + private void playTickSound() + { + const int tick_debounce_time = 30; + + if (Time.Current - sampleLastPlaybackTime <= tick_debounce_time) + return; + + var channel = sample.GetChannel(); + + channel.Frequency.Value = 1 + displayVolume * 0.1f + RNG.NextDouble(0.02f); + + if (displayVolumeInt == 0) + channel.Frequency.Value -= 0.5f; + else if (displayVolumeInt == 100) channel.Frequency.Value -= 0.5f; + + channel.Play(); + sampleLastPlaybackTime = Time.Current; + } + public double Volume { get => Bindable.Value; From bd6664d5414532a384563128581f81a46663d64d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 15:45:50 +0900 Subject: [PATCH 357/457] Add note about intentional downward pitch, against expectations --- osu.Game/Overlays/Volume/VolumeMeter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 3321dcf329..7428c6a7f6 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -242,7 +242,9 @@ namespace osu.Game.Overlays.Volume if (displayVolumeInt == 0) channel.Frequency.Value -= 0.5f; - else if (displayVolumeInt == 100) channel.Frequency.Value -= 0.5f; + else if (displayVolumeInt == 100) + // intentionally pitched down, even when hitting max. + channel.Frequency.Value -= 0.5f; channel.Play(); sampleLastPlaybackTime = Time.Current; From 910fe3e9f8bde8135cb7f8caabad32b0511bca48 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 2 Jul 2021 15:39:43 +0900 Subject: [PATCH 358/457] Center pitch randomisation around base pitch --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 7 +++---- osu.Game/Overlays/Volume/VolumeMeter.cs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 1433f0c38b..386e081e6f 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -161,10 +161,9 @@ namespace osu.Game.Graphics.UserInterface var channel = sample.Play(); - channel.Frequency.Value = 1 + NormalizedValue * 0.2f + RNG.NextDouble(0.02f); - if (NormalizedValue == 0) - channel.Frequency.Value -= 0.5f; - else if (NormalizedValue == 1) + channel.Frequency.Value = 0.99f + RNG.NextDouble(0.02f) + NormalizedValue * 0.2f; + + if (NormalizedValue == 0 || NormalizedValue == 1) channel.Frequency.Value -= 0.5f; } diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 7428c6a7f6..3300d8ee17 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -238,7 +238,7 @@ namespace osu.Game.Overlays.Volume var channel = sample.GetChannel(); - channel.Frequency.Value = 1 + displayVolume * 0.1f + RNG.NextDouble(0.02f); + channel.Frequency.Value = 0.99f + RNG.NextDouble(0.02f) + displayVolume * 0.1f; if (displayVolumeInt == 0) channel.Frequency.Value -= 0.5f; From 63d2ac66d2e3a20863a2d60761d49f357b257f54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 15:52:20 +0900 Subject: [PATCH 359/457] Fix one more instance of incorrect playback/frequency set order --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 386e081e6f..0ae77071fb 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -159,12 +159,14 @@ namespace osu.Game.Graphics.UserInterface lastSampleValue = value; lastSampleTime = Clock.CurrentTime; - var channel = sample.Play(); + var channel = sample.GetChannel(); channel.Frequency.Value = 0.99f + RNG.NextDouble(0.02f) + NormalizedValue * 0.2f; if (NormalizedValue == 0 || NormalizedValue == 1) channel.Frequency.Value -= 0.5f; + + channel.Play(); } private void updateTooltipText(T value) From 35f79669223a8f6564589fa1069b3ee4a5b9ca29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 15:55:20 +0900 Subject: [PATCH 360/457] Merge conditionals in line with other case of same logic --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 1 + osu.Game/Overlays/Volume/VolumeMeter.cs | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 0ae77071fb..f85f9327fa 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -163,6 +163,7 @@ namespace osu.Game.Graphics.UserInterface channel.Frequency.Value = 0.99f + RNG.NextDouble(0.02f) + NormalizedValue * 0.2f; + // intentionally pitched down, even when hitting max. if (NormalizedValue == 0 || NormalizedValue == 1) channel.Frequency.Value -= 0.5f; diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 3300d8ee17..532b0f4a81 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -240,10 +240,8 @@ namespace osu.Game.Overlays.Volume channel.Frequency.Value = 0.99f + RNG.NextDouble(0.02f) + displayVolume * 0.1f; - if (displayVolumeInt == 0) - channel.Frequency.Value -= 0.5f; - else if (displayVolumeInt == 100) - // intentionally pitched down, even when hitting max. + // intentionally pitched down, even when hitting max. + if (displayVolumeInt == 0 || displayVolumeInt == 100) channel.Frequency.Value -= 0.5f; channel.Play(); From 7b0f970e7db162bf356b33b2977f22c638be15db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 16:04:51 +0900 Subject: [PATCH 361/457] Fix ongoing operation being begun in an async context --- .../TestSceneMultiplayerReadyButton.cs | 20 +++++++----- .../TestSceneMultiplayerSpectateButton.cs | 32 ++++++++++++------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index fd9aa922b0..0e036e8868 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -66,19 +66,23 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), - OnReadyClick = () => Task.Run(async () => + OnReadyClick = () => { readyClickOperation = OngoingOperationTracker.BeginOperation(); - if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready) + Task.Run(async () => { - await Client.StartMatch(); - return; - } + if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready) + { + await Client.StartMatch(); + return; + } - await Client.ToggleReady(); - readyClickOperation.Dispose(); - }) + await Client.ToggleReady(); + + readyClickOperation.Dispose(); + }); + } }); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index a4b0195b00..4966dfbe50 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -70,31 +70,39 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), - OnSpectateClick = () => Task.Run(async () => + OnSpectateClick = () => { readyClickOperation = OngoingOperationTracker.BeginOperation(); - await Client.ToggleSpectate(); - readyClickOperation.Dispose(); - }) + + Task.Run(async () => + { + await Client.ToggleSpectate(); + readyClickOperation.Dispose(); + }); + } }, readyButton = new MultiplayerReadyButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), - OnReadyClick = () => Task.Run(async () => + OnReadyClick = () => { readyClickOperation = OngoingOperationTracker.BeginOperation(); - if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready) + Task.Run(async () => { - await Client.StartMatch(); - return; - } + if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready) + { + await Client.StartMatch(); + return; + } - await Client.ToggleReady(); - readyClickOperation.Dispose(); - }) + await Client.ToggleReady(); + + readyClickOperation.Dispose(); + }); + } } } }; From 6a0c5b54c316899168d39f69db1659dc706a0104 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 2 Jul 2021 15:55:25 +0800 Subject: [PATCH 362/457] Fix obsolete message in `Mod.Ranked` --- osu.Game/Rulesets/Mods/Mod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 40c3761768..6f00bb6c75 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool UserPlayable => true; - [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to true.")] // Can be removed 20211009 + [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to false.")] // Can be removed 20211009 public virtual bool Ranked => false; /// From d67fc87dcbc27fd0be7a3fd61faf174099fd9e68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 17:24:15 +0900 Subject: [PATCH 363/457] Add some basic testability of external colour setting --- .../Visual/UserInterface/TestSceneColourPicker.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs index 634e45e7a9..fa9179443d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs @@ -2,6 +2,7 @@ // 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.Testing; @@ -14,6 +15,8 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneColourPicker : OsuTestScene { + private readonly Bindable colour = new Bindable(Colour4.Aquamarine); + [SetUpSteps] public void SetUpSteps() { @@ -42,7 +45,8 @@ namespace osu.Game.Tests.Visual.UserInterface new OsuColourPicker { Anchor = Anchor.Centre, - Origin = Anchor.Centre + Origin = Anchor.Centre, + Current = { BindTarget = colour }, } } }, @@ -59,13 +63,18 @@ namespace osu.Game.Tests.Visual.UserInterface new OsuColourPicker { Anchor = Anchor.Centre, - Origin = Anchor.Centre + Origin = Anchor.Centre, + Current = { BindTarget = colour }, } } } } } }); + + AddStep("set green", () => colour.Value = Colour4.LimeGreen); + AddStep("set white", () => colour.Value = Colour4.White); + AddStep("set red", () => colour.Value = Colour4.Red); } private class ColourProvidingContainer : Container From ecb4982281dd71c48b9e3ee0ffde5a2e5919c41b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 17:51:54 +0900 Subject: [PATCH 364/457] Add missing blank lines --- osu.Game/Overlays/BeatmapSet/Info.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 89fcbae93d..a1adadbc47 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -163,6 +163,7 @@ namespace osu.Game.Overlays.BeatmapSet { case MetadataType.Tags: string[] tags = value.Split(" "); + for (int i = 0; i <= tags.Length - 1; i++) { textFlow.AddLink(tags[i], LinkAction.SearchBeatmapSet, tags[i], null, format); @@ -170,6 +171,7 @@ namespace osu.Game.Overlays.BeatmapSet if (i != tags.Length - 1) textFlow.AddText(" ", format); } + break; case MetadataType.Source: From eacf867073b17d204fc53feaacfb4faaa06e4507 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 17:58:24 +0900 Subject: [PATCH 365/457] Move shared types into their own classes --- osu.Game/Overlays/BeatmapSet/Info.cs | 75 -------------- osu.Game/Screens/Select/BeatmapDetails.cs | 108 ------------------- osu.Game/Screens/Select/MetadataSection.cs | 115 +++++++++++++++++++++ osu.Game/Screens/Select/MetadataType.cs | 14 +++ 4 files changed, 129 insertions(+), 183 deletions(-) create mode 100644 osu.Game/Screens/Select/MetadataSection.cs create mode 100644 osu.Game/Screens/Select/MetadataType.cs diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index a1adadbc47..8f5d97a6d3 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -6,14 +6,10 @@ 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.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Online.Chat; using osu.Game.Screens.Select; -using osuTK; namespace osu.Game.Overlays.BeatmapSet { @@ -138,76 +134,5 @@ namespace osu.Game.Overlays.BeatmapSet successRateBackground.Colour = colourProvider.Background4; background.Colour = colourProvider.Background5; } - - private class MetadataSection : FillFlowContainer - { - private readonly MetadataType type; - private readonly LinkFlowContainer textFlow; - - public string Text - { - set - { - if (string.IsNullOrEmpty(value)) - { - Hide(); - return; - } - - this.FadeIn(transition_duration); - - textFlow.Clear(); - static void format(SpriteText t) => t.Font = t.Font.With(size: 12); - - switch (type) - { - case MetadataType.Tags: - string[] tags = value.Split(" "); - - for (int i = 0; i <= tags.Length - 1; i++) - { - textFlow.AddLink(tags[i], LinkAction.SearchBeatmapSet, tags[i], null, format); - - if (i != tags.Length - 1) - textFlow.AddText(" ", format); - } - - break; - - case MetadataType.Source: - textFlow.AddLink(value, LinkAction.SearchBeatmapSet, value, null, format); - break; - - default: - textFlow.AddText(value, format); - break; - } - } - } - - public MetadataSection(MetadataType type) - { - this.type = type; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Spacing = new Vector2(5f); - - InternalChildren = new Drawable[] - { - new OsuSpriteText - { - Text = this.type.ToString(), - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), - Margin = new MarginPadding { Top = 15 }, - }, - textFlow = new LinkFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, - }; - } - } } } diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index d7dc73cb37..d6c108cee0 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -15,7 +15,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests; -using osu.Game.Online.Chat; using osu.Game.Rulesets; using osu.Game.Screens.Select.Details; using osuTK; @@ -129,8 +128,6 @@ namespace osu.Game.Screens.Select AutoSizeAxes = Axes.Y, LayoutDuration = transition_duration, LayoutEasing = Easing.OutQuad, - Spacing = new Vector2(spacing * 2), - Margin = new MarginPadding { Top = spacing * 2 }, Children = new[] { description = new MetadataSection(MetadataType.Description), @@ -291,110 +288,5 @@ namespace osu.Game.Screens.Select }; } } - - private class MetadataSection : Container - { - private readonly FillFlowContainer textContainer; - private readonly MetadataType type; - private TextFlowContainer textFlow; - - public MetadataSection(MetadataType type) - { - this.type = type; - - Alpha = 0; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - InternalChild = textContainer = new FillFlowContainer - { - Alpha = 0, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(spacing / 2), - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Child = new OsuSpriteText - { - Text = this.type.ToString(), - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), - }, - }, - }, - }; - } - - public string Text - { - set - { - if (string.IsNullOrEmpty(value)) - { - this.FadeOut(transition_duration); - return; - } - - this.FadeIn(transition_duration); - - setTextAsync(value); - } - } - - private void setTextAsync(string text) - { - LoadComponentAsync(new LinkFlowContainer(s => s.Font = s.Font.With(size: 14)) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Colour = Color4.White.Opacity(0.75f), - }, loaded => - { - textFlow?.Expire(); - - switch (type) - { - case MetadataType.Tags: - string[] tags = text.Split(" "); - - for (int i = 0; i <= tags.Length - 1; i++) - { - loaded.AddLink(tags[i], LinkAction.SearchBeatmapSet, tags[i]); - - if (i != tags.Length - 1) - loaded.AddText(" "); - } - - break; - - case MetadataType.Source: - loaded.AddLink(text, LinkAction.SearchBeatmapSet, text); - break; - - default: - loaded.AddText(text); - break; - } - - textContainer.Add(textFlow = loaded); - - // fade in if we haven't yet. - textContainer.FadeIn(transition_duration); - }); - } - } - } - - public enum MetadataType - { - Tags, - Source, - Description, - Genre, - Language } } diff --git a/osu.Game/Screens/Select/MetadataSection.cs b/osu.Game/Screens/Select/MetadataSection.cs new file mode 100644 index 0000000000..989223fff2 --- /dev/null +++ b/osu.Game/Screens/Select/MetadataSection.cs @@ -0,0 +1,115 @@ +// 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.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Select +{ + public class MetadataSection : Container + { + private readonly FillFlowContainer textContainer; + private readonly MetadataType type; + private TextFlowContainer textFlow; + + private const float transition_duration = 250; + + public MetadataSection(MetadataType type) + { + this.type = type; + + Alpha = 0; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = textContainer = new FillFlowContainer + { + Alpha = 0, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + + Margin = new MarginPadding { Top = 15 }, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = new OsuSpriteText + { + Text = this.type.ToString(), + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), + }, + }, + }, + }; + } + + public string Text + { + set + { + if (string.IsNullOrEmpty(value)) + { + this.FadeOut(transition_duration); + return; + } + + this.FadeIn(transition_duration); + + setTextAsync(value); + } + } + + private void setTextAsync(string text) + { + LoadComponentAsync(new LinkFlowContainer(s => s.Font = s.Font.With(size: 14)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Colour = Color4.White.Opacity(0.75f), + }, loaded => + { + textFlow?.Expire(); + + switch (type) + { + case MetadataType.Tags: + string[] tags = text.Split(" "); + + for (int i = 0; i <= tags.Length - 1; i++) + { + loaded.AddLink(tags[i], LinkAction.SearchBeatmapSet, tags[i]); + + if (i != tags.Length - 1) + loaded.AddText(" "); + } + + break; + + case MetadataType.Source: + loaded.AddLink(text, LinkAction.SearchBeatmapSet, text); + break; + + default: + loaded.AddText(text); + break; + } + + textContainer.Add(textFlow = loaded); + + // fade in if we haven't yet. + textContainer.FadeIn(transition_duration); + }); + } + } +} diff --git a/osu.Game/Screens/Select/MetadataType.cs b/osu.Game/Screens/Select/MetadataType.cs new file mode 100644 index 0000000000..b0b8f5677d --- /dev/null +++ b/osu.Game/Screens/Select/MetadataType.cs @@ -0,0 +1,14 @@ +// 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.Select +{ + public enum MetadataType + { + Tags, + Source, + Description, + Genre, + Language + } +} From 362816492fb6e70d246335ad00ed9f151d92ad9f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 18:09:16 +0900 Subject: [PATCH 366/457] Move to more friendly namespace --- .../{Screens/Select => Overlays/BeatmapSet}/MetadataSection.cs | 3 ++- .../{Screens/Select => Overlays/BeatmapSet}/MetadataType.cs | 2 +- osu.Game/Screens/Select/BeatmapDetails.cs | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) rename osu.Game/{Screens/Select => Overlays/BeatmapSet}/MetadataSection.cs (97%) rename osu.Game/{Screens/Select => Overlays/BeatmapSet}/MetadataType.cs (87%) diff --git a/osu.Game/Screens/Select/MetadataSection.cs b/osu.Game/Overlays/BeatmapSet/MetadataSection.cs similarity index 97% rename from osu.Game/Screens/Select/MetadataSection.cs rename to osu.Game/Overlays/BeatmapSet/MetadataSection.cs index 989223fff2..50e95bb04b 100644 --- a/osu.Game/Screens/Select/MetadataSection.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSection.cs @@ -8,10 +8,11 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; +using osu.Game.Screens.Select; using osuTK; using osuTK.Graphics; -namespace osu.Game.Screens.Select +namespace osu.Game.Overlays.BeatmapSet { public class MetadataSection : Container { diff --git a/osu.Game/Screens/Select/MetadataType.cs b/osu.Game/Overlays/BeatmapSet/MetadataType.cs similarity index 87% rename from osu.Game/Screens/Select/MetadataType.cs rename to osu.Game/Overlays/BeatmapSet/MetadataType.cs index b0b8f5677d..1ab4c6887e 100644 --- a/osu.Game/Screens/Select/MetadataType.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataType.cs @@ -1,7 +1,7 @@ // 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.Select +namespace osu.Game.Overlays.BeatmapSet { public enum MetadataType { diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index d6c108cee0..973f54c038 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Overlays.BeatmapSet; using osu.Game.Rulesets; using osu.Game.Screens.Select.Details; using osuTK; From 8847915c6a4322d83562e5cb7f2cf65004f33d01 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 18:51:14 +0900 Subject: [PATCH 367/457] 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 2e388b8626..591e00fb27 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c44857a77d..14efcd516e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 4772d700da..0ab0b26430 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 170513568be1a85e89f40ac622a70bee5c2ee753 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 21 Jun 2021 19:08:43 +0900 Subject: [PATCH 368/457] Move caught object stack vertical offset logic --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs | 4 ++-- osu.Game.Rulesets.Catch/UI/Catcher.cs | 10 ++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 1ad45d2f13..927175a138 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -194,8 +194,8 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("catch more fruits", () => attemptCatch(() => new Fruit(), 9)); checkPlate(10); AddAssert("caught objects are stacked", () => - catcher.CaughtObjects.All(obj => obj.Y <= Catcher.CAUGHT_FRUIT_VERTICAL_OFFSET) && - catcher.CaughtObjects.Any(obj => obj.Y == Catcher.CAUGHT_FRUIT_VERTICAL_OFFSET) && + catcher.CaughtObjects.All(obj => obj.Y <= 0) && + catcher.CaughtObjects.Any(obj => obj.Y == 0) && catcher.CaughtObjects.Any(obj => obj.Y < -25)); } diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index dcab9459ee..f2e29b2e10 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -56,11 +56,6 @@ namespace osu.Game.Rulesets.Catch.UI /// public double Speed => (Dashing ? 1 : 0.5) * BASE_SPEED * hyperDashModifier; - /// - /// The amount by which caught fruit should be offset from the plate surface to make them look visually "caught". - /// - public const float CAUGHT_FRUIT_VERTICAL_OFFSET = -5; - /// /// The amount by which caught fruit should be scaled down to fit on the plate. /// @@ -157,6 +152,8 @@ namespace osu.Game.Rulesets.Catch.UI { Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, + // offset fruit vertically to better place "above" the plate. + Y = -5 }, body = new SkinnableCatcher(), hitExplosionContainer = new HitExplosionContainer @@ -388,9 +385,6 @@ namespace osu.Game.Rulesets.Catch.UI float adjustedRadius = displayRadius * lenience_adjust; float checkDistance = MathF.Pow(adjustedRadius, 2); - // offset fruit vertically to better place "above" the plate. - position.Y += CAUGHT_FRUIT_VERTICAL_OFFSET; - while (caughtObjectContainer.Any(f => Vector2Extensions.DistanceSquared(f.Position, position) < checkDistance)) { position.X += RNG.NextSingle(-adjustedRadius, adjustedRadius); From fbba32647e8ed1f44b68c25d9b2957c13142904f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 2 Jul 2021 22:00:41 +0900 Subject: [PATCH 369/457] Decouple direction of catcher from its scale --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 21 ++++++++++--------- .../UI/CatcherTrailDisplay.cs | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index dcab9459ee..b6a9863002 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -84,8 +84,8 @@ namespace osu.Game.Rulesets.Catch.UI public CatcherAnimationState CurrentState { - get => body.AnimationState.Value; - private set => body.AnimationState.Value = value; + get => Body.AnimationState.Value; + private set => Body.AnimationState.Value = value; } /// @@ -108,18 +108,14 @@ namespace osu.Game.Rulesets.Catch.UI } } - public Direction VisualDirection - { - get => Scale.X > 0 ? Direction.Right : Direction.Left; - set => Scale = new Vector2((value == Direction.Right ? 1 : -1) * Math.Abs(Scale.X), Scale.Y); - } + public Direction VisualDirection { get; set; } = Direction.Right; /// /// Width of the area that can be used to attempt catches during gameplay. /// private readonly float catchWidth; - private readonly SkinnableCatcher body; + internal readonly SkinnableCatcher Body; private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR; private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR; @@ -158,7 +154,7 @@ namespace osu.Game.Rulesets.Catch.UI Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, }, - body = new SkinnableCatcher(), + Body = new SkinnableCatcher(), hitExplosionContainer = new HitExplosionContainer { Anchor = Anchor.TopCentre, @@ -354,6 +350,11 @@ namespace osu.Game.Rulesets.Catch.UI { base.Update(); + var scaleFromDirection = new Vector2((int)VisualDirection, 1); + Body.Scale = scaleFromDirection; + // TODO: don't flip plate contents on legacy skin. + caughtObjectContainer.Scale = hitExplosionContainer.Scale = scaleFromDirection; + // Correct overshooting. if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) || (hyperDashDirection < 0 && hyperDashTargetPosition > X)) @@ -465,7 +466,7 @@ namespace osu.Game.Rulesets.Catch.UI break; case DroppedObjectAnimation.Explode: - var originalX = droppedObjectTarget.ToSpaceOfOtherDrawable(d.DrawPosition, caughtObjectContainer).X * Scale.X; + float originalX = droppedObjectTarget.ToSpaceOfOtherDrawable(d.DrawPosition, caughtObjectContainer).X * caughtObjectContainer.Scale.X; d.MoveToY(d.Y - 50, 250, Easing.OutSine).Then().MoveToY(d.Y + 50, 500, Easing.InSine); d.MoveToX(d.X + originalX * 6, 1000); d.FadeOut(750); diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index 7e4a5b6a86..b59fabcb70 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Catch.UI CatcherTrail sprite = trailPool.Get(); sprite.AnimationState = catcher.CurrentState; - sprite.Scale = catcher.Scale; + sprite.Scale = catcher.Scale * catcher.Body.Scale; sprite.Position = catcher.Position; target.Add(sprite); From 83c80291d49b5b65d056019710e400dc0d1c16c6 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 2 Jul 2021 22:18:31 +0900 Subject: [PATCH 370/457] Don't flip catcher plate contents in legacy skin --- .../Skinning/CatchSkinConfiguration.cs | 13 +++++++++++++ .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 10 ++++++++++ osu.Game.Rulesets.Catch/UI/Catcher.cs | 13 +++++++++++-- 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs b/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs new file mode 100644 index 0000000000..ea8d742b1a --- /dev/null +++ b/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Catch.Skinning +{ + public enum CatchSkinConfiguration + { + /// + /// Whether the contents of the catcher plate should be visually flipped when the catcher direction is changed. + /// + FlipCatcherPlate + } +} diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 287ed1b4c7..b6f6e91c29 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -103,6 +103,16 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy result.Value = LegacyColourCompatibility.DisallowZeroAlpha(result.Value); return (IBindable)result; + + case CatchSkinConfiguration config: + switch (config) + { + case CatchSkinConfiguration.FlipCatcherPlate: + // Always return `false` (don't flip catcher plate contents) for a legacy skin. + return (IBindable)new Bindable(); + } + + break; } return base.GetConfig(lookup); diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index b6a9863002..4b532b9c91 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -108,8 +108,16 @@ namespace osu.Game.Rulesets.Catch.UI } } + /// + /// The currently facing direction. + /// public Direction VisualDirection { get; set; } = Direction.Right; + /// + /// Whether the contents of the catcher plate should be visually flipped when the catcher direction is changed. + /// + private bool flipCatcherPlate; + /// /// Width of the area that can be used to attempt catches during gameplay. /// @@ -343,6 +351,8 @@ namespace osu.Game.Rulesets.Catch.UI trails.HyperDashTrailsColour = hyperDashColour; trails.EndGlowSpritesColour = hyperDashEndGlowColour; + flipCatcherPlate = skin.GetConfig(CatchSkinConfiguration.FlipCatcherPlate)?.Value ?? true; + runHyperDashStateTransition(HyperDashing); } @@ -352,8 +362,7 @@ namespace osu.Game.Rulesets.Catch.UI var scaleFromDirection = new Vector2((int)VisualDirection, 1); Body.Scale = scaleFromDirection; - // TODO: don't flip plate contents on legacy skin. - caughtObjectContainer.Scale = hitExplosionContainer.Scale = scaleFromDirection; + caughtObjectContainer.Scale = hitExplosionContainer.Scale = flipCatcherPlate ? scaleFromDirection : Vector2.One; // Correct overshooting. if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) || From 333caca38667c35b10630ed9c111daf0b992e60b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 2 Jul 2021 23:09:37 +0900 Subject: [PATCH 371/457] Add test for catcher plate flipping configuration --- .../TestSceneCatchSkinConfiguration.cs | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs new file mode 100644 index 0000000000..ec186bcfb2 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs @@ -0,0 +1,114 @@ +// 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.Allocation; +using osu.Framework.Bindables; +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.Catch.Judgements; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Catch.Skinning; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; +using Direction = osu.Game.Rulesets.Catch.UI.Direction; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public class TestSceneCatchSkinConfiguration : OsuTestScene + { + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + + private Catcher catcher; + + private readonly Container container; + + public TestSceneCatchSkinConfiguration() + { + Add(droppedObjectContainer = new DroppedObjectContainer()); + Add(container = new Container { RelativeSizeAxes = Axes.Both }); + } + + [TestCase(false)] + [TestCase(true)] + public void TestCatcherPlateFlipping(bool flip) + { + AddStep("setup catcher", () => + { + var skin = new TestSkin { FlipCatcherPlate = flip }; + container.Child = new SkinProvidingContainer(skin) + { + Child = catcher = new Catcher(new Container()) + { + Anchor = Anchor.Centre + } + }; + }); + + Fruit fruit = new Fruit(); + + AddStep("catch fruit", () => catchFruit(fruit, 20)); + + float position = 0; + + AddStep("record fruit position", () => position = getCaughtObjectPosition(fruit)); + + AddStep("face left", () => catcher.VisualDirection = Direction.Left); + + if (flip) + AddAssert("fruit position changed", () => !Precision.AlmostEquals(getCaughtObjectPosition(fruit), position)); + else + AddAssert("fruit position unchanged", () => Precision.AlmostEquals(getCaughtObjectPosition(fruit), position)); + + AddStep("face right", () => catcher.VisualDirection = Direction.Right); + + AddAssert("fruit position restored", () => Precision.AlmostEquals(getCaughtObjectPosition(fruit), position)); + } + + private float getCaughtObjectPosition(Fruit fruit) + { + var caughtObject = catcher.ChildrenOfType().Single(c => c.HitObject == fruit); + return caughtObject.Parent.ToSpaceOfOtherDrawable(caughtObject.Position, catcher).X; + } + + private void catchFruit(Fruit fruit, float x) + { + fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + var drawableFruit = new DrawableFruit(fruit) { X = x }; + var judgement = fruit.CreateJudgement(); + catcher.OnNewResult(drawableFruit, new CatchJudgementResult(fruit, judgement) + { + Type = judgement.MaxResult + }); + } + + private class TestSkin : DefaultSkin + { + public bool FlipCatcherPlate { get; set; } + + public TestSkin() + : base(null) + { + } + + public override IBindable GetConfig(TLookup lookup) + { + if (lookup is CatchSkinConfiguration config) + { + if (config == CatchSkinConfiguration.FlipCatcherPlate) + return SkinUtils.As(new Bindable(FlipCatcherPlate)); + } + + return base.GetConfig(lookup); + } + } + } +} From 16d08df5e29492c679f031c3bf298c0ff64d78dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Jul 2021 15:22:03 +0200 Subject: [PATCH 372/457] Remove mention of direct from xmldoc --- 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 1ac3a69233..2ab1fa3a1f 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -367,7 +367,7 @@ namespace osu.Game public void ShowBeatmap(int beatmapId) => waitForReady(() => beatmapSetOverlay, _ => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId)); /// - /// Show Direct with a query. + /// Shows the beatmap listing overlay, with the given in the search box. /// /// The query to search for. public void SearchBeatmapSet(string query) => waitForReady(() => beatmapListing, _ => beatmapListing.ShowWithSearch(query)); From 7dae93ad669eff61cd5fb51173dd3cfc6ca09405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Jul 2021 15:23:26 +0200 Subject: [PATCH 373/457] Remove unused using directives --- osu.Game/Overlays/BeatmapSet/Info.cs | 1 - osu.Game/Overlays/BeatmapSet/MetadataSection.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 8f5d97a6d3..5a752e20fb 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Screens.Select; namespace osu.Game.Overlays.BeatmapSet { diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSection.cs b/osu.Game/Overlays/BeatmapSet/MetadataSection.cs index 50e95bb04b..3648c55714 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSection.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSection.cs @@ -8,7 +8,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; -using osu.Game.Screens.Select; using osuTK; using osuTK.Graphics; From 0be75cc4edb4943b7522857a1f2f6375ec498670 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 3 Jul 2021 22:35:46 +0900 Subject: [PATCH 374/457] Fix incorrect `base` call causing import optimisation to not work --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 14bddb6319..0d16294c68 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -324,7 +324,7 @@ namespace osu.Game.Beatmaps protected override bool CanSkipImport(BeatmapSetInfo existing, BeatmapSetInfo import) { - if (!base.CanReuseExisting(existing, import)) + if (!base.CanSkipImport(existing, import)) return false; return existing.Beatmaps.Any(b => b.OnlineBeatmapID != null); From 6fb8ed4d070b98854102dc45aecdf622ef0836d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Jul 2021 15:47:46 +0200 Subject: [PATCH 375/457] Trim no longer used constant --- osu.Game/Overlays/BeatmapSet/Info.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 5a752e20fb..f9b8de9dba 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -14,7 +14,6 @@ namespace osu.Game.Overlays.BeatmapSet { public class Info : Container { - private const float transition_duration = 250; private const float metadata_width = 175; private const float spacing = 20; private const float base_height = 220; From 8a23dfa6f58f09ca2a73184262d07eeeabc1fdd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Jul 2021 17:58:12 +0200 Subject: [PATCH 376/457] Fix optimised import path buffering events without flush --- osu.Game/Database/ArchiveModelManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index f72a43fa01..87bf54f981 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -353,8 +353,6 @@ namespace osu.Game.Database { cancellationToken.ThrowIfCancellationRequested(); - delayEvents(); - bool checkedExisting = false; TModel existing = null; @@ -394,6 +392,8 @@ namespace osu.Game.Database } } + delayEvents(); + try { LogForModel(item, @"Beginning import..."); From 12371f74245632e061c5ef7bb8ab0693d98d2668 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sat, 3 Jul 2021 18:12:22 +0800 Subject: [PATCH 377/457] Fix playlist item displays as empty string if no unicode title is present --- osu.Game/Beatmaps/BeatmapMetadata.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index bfc0236db3..713f80d1fe 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -94,7 +94,10 @@ namespace osu.Game.Beatmaps public RomanisableString ToRomanisableString() { string author = Author == null ? string.Empty : $"({Author})"; - return new RomanisableString($"{ArtistUnicode} - {TitleUnicode} {author}".Trim(), $"{Artist} - {Title} {author}".Trim()); + var artistUnicode = string.IsNullOrEmpty(ArtistUnicode) ? Artist : ArtistUnicode; + var titleUnicode = string.IsNullOrEmpty(TitleUnicode) ? Title : TitleUnicode; + + return new RomanisableString($"{artistUnicode} - {titleUnicode} {author}".Trim(), $"{Artist} - {Title} {author}".Trim()); } [JsonIgnore] From 623ba1591930383b2518ac1b9ad2b2d71f74b873 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 4 Jul 2021 10:23:49 +0900 Subject: [PATCH 378/457] Relax caught object stacking test The stacking code currently uses an unseeded RNG and there is a non-zero chance the stack will be very flat (small Y position difference). Technically, `RNG.NextSingle(0, 5)` can return `0`, but extremely unlikely that the all RNG calls return 0. --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 927175a138..8359657f84 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Catch.Tests AddAssert("caught objects are stacked", () => catcher.CaughtObjects.All(obj => obj.Y <= 0) && catcher.CaughtObjects.Any(obj => obj.Y == 0) && - catcher.CaughtObjects.Any(obj => obj.Y < -25)); + catcher.CaughtObjects.Any(obj => obj.Y < 0)); } [Test] From ec71deec5149b1c1c2e4b0ffd760772a542fa8ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Jul 2021 12:39:50 +0900 Subject: [PATCH 379/457] Remove some mentions of "lazer" I am aware there are more throughout the codebase but intentionally left the remaining mentioned for one reason or another. The intention here is to mainly change user-facing versioning to change the positioning of the "lazer" term (to be where we would expect "cuttingedge" or "beta" to be). --- osu.Desktop/osu.Desktop.csproj | 4 ++-- osu.Desktop/osu.nuspec | 3 +-- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs | 2 +- .../Visual/SongSelect/TestSceneBeatmapDetails.cs | 8 ++++---- osu.Game/OsuGameBase.cs | 4 ++-- osu.Game/Scoring/ScoreManager.cs | 2 +- osu.Game/Screens/Menu/Disclaimer.cs | 2 +- osu.Game/Skinning/LegacyColourCompatibility.cs | 2 +- osu.Game/Skinning/SkinInfo.cs | 2 +- osu.Game/Updater/UpdateManager.cs | 2 +- 10 files changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index ad5c323e9b..53a4e5edf5 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -5,8 +5,8 @@ true A free-to-win rhythm game. Rhythm is just a *click* away! osu! - osu!lazer - osu!lazer + osu! + osu! lazer.ico app.manifest 0.0.0 diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec index fa182f8e70..1757fd7c73 100644 --- a/osu.Desktop/osu.nuspec +++ b/osu.Desktop/osu.nuspec @@ -3,7 +3,7 @@ osulazer 0.0.0 - osu!lazer + osu! ppy Pty Ltd Dean Herbert https://osu.ppy.sh/ @@ -20,4 +20,3 @@ - diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 317649785e..b2311dcb91 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy /// /// All constants are in osu!stable's gamefield space, which is shifted 16px downwards. - /// This offset is negated in both osu!stable and osu!lazer to bring all constants into window-space. + /// This offset is negated to bring all constants into window-space. /// Note: SPINNER_Y_CENTRE + SPINNER_TOP_OFFSET - Position.Y = 240 (=480/2, or half the window-space in osu!stable) /// protected const float SPINNER_TOP_OFFSET = 45f - 16f; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs index 06572f66bf..b4544fbc85 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.SongSelect Version = "All Metrics", Metadata = new BeatmapMetadata { - Source = "osu!lazer", + Source = "osu!", Tags = "this beatmap has all the metrics", }, BaseDifficulty = new BeatmapDifficulty @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.SongSelect Version = "Only Ratings", Metadata = new BeatmapMetadata { - Source = "osu!lazer", + Source = "osu!", Tags = "this beatmap has ratings metrics but not retries or fails", }, BaseDifficulty = new BeatmapDifficulty @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.SongSelect Version = "Only Retries and Fails", Metadata = new BeatmapMetadata { - Source = "osu!lazer", + Source = "osu!", Tags = "this beatmap has retries and fails but no ratings", }, BaseDifficulty = new BeatmapDifficulty @@ -149,7 +149,7 @@ namespace osu.Game.Tests.Visual.SongSelect Version = "No Metrics", Metadata = new BeatmapMetadata { - Source = "osu!lazer", + Source = "osu!", Tags = "this beatmap has no metrics", }, BaseDifficulty = new BeatmapDifficulty diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7954eafdca..5878727ad8 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -80,7 +80,7 @@ namespace osu.Game return @"local " + (DebugUtils.IsDebugBuild ? @"debug" : @"release"); var version = AssemblyVersion; - return $@"{version.Major}.{version.Minor}.{version.Build}"; + return $@"{version.Major}.{version.Minor}.{version.Build}-lazer"; } } @@ -162,7 +162,7 @@ namespace osu.Game public OsuGameBase() { UseDevelopmentServer = DebugUtils.IsDebugBuild; - Name = @"osu!lazer"; + Name = @"osu!"; } [BackgroundDependencyLoader] diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index d5bea0affc..ebbdc8a109 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -208,7 +208,7 @@ namespace osu.Game.Scoring } else { - // This score is guaranteed to be an osu!lazer score. + // This is guaranteed to be a non-legacy score. // The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values. beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetOrDefault(r)).Sum(); } diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index b0a2fad813..a71b53157a 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -230,7 +230,7 @@ namespace osu.Game.Screens.Menu "New features are coming online every update. Make sure to stay up-to-date!", "If you find the UI too large or small, try adjusting UI scale in settings!", "Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!", - "For now, what used to be \"osu!direct\" is available to all users on lazer. You can access it anywhere using Ctrl-D!", + "What used to be \"osu!direct\" is available to all users just like on the website. You can access it anywhere using Ctrl-D!", "Seeking in replays is available by dragging on the difficulty bar at the bottom of the screen!", "Multithreading support means that even with low \"FPS\" your input and judgements will be accurate!", "Try scrolling down in the mod select panel to find a bunch of new fun mods!", diff --git a/osu.Game/Skinning/LegacyColourCompatibility.cs b/osu.Game/Skinning/LegacyColourCompatibility.cs index b842b50426..38e43432ce 100644 --- a/osu.Game/Skinning/LegacyColourCompatibility.cs +++ b/osu.Game/Skinning/LegacyColourCompatibility.cs @@ -7,7 +7,7 @@ using osuTK.Graphics; namespace osu.Game.Skinning { /// - /// Compatibility methods to convert osu!stable colours to osu!lazer-compatible ones. Should be used for legacy skins only. + /// Compatibility methods to apply osu!stable quirks to colours. Should be used for legacy skins only. /// public static class LegacyColourCompatibility { diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index e30bc16d8b..851d71f914 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -46,7 +46,7 @@ namespace osu.Game.Skinning public static SkinInfo Default { get; } = new SkinInfo { ID = DEFAULT_SKIN, - Name = "osu!lazer", + Name = "osu! (triangles)", Creator = "team osu!", InstantiationInfo = typeof(DefaultSkin).GetInvariantInstantiationInfo() }; diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 1c72f3ebe2..98ce2cb46c 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -90,7 +90,7 @@ namespace osu.Game.Updater public UpdateCompleteNotification(string version) { this.version = version; - Text = $"You are now running osu!lazer {version}.\nClick to see what's new!"; + Text = $"You are now running osu! {version}.\nClick to see what's new!"; } [BackgroundDependencyLoader] From 149a200f34fd4b6b6108cfd646dc7344f06ab7a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Jul 2021 14:56:15 +0900 Subject: [PATCH 380/457] Fix volume metre not correctly showing volume when initially zero Closes https://github.com/ppy/osu/issues/13761. --- osu.Game/Overlays/Volume/VolumeMeter.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 532b0f4a81..1832841476 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -191,7 +191,7 @@ namespace osu.Game.Overlays.Volume bgProgress.Current.Value = 0.75f; } - private int displayVolumeInt; + private int? displayVolumeInt; private double displayVolume; @@ -200,9 +200,6 @@ namespace osu.Game.Overlays.Volume get => displayVolume; set { - if (value == displayVolume) - return; - displayVolume = value; int intValue = (int)Math.Round(displayVolume * 100); @@ -218,7 +215,7 @@ namespace osu.Game.Overlays.Volume else { maxGlow.EffectColour = Color4.Transparent; - text.Text = displayVolumeInt.ToString(CultureInfo.CurrentCulture); + text.Text = intValue.ToString(CultureInfo.CurrentCulture); } volumeCircle.Current.Value = displayVolume * 0.75f; From ef825283090514dbf69b1bee27c6cdeff1b64f00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Jul 2021 15:14:25 +0900 Subject: [PATCH 381/457] Don't attempt to submit score when nothing has been hit --- osu.Game/Screens/Play/SubmittingPlayer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 7c5a06707d..9607cc245a 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -10,6 +10,7 @@ using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.Rooms; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; namespace osu.Game.Screens.Play @@ -144,6 +145,10 @@ namespace osu.Game.Screens.Play if (scoreSubmissionSource != null) return scoreSubmissionSource.Task; + // if the user never hit anything, this score should not be counted in any way. + if (!score.ScoreInfo.Statistics.Any(s => s.Key.IsHit())) + return Task.CompletedTask; + scoreSubmissionSource = new TaskCompletionSource(); var request = CreateSubmissionRequest(score, token.Value); From 567e9f33a9b74e577761b1eb62564d35be78e8d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Jul 2021 16:24:39 +0900 Subject: [PATCH 382/457] Fix thread safety of realm `Refresh` operation Due to the lack of locking, there was a chance the the update thread `context` was retrieved just before the `flushContexts` call, followed by `.Refresh()` being run while the blocking behaviour was invoked. This can be seen in test failures such as https://ci.appveyor.com/project/peppy/osu/builds/39859786/tests. As an aside, I tried multiple different methods to avoid `lock()` on the update thread but they felt flaky. The overhead of lock when there's no contention is reportedly around 30-50ns, so likely not of concern. We can address it at a later point if it becomes one. --- osu.Game/Database/RealmContextFactory.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index fb5e2faff8..3354b97849 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -38,6 +38,8 @@ namespace osu.Game.Database private static readonly GlobalStatistic pending_writes = GlobalStatistics.Get("Realm", "Pending writes"); private static readonly GlobalStatistic active_usages = GlobalStatistics.Get("Realm", "Active usages"); + private readonly object updateContextLock = new object(); + private Realm context; public Realm Context @@ -107,8 +109,11 @@ namespace osu.Game.Database { base.Update(); - if (context?.Refresh() == true) - refreshes.Value++; + lock (updateContextLock) + { + if (context?.Refresh() == true) + refreshes.Value++; + } } private Realm createContext() @@ -156,7 +161,9 @@ namespace osu.Game.Database Logger.Log(@"Flushing realm contexts...", LoggingTarget.Database); var previousContext = context; - context = null; + + lock (updateContextLock) + context = null; // wait for all threaded usages to finish while (active_usages.Value > 0) From 7a710ceffe9cb8eb46b374b6ee07d23e977912ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Jul 2021 16:41:09 +0900 Subject: [PATCH 383/457] Check count as well (statistics can be populated with zero counts) --- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 9607cc245a..76e9f28dae 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -146,7 +146,7 @@ namespace osu.Game.Screens.Play return scoreSubmissionSource.Task; // if the user never hit anything, this score should not be counted in any way. - if (!score.ScoreInfo.Statistics.Any(s => s.Key.IsHit())) + if (!score.ScoreInfo.Statistics.Any(s => s.Key.IsHit() && s.Value > 0)) return Task.CompletedTask; scoreSubmissionSource = new TaskCompletionSource(); From 2a74b1c5399b7f149abe68d15e9b3337217e1906 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Jul 2021 17:26:46 +0900 Subject: [PATCH 384/457] Add test coverage of new scenarios --- .../TestScenePlayerScoreSubmission.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index d80fbfe309..0c1979ad21 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -9,6 +9,8 @@ using osu.Game.Online.Rooms; using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; namespace osu.Game.Tests.Visual.Gameplay @@ -52,6 +54,9 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for token request", () => Player.TokenCreationRequested); AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + + addFakeHit(); + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); @@ -71,6 +76,21 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("ensure no submission", () => Player.SubmittedScore == null); } + [Test] + public void TestNoSubmissionOnEmptyFail() + { + prepareTokenResponse(true); + + CreateTest(() => allowFail = true); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + AddUntilStep("wait for fail", () => Player.HasFailed); + AddStep("exit", () => Player.Exit()); + + AddAssert("ensure no submission", () => Player.SubmittedScore == null); + } + [Test] public void TestSubmissionOnFail() { @@ -79,12 +99,28 @@ namespace osu.Game.Tests.Visual.Gameplay CreateTest(() => allowFail = true); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + addFakeHit(); + AddUntilStep("wait for fail", () => Player.HasFailed); AddStep("exit", () => Player.Exit()); AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false); } + [Test] + public void TestNoSubmissionOnEmptyExit() + { + prepareTokenResponse(true); + + CreateTest(() => allowFail = false); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + AddStep("exit", () => Player.Exit()); + AddAssert("ensure no submission", () => Player.SubmittedScore == null); + } + [Test] public void TestSubmissionOnExit() { @@ -93,10 +129,27 @@ namespace osu.Game.Tests.Visual.Gameplay CreateTest(() => allowFail = false); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + addFakeHit(); + AddStep("exit", () => Player.Exit()); AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false); } + private void addFakeHit() + { + AddUntilStep("wait for first result", () => Player.Results.Count > 0); + + AddStep("force successfuly hit", () => + { + Player.ScoreProcessor.RevertResult(Player.Results.First()); + Player.ScoreProcessor.ApplyResult(new OsuJudgementResult(Beatmap.Value.Beatmap.HitObjects.First(), new OsuJudgement()) + { + Type = HitResult.Great, + }); + }); + } + private void prepareTokenResponse(bool validToken) { AddStep("Prepare test API", () => From 3ec7dc3bb94972fe16a7359db595faf8136bf5fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Jul 2021 17:59:39 +0900 Subject: [PATCH 385/457] Update tests in line with thread safety check --- .../Database/TestRealmKeyBindingStore.cs | 54 +++++++++++-------- osu.Game/Database/RealmContextFactory.cs | 21 +++++--- 2 files changed, 47 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index cac331451b..642ecf00b8 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -38,19 +38,28 @@ namespace osu.Game.Tests.Database [Test] public void TestDefaultsPopulationAndQuery() { - Assert.That(query().Count, Is.EqualTo(0)); + Assert.That(queryCount(), Is.EqualTo(0)); KeyBindingContainer testContainer = new TestKeyBindingContainer(); keyBindingStore.Register(testContainer); - Assert.That(query().Count, Is.EqualTo(3)); + Assert.That(queryCount(), Is.EqualTo(3)); - Assert.That(query().Where(k => k.ActionInt == (int)GlobalAction.Back).Count, Is.EqualTo(1)); - Assert.That(query().Where(k => k.ActionInt == (int)GlobalAction.Select).Count, Is.EqualTo(2)); + Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(1)); + Assert.That(queryCount(GlobalAction.Select), Is.EqualTo(2)); } - private IQueryable query() => realmContextFactory.Context.All(); + private int queryCount(GlobalAction? match = null) + { + using (var usage = realmContextFactory.GetForRead()) + { + var results = usage.Realm.All(); + if (match.HasValue) + results = results.Where(k => k.ActionInt == (int)match.Value); + return results.Count(); + } + } [Test] public void TestUpdateViaQueriedReference() @@ -59,25 +68,28 @@ namespace osu.Game.Tests.Database keyBindingStore.Register(testContainer); - var backBinding = query().Single(k => k.ActionInt == (int)GlobalAction.Back); - - Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape })); - - var tsr = ThreadSafeReference.Create(backBinding); - - using (var usage = realmContextFactory.GetForWrite()) + using (var primaryUsage = realmContextFactory.GetForRead()) { - var binding = usage.Realm.ResolveReference(tsr); - binding.KeyCombination = new KeyCombination(InputKey.BackSpace); + var backBinding = primaryUsage.Realm.All().Single(k => k.ActionInt == (int)GlobalAction.Back); - usage.Commit(); + Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape })); + + var tsr = ThreadSafeReference.Create(backBinding); + + using (var usage = realmContextFactory.GetForWrite()) + { + var binding = usage.Realm.ResolveReference(tsr); + binding.KeyCombination = new KeyCombination(InputKey.BackSpace); + + usage.Commit(); + } + + Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); + + // check still correct after re-query. + backBinding = primaryUsage.Realm.All().Single(k => k.ActionInt == (int)GlobalAction.Back); + Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); } - - Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); - - // check still correct after re-query. - backBinding = query().Single(k => k.ActionInt == (int)GlobalAction.Back); - Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); } [TearDown] diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 3354b97849..f706c37419 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -4,6 +4,7 @@ using System; using System.Threading; using osu.Framework.Allocation; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Platform; @@ -46,15 +47,21 @@ namespace osu.Game.Database { get { - if (context == null) + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException($"Use {nameof(GetForRead)} when performing realm operations from a non-update thread"); + + lock (updateContextLock) { - context = createContext(); - Logger.Log($"Opened realm \"{context.Config.DatabasePath}\" at version {context.Config.SchemaVersion}"); + if (context == null) + { + context = createContext(); + Logger.Log($"Opened realm \"{context.Config.DatabasePath}\" at version {context.Config.SchemaVersion}"); + } + + // creating a context will ensure our schema is up-to-date and migrated. + + return context; } - - // creating a context will ensure our schema is up-to-date and migrated. - - return context; } } From b89521314f7b8f72691a865f79b3574f24ba575c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 4 Jul 2021 12:07:31 +0200 Subject: [PATCH 386/457] Mention alternatives to `Context` when not on update thread in xmldoc --- osu.Game/Database/IRealmFactory.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Database/IRealmFactory.cs b/osu.Game/Database/IRealmFactory.cs index c79442134c..0e93e5bf4f 100644 --- a/osu.Game/Database/IRealmFactory.cs +++ b/osu.Game/Database/IRealmFactory.cs @@ -9,6 +9,7 @@ namespace osu.Game.Database { /// /// The main realm context, bound to the update thread. + /// If querying from a non-update thread is needed, use or to receive a context instead. /// Realm Context { get; } From 3291f15dccee50bcaac9f042e7b830fff528236e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 4 Jul 2021 12:08:15 +0200 Subject: [PATCH 387/457] Mention `GetForWrite()` as another alternative to `Context` accesses --- osu.Game/Database/RealmContextFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index f706c37419..b0241fb93c 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -48,7 +48,7 @@ namespace osu.Game.Database get { if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException($"Use {nameof(GetForRead)} when performing realm operations from a non-update thread"); + throw new InvalidOperationException($"Use {nameof(GetForRead)} or {nameof(GetForWrite)} when performing realm operations from a non-update thread"); lock (updateContextLock) { From 19f02dc3d90f6e16ed23f28e696c1f2bcb2cdf90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 4 Jul 2021 14:34:52 +0200 Subject: [PATCH 388/457] Ensure tests with no token have at least one hit Because submission can be prevented by both not having been issued a correct submission token, and by not actually registering any hits in gameplay, ensure that tests that don't receive a token register at least one hit, to avoid potentially having test cases that test the "no token" flow pass erroneously because they never had any hits in the first place. --- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 0c1979ad21..d9c0544d3c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -37,6 +37,9 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for token request", () => Player.TokenCreationRequested); AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + + addFakeHit(); + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); @@ -72,6 +75,10 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + + addFakeHit(); + AddStep("exit", () => Player.Exit()); AddAssert("ensure no submission", () => Player.SubmittedScore == null); } From 44d540eb53f6e169de2cede447e8079fac89054a Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 4 Jul 2021 22:09:23 +0800 Subject: [PATCH 389/457] Add test --- .../BeatmapMetadataRomanisationTest.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs diff --git a/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs b/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs new file mode 100644 index 0000000000..582864079d --- /dev/null +++ b/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs @@ -0,0 +1,31 @@ +// 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.Beatmaps; + +namespace osu.Game.Tests.Localisation +{ + [TestFixture] + public class BeatmapMetadataRomanisationTest + { + [Test] + public void TestNoUnicode() + { + var beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Artist = "Artist", + Title = "Romanised title" + } + } + }; + var romanisableString = beatmap.Metadata.ToRomanisableString(); + + Assert.AreEqual(romanisableString.Romanised, romanisableString.Original); + } + } +} From 356f5dceef8e898dc592b8630b82b1bfda3eecfa Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 4 Jul 2021 22:31:08 +0800 Subject: [PATCH 390/457] Add more test case --- .../BeatmapMetadataRomanisationTest.cs | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs b/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs index 582864079d..dab4825919 100644 --- a/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs +++ b/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs @@ -10,20 +10,30 @@ namespace osu.Game.Tests.Localisation public class BeatmapMetadataRomanisationTest { [Test] - public void TestNoUnicode() + public void TestRomanisation() { - var beatmap = new Beatmap + var metadata = new BeatmapMetadata { - BeatmapInfo = new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - Artist = "Artist", - Title = "Romanised title" - } - } + Artist = "Romanised Artist", + ArtistUnicode = "Unicode Artist", + Title = "Romanised title", + TitleUnicode = "Unicode Title" }; - var romanisableString = beatmap.Metadata.ToRomanisableString(); + var romanisableString = metadata.ToRomanisableString(); + + Assert.AreEqual(metadata.ToString(), romanisableString.Romanised); + Assert.AreEqual($"{metadata.ArtistUnicode} - {metadata.TitleUnicode}", romanisableString.Original); + } + + [Test] + public void TestRomanisationNoUnicode() + { + var metadata = new BeatmapMetadata + { + Artist = "Romanised Artist", + Title = "Romanised title" + }; + var romanisableString = metadata.ToRomanisableString(); Assert.AreEqual(romanisableString.Romanised, romanisableString.Original); } From 32b068fbdc93d3298bf3257c7d6aa9e6b9749119 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 21:50:58 +0200 Subject: [PATCH 391/457] Fix typo causing nested windows to be ignored --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 8dcc1ca164..cccd73f19f 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -496,7 +496,7 @@ namespace osu.Game.Rulesets.UI foreach (var n in h.NestedHitObjects) { - if (h.HitWindows.WindowFor(HitResult.Miss) > 0) + if (n.HitWindows.WindowFor(HitResult.Miss) > 0) return n.HitWindows; } } From 6d2ffe3a94ec9edab261682d16887e6dd519c70e Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 22:51:35 +0200 Subject: [PATCH 392/457] Add basic tests --- .../NonVisual/FirstAvailableHitWindowTest.cs | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs new file mode 100644 index 0000000000..92766d8334 --- /dev/null +++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs @@ -0,0 +1,121 @@ +// 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.Diagnostics.CodeAnalysis; +using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; + +namespace osu.Game.Tests.NonVisual +{ + public class FirstAvailableHitWindowsTest + { + private TestDrawableRuleset testDrawableRuleset = new TestDrawableRuleset(); + + [Test] + public void TestResultIfOnlyParentHitWindowIsEmpty() + { + var testObject = new TestHitObject(hitWindows: HitWindows.Empty); + HitObject nested = new TestHitObject(hitWindows: new HitWindows()); + testObject.AddNested(nested); + testDrawableRuleset.HitObjects = new List { testObject }; + + // If the parent window is empty, but its nested object isn't, return the nested object + Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, nested.HitWindows); + } + + [Test] + public void TestResultIfParentHitWindowsIsNotEmpty() + { + var testObject = new TestHitObject(hitWindows: new HitWindows()); + HitObject nested = new TestHitObject(hitWindows: new HitWindows()); + testObject.AddNested(nested); + testDrawableRuleset.HitObjects = new List { testObject }; + + // If the parent window is not empty, return that immediately + Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, testObject.HitWindows); + } + + [Test] + public void TestResultIfParentAndChildHitWindowsAreEmpty() + { + var firstObject = new TestHitObject(hitWindows: HitWindows.Empty); + HitObject nested = new TestHitObject(hitWindows: HitWindows.Empty); + firstObject.AddNested(nested); + + var secondObject = new TestHitObject(hitWindows: new HitWindows()); + testDrawableRuleset.HitObjects = new List { firstObject, secondObject }; + + // If the parent and child windows are empty, return the next object if window isn't empty + Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, secondObject.HitWindows); + } + + [Test] + public void TestResultIfAllHitWindowsAreEmpty() + { + var firstObject = new TestHitObject(hitWindows: HitWindows.Empty); + HitObject nested = new TestHitObject(hitWindows: HitWindows.Empty); + firstObject.AddNested(nested); + + testDrawableRuleset.HitObjects = new List { firstObject }; + + // If all windows are empty, this should return null + Assert.IsNull(testDrawableRuleset.FirstAvailableHitWindows); + } + + [SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] + private class TestDrawableRuleset : DrawableRuleset + { + public List HitObjects; + public override IEnumerable Objects => HitObjects; + + public override event Action NewResult; + public override event Action RevertResult; + + public override Playfield Playfield { get; } + public override Container Overlays { get; } + public override Container FrameStableComponents { get; } + public override IFrameStableClock FrameStableClock { get; } + internal override bool FrameStablePlayback { get; set; } + public override IReadOnlyList Mods { get; } + + public override double GameplayStartTime { get; } + public override GameplayCursorContainer Cursor { get; } + + public TestDrawableRuleset() + : base(new OsuRuleset()) + { + // won't compile without this. + NewResult?.Invoke(null); + RevertResult?.Invoke(null); + } + + public override void SetReplayScore(Score replayScore) => throw new NotImplementedException(); + + public override void SetRecordTarget(Score score) => throw new NotImplementedException(); + + public override void RequestResume(Action continueResume) => throw new NotImplementedException(); + + public override void CancelResume() => throw new NotImplementedException(); + } + } + + public class TestHitObject : HitObject + { + public TestHitObject(HitWindows hitWindows) + { + HitWindows = hitWindows; + HitWindows.SetDifficulty(0.5f); + } + + public new void AddNested(HitObject nested) => base.AddNested(nested); + } +} From 1facdcf4838c4948f03d58c8b23fcf8dd5cd8759 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 23:23:24 +0200 Subject: [PATCH 393/457] Apply changes to tests --- .../NonVisual/FirstAvailableHitWindowTest.cs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs index 92766d8334..2f1ad194bb 100644 --- a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs +++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs @@ -23,51 +23,47 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestResultIfOnlyParentHitWindowIsEmpty() { - var testObject = new TestHitObject(hitWindows: HitWindows.Empty); - HitObject nested = new TestHitObject(hitWindows: new HitWindows()); + var testObject = new TestHitObject(HitWindows.Empty); + HitObject nested = new TestHitObject(new HitWindows()); testObject.AddNested(nested); testDrawableRuleset.HitObjects = new List { testObject }; - // If the parent window is empty, but its nested object isn't, return the nested object Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, nested.HitWindows); } [Test] public void TestResultIfParentHitWindowsIsNotEmpty() { - var testObject = new TestHitObject(hitWindows: new HitWindows()); - HitObject nested = new TestHitObject(hitWindows: new HitWindows()); + var testObject = new TestHitObject(new HitWindows()); + HitObject nested = new TestHitObject(new HitWindows()); testObject.AddNested(nested); testDrawableRuleset.HitObjects = new List { testObject }; - // If the parent window is not empty, return that immediately Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, testObject.HitWindows); } [Test] public void TestResultIfParentAndChildHitWindowsAreEmpty() { - var firstObject = new TestHitObject(hitWindows: HitWindows.Empty); - HitObject nested = new TestHitObject(hitWindows: HitWindows.Empty); + var firstObject = new TestHitObject(HitWindows.Empty); + HitObject nested = new TestHitObject(HitWindows.Empty); firstObject.AddNested(nested); - var secondObject = new TestHitObject(hitWindows: new HitWindows()); + var secondObject = new TestHitObject(new HitWindows()); testDrawableRuleset.HitObjects = new List { firstObject, secondObject }; - // If the parent and child windows are empty, return the next object if window isn't empty Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, secondObject.HitWindows); } [Test] public void TestResultIfAllHitWindowsAreEmpty() { - var firstObject = new TestHitObject(hitWindows: HitWindows.Empty); - HitObject nested = new TestHitObject(hitWindows: HitWindows.Empty); + var firstObject = new TestHitObject(HitWindows.Empty); + HitObject nested = new TestHitObject(HitWindows.Empty); firstObject.AddNested(nested); testDrawableRuleset.HitObjects = new List { firstObject }; - // If all windows are empty, this should return null Assert.IsNull(testDrawableRuleset.FirstAvailableHitWindows); } From 216e52d6d0333d730ef13efdbd09534ee0c59e62 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 23:24:17 +0200 Subject: [PATCH 394/457] Avoid using single letter variable names --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index cccd73f19f..daf46dcdcc 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -489,15 +489,15 @@ namespace osu.Game.Rulesets.UI { get { - foreach (var h in Objects) + foreach (var hitObject in Objects) { - if (h.HitWindows.WindowFor(HitResult.Miss) > 0) - return h.HitWindows; + if (hitObject.HitWindows.WindowFor(HitResult.Miss) > 0) + return hitObject.HitWindows; - foreach (var n in h.NestedHitObjects) + foreach (var nested in hitObject.NestedHitObjects) { - if (n.HitWindows.WindowFor(HitResult.Miss) > 0) - return n.HitWindows; + if (nested.HitWindows.WindowFor(HitResult.Miss) > 0) + return nested.HitWindows; } } From cc877f88e2fd97dcd0556e233067a7cc0e2b9d57 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Jul 2021 10:13:01 +0900 Subject: [PATCH 395/457] Fix inspection (create a new ruleset every time) --- osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs index 2f1ad194bb..aed2a5c122 100644 --- a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs +++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs @@ -18,7 +18,13 @@ namespace osu.Game.Tests.NonVisual { public class FirstAvailableHitWindowsTest { - private TestDrawableRuleset testDrawableRuleset = new TestDrawableRuleset(); + private TestDrawableRuleset testDrawableRuleset; + + [SetUp] + public void Setup() + { + testDrawableRuleset = new TestDrawableRuleset(); + } [Test] public void TestResultIfOnlyParentHitWindowIsEmpty() From f510ef9153618d238ea534edb943b1c732b95690 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 10:49:55 +0900 Subject: [PATCH 396/457] Move `previousContext` assign within `lock` to make things look safer Not an actual requirement, but no harm. --- osu.Game/Database/RealmContextFactory.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index b0241fb93c..ed3dc01f15 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Development; @@ -166,11 +167,15 @@ namespace osu.Game.Database private void flushContexts() { Logger.Log(@"Flushing realm contexts...", LoggingTarget.Database); + Debug.Assert(blockingLock.CurrentCount == 0); - var previousContext = context; + Realm previousContext; lock (updateContextLock) + { + previousContext = context; context = null; + } // wait for all threaded usages to finish while (active_usages.Value > 0) From bfb603cfebed6b77b192c87309365f108a6d8d11 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 5 Jul 2021 09:51:24 +0800 Subject: [PATCH 397/457] Change the test for unimplemented mod to use a mock ruleset and mod --- .../TestSceneModSelectOverlay.cs | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index df8ef92a05..07bd28ac64 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -109,8 +109,6 @@ namespace osu.Game.Tests.Visual.UserInterface var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); - var targetMod = conversionMods.FirstOrDefault(m => m is OsuModTarget); - var easy = easierMods.FirstOrDefault(m => m is OsuModEasy); var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock); @@ -118,8 +116,6 @@ namespace osu.Game.Tests.Visual.UserInterface testMultiMod(doubleTimeMod); testIncompatibleMods(easy, hardRock); testDeselectAll(easierMods.Where(m => !(m is MultiMod))); - - testUnimplementedMod(targetMod); } [Test] @@ -249,6 +245,19 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("DT + HD still selected", () => modSelect.ChildrenOfType().Count(b => b.Selected) == 2); } + [Test] + public void TestUnimplementedModIsUnselectable() + { + var testRuleset = new TestUnimplementedModOsuRuleset(); + changeTestRuleset(testRuleset.RulesetInfo); + + var conversionMods = testRuleset.GetModsFor(ModType.Conversion); + + var unimplementedMod = conversionMods.FirstOrDefault(m => m is TestUnimplementedMod); + + testUnimplementedMod(unimplementedMod); + } + private void testSingleMod(Mod mod) { selectNext(mod); @@ -343,6 +352,12 @@ namespace osu.Game.Tests.Visual.UserInterface waitForLoad(); } + private void changeTestRuleset(RulesetInfo rulesetInfo) + { + AddStep($"change ruleset to {rulesetInfo.Name}", () => { Ruleset.Value = rulesetInfo; }); + waitForLoad(); + } + private void waitForLoad() => AddUntilStep("wait for icons to load", () => modSelect.AllLoaded); @@ -401,5 +416,24 @@ namespace osu.Game.Tests.Visual.UserInterface { protected override bool Stacked => false; } + + private class TestUnimplementedMod : Mod + { + public override string Name => "Unimplemented mod"; + public override string Acronym => "UM"; + public override string Description => "A mod that is not implemented."; + public override double ScoreMultiplier => 1; + public override ModType Type => ModType.Conversion; + } + + private class TestUnimplementedModOsuRuleset : OsuRuleset + { + public override IEnumerable GetModsFor(ModType type) + { + if (type == ModType.Conversion) return base.GetModsFor(type).Concat(new[] { new TestUnimplementedMod() }); + + return base.GetModsFor(type); + } + } } } From 3c371404264a9f792f28ace262e6d7ebe8296ca1 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 5 Jul 2021 09:52:13 +0800 Subject: [PATCH 398/457] Remove an unused local variable --- osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 07bd28ac64..4a1d90d871 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -103,7 +103,6 @@ namespace osu.Game.Tests.Visual.UserInterface var easierMods = osu.GetModsFor(ModType.DifficultyReduction); var harderMods = osu.GetModsFor(ModType.DifficultyIncrease); - var conversionMods = osu.GetModsFor(ModType.Conversion); var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail); From 1e4beddd2d37ef522045a35c1bb263b46e4579c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 15:41:48 +0900 Subject: [PATCH 399/457] Disable foreign key enforcing at an sqlite level --- osu.Game/Database/OsuDbContext.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index e0c0f56cb3..68d186c65d 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -77,6 +77,9 @@ namespace osu.Game.Database { cmd.CommandText = "PRAGMA journal_mode=WAL;"; cmd.ExecuteNonQuery(); + + cmd.CommandText = "PRAGMA foreign_keys=OFF;"; + cmd.ExecuteNonQuery(); } } catch From 96c0ab8dedbc5702977f07785643e8eb2a5d41c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Jul 2021 17:11:59 +0900 Subject: [PATCH 400/457] Adjust last frame position when not waiting --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 7b0cf651c8..a9b5a2d0ef 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -249,6 +249,18 @@ namespace osu.Game.Rulesets.Osu.Replays ((lastPosition - targetPos).Length > h.Radius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough timeDifference >= 266)) // ... or the beats are slow enough to tap anyway. { + OsuReplayFrame lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null; + + // The last frame may be a key-up frame if it shares a position with the second-last frame. + // If it is a key-up frame and its time occurs after the "wait time" (i.e. there was no wait period), adjust its position to begin eased movement instantaneously. + if (lastLastFrame?.Position == lastFrame.Position && lastFrame.Time >= waitTime) + { + // [lastLastFrame] ... [lastFrame] ... [current frame] + // We want to find the cursor position at lastFrame, so interpolate between lastLastFrame and the new target position. + lastFrame.Position = Interpolation.ValueAt(lastFrame.Time, lastFrame.Position, targetPos, lastLastFrame.Time, h.StartTime, easing); + lastPosition = lastFrame.Position; + } + // Perform eased movement for (double time = lastFrame.Time + GetFrameDelay(lastFrame.Time); time < h.StartTime; time += GetFrameDelay(time)) { From 6a2c0f772e84cd9dddb3ca9b8f35295956b6d990 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Jul 2021 17:22:48 +0900 Subject: [PATCH 401/457] Always apply easing, adjust heuristic a bit --- .../Replays/OsuAutoGenerator.cs | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index a9b5a2d0ef..0df5909d50 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -240,40 +240,33 @@ namespace osu.Game.Rulesets.Osu.Replays AddFrameToReplay(lastFrame); } - Vector2 lastPosition = lastFrame.Position; - double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime); - // Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up. - if (timeDifference > 0 && // Sanity checks - ((lastPosition - targetPos).Length > h.Radius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough - timeDifference >= 266)) // ... or the beats are slow enough to tap anyway. + OsuReplayFrame lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null; + + // The last frame may be a key-up frame if it shares a position with the second-last frame. + // If it is a key-up frame and its time occurs after the "wait time" (i.e. there was no wait period), adjust its position to begin eased movement instantaneously. + if (lastLastFrame?.Position == lastFrame.Position && lastFrame.Time >= waitTime) { - OsuReplayFrame lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null; - - // The last frame may be a key-up frame if it shares a position with the second-last frame. - // If it is a key-up frame and its time occurs after the "wait time" (i.e. there was no wait period), adjust its position to begin eased movement instantaneously. - if (lastLastFrame?.Position == lastFrame.Position && lastFrame.Time >= waitTime) - { - // [lastLastFrame] ... [lastFrame] ... [current frame] - // We want to find the cursor position at lastFrame, so interpolate between lastLastFrame and the new target position. - lastFrame.Position = Interpolation.ValueAt(lastFrame.Time, lastFrame.Position, targetPos, lastLastFrame.Time, h.StartTime, easing); - lastPosition = lastFrame.Position; - } - - // Perform eased movement - for (double time = lastFrame.Time + GetFrameDelay(lastFrame.Time); time < h.StartTime; time += GetFrameDelay(time)) - { - Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing); - AddFrameToReplay(new OsuReplayFrame((int)time, new Vector2(currentPosition.X, currentPosition.Y)) { Actions = lastFrame.Actions }); - } - - buttonIndex = 0; + // [lastLastFrame] ... [lastFrame] ... [current frame] + // We want to find the cursor position at lastFrame, so interpolate between lastLastFrame and the new target position. + lastFrame.Position = Interpolation.ValueAt(lastFrame.Time, lastFrame.Position, targetPos, lastLastFrame.Time, h.StartTime, easing); } - else + + Vector2 lastPosition = lastFrame.Position; + + // Perform eased movement. + for (double time = lastFrame.Time + GetFrameDelay(lastFrame.Time); time < h.StartTime; time += GetFrameDelay(time)) { + Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing); + AddFrameToReplay(new OsuReplayFrame((int)time, new Vector2(currentPosition.X, currentPosition.Y)) { Actions = lastFrame.Actions }); + } + + // Start alternating once the time separation is too small (equivalent 120BPM @ 1/4 divisor). + if (timeDifference > 0 && timeDifference < 125) buttonIndex++; - } + else + buttonIndex = 0; } /// From 12ca845e55d7f0bf1fe4594a3f0330307b39a780 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Jul 2021 17:24:23 +0900 Subject: [PATCH 402/457] Use marker class (cleanup) --- .../Replays/OsuAutoGenerator.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 0df5909d50..37d3e9d57a 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -233,20 +233,20 @@ namespace osu.Game.Rulesets.Osu.Replays // Wait until Auto could "see and react" to the next note. double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - getReactionTime(h.StartTime - h.TimePreempt)); + bool hasWaited = false; if (waitTime > lastFrame.Time) { lastFrame = new OsuReplayFrame(waitTime, lastFrame.Position) { Actions = lastFrame.Actions }; + hasWaited = true; AddFrameToReplay(lastFrame); } double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime); - OsuReplayFrame lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null; - // The last frame may be a key-up frame if it shares a position with the second-last frame. - // If it is a key-up frame and its time occurs after the "wait time" (i.e. there was no wait period), adjust its position to begin eased movement instantaneously. - if (lastLastFrame?.Position == lastFrame.Position && lastFrame.Time >= waitTime) + // If the last frame is a key-up frame and there has been no wait period, adjust the last frame's position such that it begins eased movement instantaneously. + if (lastLastFrame != null && lastFrame is OsuKeyUpReplayFrame && !hasWaited) { // [lastLastFrame] ... [lastFrame] ... [current frame] // We want to find the cursor position at lastFrame, so interpolate between lastLastFrame and the new target position. @@ -289,7 +289,7 @@ namespace osu.Game.Rulesets.Osu.Replays // TODO: Why do we delay 1 ms if the object is a spinner? There already is KEY_UP_DELAY from hEndTime. double hEndTime = h.GetEndTime() + KEY_UP_DELAY; int endDelay = h is Spinner ? 1 : 0; - var endFrame = new OsuReplayFrame(hEndTime + endDelay, new Vector2(h.StackedEndPosition.X, h.StackedEndPosition.Y)); + var endFrame = new OsuKeyUpReplayFrame(hEndTime + endDelay, new Vector2(h.StackedEndPosition.X, h.StackedEndPosition.Y)); // Decrement because we want the previous frame, not the next one int index = FindInsertionIndex(startFrame) - 1; @@ -386,5 +386,13 @@ namespace osu.Game.Rulesets.Osu.Replays } #endregion + + private class OsuKeyUpReplayFrame : OsuReplayFrame + { + public OsuKeyUpReplayFrame(double time, Vector2 position) + : base(time, position) + { + } + } } } From 229bba14e6261142692bda7bba8bf5cdb7f325eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 17:45:08 +0900 Subject: [PATCH 403/457] Fix master clock becoming incorrectly paused when all spectator players are too far ahead --- .../OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index 94278a47b6..cf0dfbb585 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -130,6 +130,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate // This is a quiet case in which the catchup is done by the master clock, so IsCatchingUp is not set on the player clock. if (timeDelta < -SYNC_TARGET) { + // Importantly, set the clock to a non-catchup state. if this isn't done, updateMasterState may incorrectly pause the master clock + // when it is required to be running (ie. if all players are ahead of the master). + clock.IsCatchingUp = false; clock.Stop(); continue; } From 2b8efe21caeb6c0450388749089183a184d42cf8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Jul 2021 17:52:10 +0900 Subject: [PATCH 404/457] Don't ease with 0 time difference --- .../Replays/OsuAutoGenerator.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 37d3e9d57a..20a5829274 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -245,21 +245,25 @@ namespace osu.Game.Rulesets.Osu.Replays double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime); OsuReplayFrame lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null; - // If the last frame is a key-up frame and there has been no wait period, adjust the last frame's position such that it begins eased movement instantaneously. - if (lastLastFrame != null && lastFrame is OsuKeyUpReplayFrame && !hasWaited) + if (timeDifference > 0) { - // [lastLastFrame] ... [lastFrame] ... [current frame] - // We want to find the cursor position at lastFrame, so interpolate between lastLastFrame and the new target position. - lastFrame.Position = Interpolation.ValueAt(lastFrame.Time, lastFrame.Position, targetPos, lastLastFrame.Time, h.StartTime, easing); - } + // If the last frame is a key-up frame and there has been no wait period, adjust the last frame's position such that it begins eased movement instantaneously. + if (lastLastFrame != null && lastFrame is OsuKeyUpReplayFrame && !hasWaited + && lastFrame.Time > lastLastFrame.Time) // + { + // [lastLastFrame] ... [lastFrame] ... [current frame] + // We want to find the cursor position at lastFrame, so interpolate between lastLastFrame and the new target position. + lastFrame.Position = Interpolation.ValueAt(lastFrame.Time, lastFrame.Position, targetPos, lastLastFrame.Time, h.StartTime, easing); + } - Vector2 lastPosition = lastFrame.Position; + Vector2 lastPosition = lastFrame.Position; - // Perform eased movement. - for (double time = lastFrame.Time + GetFrameDelay(lastFrame.Time); time < h.StartTime; time += GetFrameDelay(time)) - { - Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing); - AddFrameToReplay(new OsuReplayFrame((int)time, new Vector2(currentPosition.X, currentPosition.Y)) { Actions = lastFrame.Actions }); + // Perform eased movement. + for (double time = lastFrame.Time + GetFrameDelay(lastFrame.Time); time < h.StartTime; time += GetFrameDelay(time)) + { + Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing); + AddFrameToReplay(new OsuReplayFrame((int)time, new Vector2(currentPosition.X, currentPosition.Y)) { Actions = lastFrame.Actions }); + } } // Start alternating once the time separation is too small (equivalent 120BPM @ 1/4 divisor). From 7645da7d379b19b6d9a106efd4fc463d0eaccc69 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 18:20:04 +0900 Subject: [PATCH 405/457] Fix incorrect filename --- .../NonVisual/FirstAvailableHitWindowTest.cs | 104 ---------------- .../NonVisual/FirstAvailableHitWindowsTest.cs | 112 ++++++++++++++++++ 2 files changed, 112 insertions(+), 104 deletions(-) create mode 100644 osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs index aed2a5c122..51e15d2426 100644 --- a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs +++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs @@ -1,115 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using NUnit.Framework; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Scoring; namespace osu.Game.Tests.NonVisual { - public class FirstAvailableHitWindowsTest - { - private TestDrawableRuleset testDrawableRuleset; - - [SetUp] - public void Setup() - { - testDrawableRuleset = new TestDrawableRuleset(); - } - - [Test] - public void TestResultIfOnlyParentHitWindowIsEmpty() - { - var testObject = new TestHitObject(HitWindows.Empty); - HitObject nested = new TestHitObject(new HitWindows()); - testObject.AddNested(nested); - testDrawableRuleset.HitObjects = new List { testObject }; - - Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, nested.HitWindows); - } - - [Test] - public void TestResultIfParentHitWindowsIsNotEmpty() - { - var testObject = new TestHitObject(new HitWindows()); - HitObject nested = new TestHitObject(new HitWindows()); - testObject.AddNested(nested); - testDrawableRuleset.HitObjects = new List { testObject }; - - Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, testObject.HitWindows); - } - - [Test] - public void TestResultIfParentAndChildHitWindowsAreEmpty() - { - var firstObject = new TestHitObject(HitWindows.Empty); - HitObject nested = new TestHitObject(HitWindows.Empty); - firstObject.AddNested(nested); - - var secondObject = new TestHitObject(new HitWindows()); - testDrawableRuleset.HitObjects = new List { firstObject, secondObject }; - - Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, secondObject.HitWindows); - } - - [Test] - public void TestResultIfAllHitWindowsAreEmpty() - { - var firstObject = new TestHitObject(HitWindows.Empty); - HitObject nested = new TestHitObject(HitWindows.Empty); - firstObject.AddNested(nested); - - testDrawableRuleset.HitObjects = new List { firstObject }; - - Assert.IsNull(testDrawableRuleset.FirstAvailableHitWindows); - } - - [SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] - private class TestDrawableRuleset : DrawableRuleset - { - public List HitObjects; - public override IEnumerable Objects => HitObjects; - - public override event Action NewResult; - public override event Action RevertResult; - - public override Playfield Playfield { get; } - public override Container Overlays { get; } - public override Container FrameStableComponents { get; } - public override IFrameStableClock FrameStableClock { get; } - internal override bool FrameStablePlayback { get; set; } - public override IReadOnlyList Mods { get; } - - public override double GameplayStartTime { get; } - public override GameplayCursorContainer Cursor { get; } - - public TestDrawableRuleset() - : base(new OsuRuleset()) - { - // won't compile without this. - NewResult?.Invoke(null); - RevertResult?.Invoke(null); - } - - public override void SetReplayScore(Score replayScore) => throw new NotImplementedException(); - - public override void SetRecordTarget(Score score) => throw new NotImplementedException(); - - public override void RequestResume(Action continueResume) => throw new NotImplementedException(); - - public override void CancelResume() => throw new NotImplementedException(); - } - } - public class TestHitObject : HitObject { public TestHitObject(HitWindows hitWindows) diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs new file mode 100644 index 0000000000..d4364b15e7 --- /dev/null +++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs @@ -0,0 +1,112 @@ +// 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.Diagnostics.CodeAnalysis; +using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; + +namespace osu.Game.Tests.NonVisual +{ + public class FirstAvailableHitWindowsTest + { + private TestDrawableRuleset testDrawableRuleset; + + [SetUp] + public void Setup() + { + testDrawableRuleset = new TestDrawableRuleset(); + } + + [Test] + public void TestResultIfOnlyParentHitWindowIsEmpty() + { + var testObject = new TestHitObject(HitWindows.Empty); + HitObject nested = new TestHitObject(new HitWindows()); + testObject.AddNested(nested); + testDrawableRuleset.HitObjects = new List { testObject }; + + Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, nested.HitWindows); + } + + [Test] + public void TestResultIfParentHitWindowsIsNotEmpty() + { + var testObject = new TestHitObject(new HitWindows()); + HitObject nested = new TestHitObject(new HitWindows()); + testObject.AddNested(nested); + testDrawableRuleset.HitObjects = new List { testObject }; + + Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, testObject.HitWindows); + } + + [Test] + public void TestResultIfParentAndChildHitWindowsAreEmpty() + { + var firstObject = new TestHitObject(HitWindows.Empty); + HitObject nested = new TestHitObject(HitWindows.Empty); + firstObject.AddNested(nested); + + var secondObject = new TestHitObject(new HitWindows()); + testDrawableRuleset.HitObjects = new List { firstObject, secondObject }; + + Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, secondObject.HitWindows); + } + + [Test] + public void TestResultIfAllHitWindowsAreEmpty() + { + var firstObject = new TestHitObject(HitWindows.Empty); + HitObject nested = new TestHitObject(HitWindows.Empty); + firstObject.AddNested(nested); + + testDrawableRuleset.HitObjects = new List { firstObject }; + + Assert.IsNull(testDrawableRuleset.FirstAvailableHitWindows); + } + + [SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] + private class TestDrawableRuleset : DrawableRuleset + { + public List HitObjects; + public override IEnumerable Objects => HitObjects; + + public override event Action NewResult; + public override event Action RevertResult; + + public override Playfield Playfield { get; } + public override Container Overlays { get; } + public override Container FrameStableComponents { get; } + public override IFrameStableClock FrameStableClock { get; } + internal override bool FrameStablePlayback { get; set; } + public override IReadOnlyList Mods { get; } + + public override double GameplayStartTime { get; } + public override GameplayCursorContainer Cursor { get; } + + public TestDrawableRuleset() + : base(new OsuRuleset()) + { + // won't compile without this. + NewResult?.Invoke(null); + RevertResult?.Invoke(null); + } + + public override void SetReplayScore(Score replayScore) => throw new NotImplementedException(); + + public override void SetRecordTarget(Score score) => throw new NotImplementedException(); + + public override void RequestResume(Action continueResume) => throw new NotImplementedException(); + + public override void CancelResume() => throw new NotImplementedException(); + } + } +} From 695af31c58d696f4cd6d5897ea56d1c80e3f3167 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Jul 2021 18:38:02 +0900 Subject: [PATCH 406/457] Start alternating at 225BPM as previously --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 20a5829274..68743c2a86 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -266,8 +266,8 @@ namespace osu.Game.Rulesets.Osu.Replays } } - // Start alternating once the time separation is too small (equivalent 120BPM @ 1/4 divisor). - if (timeDifference > 0 && timeDifference < 125) + // Start alternating once the time separation is too small (faster than ~225BPM). + if (timeDifference > 0 && timeDifference < 266) buttonIndex++; else buttonIndex = 0; From 7d6ab08bb3c1bd870216351379587f66482f62fb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Jul 2021 18:49:09 +0900 Subject: [PATCH 407/457] Remove unnecessary conditional --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 68743c2a86..73dbcbd501 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -248,8 +248,7 @@ namespace osu.Game.Rulesets.Osu.Replays if (timeDifference > 0) { // If the last frame is a key-up frame and there has been no wait period, adjust the last frame's position such that it begins eased movement instantaneously. - if (lastLastFrame != null && lastFrame is OsuKeyUpReplayFrame && !hasWaited - && lastFrame.Time > lastLastFrame.Time) // + if (lastLastFrame != null && lastFrame is OsuKeyUpReplayFrame && !hasWaited) { // [lastLastFrame] ... [lastFrame] ... [current frame] // We want to find the cursor position at lastFrame, so interpolate between lastLastFrame and the new target position. From 8b7ccdc8b5c591120f09b30baed07c919082a864 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Jul 2021 18:51:23 +0900 Subject: [PATCH 408/457] Adjust comment --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 73dbcbd501..b88bf9108b 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -257,7 +257,7 @@ namespace osu.Game.Rulesets.Osu.Replays Vector2 lastPosition = lastFrame.Position; - // Perform eased movement. + // Perform the rest of the eased movement until the target position is reached. for (double time = lastFrame.Time + GetFrameDelay(lastFrame.Time); time < h.StartTime; time += GetFrameDelay(time)) { Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing); From cd2916f778c7c740793a1b72502461269851368e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 18:56:59 +0900 Subject: [PATCH 409/457] Fix remaining incorrect filename --- .../NonVisual/FirstAvailableHitWindowTest.cs | 19 ------------------- .../NonVisual/FirstAvailableHitWindowsTest.cs | 11 +++++++++++ 2 files changed, 11 insertions(+), 19 deletions(-) delete mode 100644 osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs deleted file mode 100644 index 51e15d2426..0000000000 --- a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs +++ /dev/null @@ -1,19 +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.Objects; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Tests.NonVisual -{ - public class TestHitObject : HitObject - { - public TestHitObject(HitWindows hitWindows) - { - HitWindows = hitWindows; - HitWindows.SetDifficulty(0.5f); - } - - public new void AddNested(HitObject nested) => base.AddNested(nested); - } -} diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs index d4364b15e7..97105b6b6a 100644 --- a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs +++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs @@ -108,5 +108,16 @@ namespace osu.Game.Tests.NonVisual public override void CancelResume() => throw new NotImplementedException(); } + + public class TestHitObject : HitObject + { + public TestHitObject(HitWindows hitWindows) + { + HitWindows = hitWindows; + HitWindows.SetDifficulty(0.5f); + } + + public new void AddNested(HitObject nested) => base.AddNested(nested); + } } } From 9c2fed4806217e05312892a2e339bce6cebda0eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 19:10:27 +0900 Subject: [PATCH 410/457] Move setup steps to `SetUpSteps` and add empty test case --- .../Multiplayer/TestSceneMultiplayer.cs | 70 +++++++++---------- 1 file changed, 32 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index c93640e7b5..7f3e17985e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -39,11 +39,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private TestMultiplayer multiplayerScreen; private TestMultiplayerClient client; - public TestSceneMultiplayer() - { - loadMultiplayer(); - } - [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { @@ -51,18 +46,43 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); } - [SetUp] - public void Setup() => Schedule(() => + public override void SetUpSteps() { - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); - }); + base.SetUpSteps(); + + AddStep("import beatmap", () => + { + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + }); + + AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer()); + + AddStep("load dependencies", () => + { + client = new TestMultiplayerClient(multiplayerScreen.RoomManager); + + // The screen gets suspended so it stops receiving updates. + Child = client; + + LoadScreen(dependenciesScreen = new DependenciesScreen(client)); + }); + + AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded); + + AddStep("load multiplayer", () => LoadScreen(multiplayerScreen)); + AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded); + } + + [Test] + public void TestEmpty() + { + // used to test the flow of multiplayer from visual tests. + } [Test] public void TestUserSetToIdleWhenBeatmapDeleted() { - loadMultiplayer(); - createRoom(() => new Room { Name = { Value = "Test Room" }, @@ -85,8 +105,6 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap() { - loadMultiplayer(); - createRoom(() => new Room { Name = { Value = "Test Room" }, @@ -123,8 +141,6 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestLocalPlayStartsWhileSpectatingWhenBeatmapBecomesAvailable() { - loadMultiplayer(); - createRoom(() => new Room { Name = { Value = "Test Room" }, @@ -167,8 +183,6 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestLeaveNavigation() { - loadMultiplayer(); - createRoom(() => new Room { Name = { Value = "Test Room" }, @@ -227,26 +241,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for join", () => client.Room != null); } - private void loadMultiplayer() - { - AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer()); - - AddStep("load dependencies", () => - { - client = new TestMultiplayerClient(multiplayerScreen.RoomManager); - - // The screen gets suspended so it stops receiving updates. - Child = client; - - LoadScreen(dependenciesScreen = new DependenciesScreen(client)); - }); - - AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded); - - AddStep("load multiplayer", () => LoadScreen(multiplayerScreen)); - AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded); - } - /// /// Used for the sole purpose of adding as a resolvable dependency. /// From 10e7c846e55a1940ce9230768de1ed0af2f0e45d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 19:41:00 +0900 Subject: [PATCH 411/457] Add local `UserLookupCache` to avoid online requests being fired from multiplayer tests --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 7f3e17985e..2bb3129f68 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -11,6 +11,7 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -39,6 +40,9 @@ namespace osu.Game.Tests.Visual.Multiplayer private TestMultiplayer multiplayerScreen; private TestMultiplayerClient client; + [Cached(typeof(UserLookupCache))] + private UserLookupCache lookupCache = new TestUserLookupCache(); + [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { From 9c311a6d8a60db4a3318478b007b4e7e09dc78c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 19:56:16 +0900 Subject: [PATCH 412/457] Add ability to lookup multiple users at once to `UserLookupCache` --- osu.Game/Database/UserLookupCache.cs | 24 ++++++++++++++++++++ osu.Game/Screens/Spectate/SpectatorScreen.cs | 21 +---------------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs index 19cc211709..e5fd9c82ac 100644 --- a/osu.Game/Database/UserLookupCache.cs +++ b/osu.Game/Database/UserLookupCache.cs @@ -27,6 +27,30 @@ namespace osu.Game.Database [ItemCanBeNull] public Task GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token); + /// + /// Perform an API lookup on the specified users, populating a model. + /// + /// The users to lookup. + /// An optional cancellation token. + /// . + public Task GetUsersAsync(int[] userIds, CancellationToken token = default) + { + var userLookupTasks = new List>(); + + foreach (var u in userIds) + { + userLookupTasks.Add(GetUserAsync(u, token).ContinueWith(task => + { + if (!task.IsCompletedSuccessfully) + return null; + + return task.Result; + }, token)); + } + + return Task.WhenAll(userLookupTasks); + } + protected override async Task ComputeValueAsync(int lookup, CancellationToken token = default) => await queryUser(lookup).ConfigureAwait(false); diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 8fc9222f59..cd386a3d48 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -61,7 +60,7 @@ namespace osu.Game.Screens.Spectate { base.LoadComplete(); - getAllUsers().ContinueWith(users => Schedule(() => + userLookupCache.GetUsersAsync(userIds.ToArray()).ContinueWith(users => Schedule(() => { foreach (var u in users.Result) userMap[u.Id] = u; @@ -77,24 +76,6 @@ namespace osu.Game.Screens.Spectate })); } - private Task getAllUsers() - { - var userLookupTasks = new List>(); - - foreach (var u in userIds) - { - userLookupTasks.Add(userLookupCache.GetUserAsync(u).ContinueWith(task => - { - if (!task.IsCompletedSuccessfully) - return null; - - return task.Result; - })); - } - - return Task.WhenAll(userLookupTasks); - } - private void beatmapUpdated(ValueChangedEvent> e) { if (!e.NewValue.TryGetTarget(out var beatmapSet)) From 77adf687c6946471301a31a0c52e2857fe4cd5a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 19:56:36 +0900 Subject: [PATCH 413/457] Consume ability to lookup multiple users in `MultiplayerGameplayLeaderboard` Avoids syncrhonously blocking on asynchronous operations (which was leading to LCA starvation in tests). --- .../Multiplayer/MultiplayerPlayer.cs | 4 +-- .../HUD/MultiplayerGameplayLeaderboard.cs | 26 +++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 1bbe49a705..043cce4630 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -125,9 +125,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { const float padding = 44; // enough margin to avoid the hit error display. - leaderboard.Position = new Vector2( - padding, - padding + HUDOverlay.TopScoringElementsHeight); + leaderboard.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight); } private void onMatchStarted() => Scheduler.Add(() => diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index c3bfe19b29..45f871ed13 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -53,22 +53,22 @@ namespace osu.Game.Screens.Play.HUD { scoringMode = config.GetBindable(OsuSetting.ScoreDisplayMode); - foreach (var userId in playingUsers) + userLookupCache.GetUsersAsync(playingUsers.ToArray()).ContinueWith(users => Schedule(() => { - // probably won't be required in the final implementation. - var resolvedUser = userLookupCache.GetUserAsync(userId).Result; + foreach (var user in users.Result) + { + var trackedUser = CreateUserData(user.Id, scoreProcessor); + trackedUser.ScoringMode.BindTo(scoringMode); - var trackedUser = CreateUserData(userId, scoreProcessor); - trackedUser.ScoringMode.BindTo(scoringMode); + var leaderboardScore = AddPlayer(user, user.Id == api.LocalUser.Value.Id); + leaderboardScore.Accuracy.BindTo(trackedUser.Accuracy); + leaderboardScore.TotalScore.BindTo(trackedUser.Score); + leaderboardScore.Combo.BindTo(trackedUser.CurrentCombo); + leaderboardScore.HasQuit.BindTo(trackedUser.UserQuit); - var leaderboardScore = AddPlayer(resolvedUser, resolvedUser?.Id == api.LocalUser.Value.Id); - leaderboardScore.Accuracy.BindTo(trackedUser.Accuracy); - leaderboardScore.TotalScore.BindTo(trackedUser.Score); - leaderboardScore.Combo.BindTo(trackedUser.CurrentCombo); - leaderboardScore.HasQuit.BindTo(trackedUser.UserQuit); - - UserScores[userId] = trackedUser; - } + UserScores[user.Id] = trackedUser; + } + })); } protected override void LoadComplete() From d247b8042e2ccc4f1e54e308887737c41259ee6e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 5 Jul 2021 20:05:08 +0900 Subject: [PATCH 414/457] Fix default skin catcher not flipping catcher plate When legacy beatmap skin is present but catcher is not provided, it was using the legacy setting (always false). --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index b6f6e91c29..5e744ec001 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -108,8 +108,11 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy switch (config) { case CatchSkinConfiguration.FlipCatcherPlate: - // Always return `false` (don't flip catcher plate contents) for a legacy skin. - return (IBindable)new Bindable(); + // Don't flip catcher plate contents if the catcher is provided by this legacy skin. + if (GetDrawableComponent(new CatchSkinComponent(CatchSkinComponents.Catcher)) != null) + return (IBindable)new Bindable(); + + break; } break; From 459f9a04654da0e7142601efa74503ac0e9a99ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 21:30:24 +0900 Subject: [PATCH 415/457] Handle nulls and fix missing documentation --- osu.Game/Database/UserLookupCache.cs | 2 +- osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs | 3 +++ osu.Game/Screens/Spectate/SpectatorScreen.cs | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs index e5fd9c82ac..13c37ddfe9 100644 --- a/osu.Game/Database/UserLookupCache.cs +++ b/osu.Game/Database/UserLookupCache.cs @@ -32,7 +32,7 @@ namespace osu.Game.Database /// /// The users to lookup. /// An optional cancellation token. - /// . + /// The populated users. May include null results for failed retrievals. public Task GetUsersAsync(int[] userIds, CancellationToken token = default) { var userLookupTasks = new List>(); diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 45f871ed13..a32aca51c5 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -57,6 +57,9 @@ namespace osu.Game.Screens.Play.HUD { foreach (var user in users.Result) { + if (user == null) + continue; + var trackedUser = CreateUserData(user.Id, scoreProcessor); trackedUser.ScoringMode.BindTo(scoringMode); diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index cd386a3d48..b6eafe496f 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -63,7 +63,12 @@ namespace osu.Game.Screens.Spectate userLookupCache.GetUsersAsync(userIds.ToArray()).ContinueWith(users => Schedule(() => { foreach (var u in users.Result) + { + if (u == null) + continue; + userMap[u.Id] = u; + } playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); From f1014af2848b9875a9f9db1e60e13e7a110a410d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 21:35:36 +0900 Subject: [PATCH 416/457] Move `LoadComplete` content to run after user retrieval has completed Feels safer, I think. --- osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index a32aca51c5..0c13e87f9f 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -71,13 +71,13 @@ namespace osu.Game.Screens.Play.HUD UserScores[user.Id] = trackedUser; } + + prepareDataStreams(); })); } - protected override void LoadComplete() + private void prepareDataStreams() { - base.LoadComplete(); - // BindableList handles binding in a really bad way (Clear then AddRange) so we need to do this manually.. foreach (int userId in playingUsers) { From fdfdfeecab2a4775f7b6b0733421112d14c4446b Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Mon, 5 Jul 2021 23:35:17 +0800 Subject: [PATCH 417/457] Suppress IDE0057 --- .editorconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index f4d7e08d08..bbe091a852 100644 --- a/.editorconfig +++ b/.editorconfig @@ -168,8 +168,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent #Style - C# 8 features csharp_prefer_static_local_function = true:warning csharp_prefer_simple_using_statement = true:silent -csharp_style_prefer_index_operator = true:warning -csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_index_operator = false:silent +csharp_style_prefer_range_operator = false:silent csharp_style_prefer_switch_expression = false:none #Supressing roslyn built-in analyzers From a4545051f2463fdeeff7567ba0687fe7c96388e4 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Mon, 5 Jul 2021 23:36:15 +0800 Subject: [PATCH 418/457] Suppress IDE0042 --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index bbe091a852..19bd89c52f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -157,7 +157,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning #Style - variable declaration csharp_style_inlined_variable_declaration = true:warning -csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_deconstructed_variable_declaration = false:silent #Style - other C# 7.x features dotnet_style_prefer_inferred_tuple_names = true:warning From e1c646b9b297397bbb02fed8acb6b8e70cfe4158 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Mon, 5 Jul 2021 23:52:39 +0800 Subject: [PATCH 419/457] Remove redundant arguments --- .../TestSceneHyperDashColouring.cs | 2 +- .../TestSceneGameplayCursor.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 6 ++--- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 4 ++-- .../Skinning/Default/DefaultSpinnerDisc.cs | 14 ++++++------ .../Skinning/Default/SliderBall.cs | 2 +- .../Skinning/Legacy/LegacyNewStyleSpinner.cs | 8 +++---- .../Skinning/Legacy/LegacyOldStyleSpinner.cs | 4 ++-- .../Skinning/Legacy/LegacySpinner.cs | 8 +++---- .../Objects/Drawables/DrawableSwell.cs | 2 +- .../Beatmaps/SliderEventGenerationTest.cs | 10 ++++----- .../TestSceneCompletionCancellation.cs | 5 ----- .../Gameplay/TestSceneSkinnableDrawable.cs | 2 +- .../Gameplay/TestSceneSpectatorPlayback.cs | 2 +- .../TestSceneMultiSpectatorScreen.cs | 4 ++-- .../TestSceneProfileSubsectionHeader.cs | 2 +- .../Screens/Gameplay/GameplayScreen.cs | 2 +- .../Containers/BeatSyncedContainer.cs | 2 +- .../Graphics/UserInterface/StarCounter.cs | 2 +- .../Requests/GetSpotlightRankingsRequest.cs | 2 +- osu.Game/Online/Leaderboards/Leaderboard.cs | 2 +- .../Online/Leaderboards/LeaderboardScore.cs | 6 ++--- osu.Game/Overlays/MedalOverlay.cs | 4 ++-- osu.Game/Overlays/Mods/ModButton.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- osu.Game/Overlays/News/NewsHeader.cs | 2 +- .../Header/Components/PreviousUsernames.cs | 2 +- .../Overlays/Rankings/Tables/RankingsTable.cs | 2 +- .../Rulesets/Judgements/DrawableJudgement.cs | 2 +- osu.Game/Rulesets/Mods/ModBarrelRoll.cs | 2 +- .../Objects/Drawables/DrawableHitObject.cs | 6 ++--- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- osu.Game/Screens/Menu/Disclaimer.cs | 2 +- osu.Game/Screens/Menu/IntroSequence.cs | 14 ++++++------ osu.Game/Screens/Menu/IntroTriangles.cs | 22 +++++++++---------- osu.Game/Screens/Menu/IntroWelcome.cs | 2 +- .../Match/MultiplayerMatchSettingsOverlay.cs | 2 +- .../PlaylistsMatchSettingsOverlay.cs | 2 +- osu.Game/Screens/Play/BreakOverlay.cs | 4 ++-- .../Screens/Play/HUD/LegacyComboCounter.cs | 10 ++++----- .../Expanded/ExpandedPanelMiddleContent.cs | 4 ++-- .../Expanded/Statistics/ComboStatistic.cs | 2 +- osu.Game/Screens/Ranking/ScorePanel.cs | 2 +- osu.Game/Screens/ScreenWhiteBox.cs | 2 +- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 2 +- osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 50 files changed, 97 insertions(+), 102 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 7fa981d492..e7b0259ea2 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("finish hyper-dashing", () => { - catcherArea.MovableCatcher.SetHyperDashState(1); + catcherArea.MovableCatcher.SetHyperDashState(); catcherArea.MovableCatcher.FinishTransforms(); }); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index 78bb88322a..2326a0c391 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Tests { case OsuSkinConfiguration osuLookup: if (osuLookup == OsuSkinConfiguration.CursorCentre) - return SkinUtils.As(new BindableBool(false)); + return SkinUtils.As(new BindableBool()); break; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index ebf6f9dda7..636cd63c69 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -158,17 +158,17 @@ namespace osu.Game.Rulesets.Osu.Mods var firstObj = beatmap.HitObjects[0]; var startDelay = firstObj.StartTime - firstObj.TimePreempt; - using (BeginAbsoluteSequence(startDelay + break_close_late, true)) + using (BeginAbsoluteSequence(startDelay + break_close_late)) leaveBreak(); foreach (var breakInfo in beatmap.Breaks) { if (breakInfo.HasEffect) { - using (BeginAbsoluteSequence(breakInfo.StartTime - break_open_early, true)) + using (BeginAbsoluteSequence(breakInfo.StartTime - break_open_early)) { enterBreak(); - using (BeginDelayedSequence(breakInfo.Duration + break_open_early + break_close_late, true)) + using (BeginDelayedSequence(breakInfo.Duration + break_open_early + break_close_late)) leaveBreak(); } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index 56c246953e..95e7d13ee7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) { circle.ApproachCircle.Hide(); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index a05e4dea03..07ce009cf8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable hitCircle = null) { var h = hitObject.HitObject; - using (hitObject.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + using (hitObject.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) (hitCircle ?? hitObject).Hide(); } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index b5905d7015..8122ab563e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Mods double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1; double moveDuration = hitObject.TimePreempt + 1; - using (drawable.BeginAbsoluteSequence(appearTime, true)) + using (drawable.BeginAbsoluteSequence(appearTime)) { drawable .MoveToOffset(appearOffset) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index a01cec4bb3..ff6ba6e121 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Mods for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration, true)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration)) wiggle(); } @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Mods for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration, true)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration)) wiggle(); } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs index 542f3eff0d..4ea0831627 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs @@ -130,18 +130,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default Spinner spinner = drawableSpinner.HitObject; - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) { this.ScaleTo(initial_scale); this.RotateTo(0); - using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) + using (BeginDelayedSequence(spinner.TimePreempt / 2)) { // constant ambient rotation to give the spinner "spinning" character. this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration); } - using (BeginDelayedSequence(spinner.TimePreempt + spinner.Duration + drawableHitObject.Result.TimeOffset, true)) + using (BeginDelayedSequence(spinner.TimePreempt + spinner.Duration + drawableHitObject.Result.TimeOffset)) { switch (state) { @@ -157,17 +157,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default } } - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) { centre.ScaleTo(0); mainContainer.ScaleTo(0); - using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) + using (BeginDelayedSequence(spinner.TimePreempt / 2)) { centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint); mainContainer.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint); - using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) + using (BeginDelayedSequence(spinner.TimePreempt / 2)) { centre.ScaleTo(0.5f, spinner.TimePreempt / 2, Easing.OutQuint); mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint); @@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default } // transforms we have from completing the spinner will be rolled back, so reapply immediately. - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) updateComplete(state == ArmedState.Hit, 0); } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index 8feeca56e8..1f988df947 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default public override void ApplyTransformsAt(double time, bool propagateChildren = false) { // For the same reasons as above w.r.t rewinding, we shouldn't propagate to children here either. - base.ApplyTransformsAt(time, false); + base.ApplyTransformsAt(time); } private bool tracking; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs index ae8d6a61f8..1e170036e4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs @@ -100,17 +100,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy case DrawableSpinner d: Spinner spinner = d.HitObject; - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) this.FadeOut(); - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2)) this.FadeInFromZero(spinner.TimeFadeIn / 2); - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) { fixedMiddle.FadeColour(Color4.White); - using (BeginDelayedSequence(spinner.TimePreempt, true)) + using (BeginDelayedSequence(spinner.TimePreempt)) fixedMiddle.FadeColour(Color4.Red, spinner.Duration); } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs index cbe721d21d..e3e8f3ce88 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs @@ -89,10 +89,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Spinner spinner = d.HitObject; - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) this.FadeOut(); - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2)) this.FadeInFromZero(spinner.TimeFadeIn / 2); } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index b2311dcb91..93aba608e6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { double startTime = Math.Min(Time.Current, DrawableSpinner.HitStateUpdateTime - 400); - using (BeginAbsoluteSequence(startTime, true)) + using (BeginAbsoluteSequence(startTime)) { clear.FadeInFromZero(400, Easing.Out); @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } const double fade_out_duration = 50; - using (BeginAbsoluteSequence(DrawableSpinner.HitStateUpdateTime - fade_out_duration, true)) + using (BeginAbsoluteSequence(DrawableSpinner.HitStateUpdateTime - fade_out_duration)) clear.FadeOut(fade_out_duration); } else @@ -182,14 +182,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy double spinFadeOutLength = Math.Min(400, d.HitObject.Duration); - using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - spinFadeOutLength, true)) + using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - spinFadeOutLength)) spin.FadeOutFromOne(spinFadeOutLength); break; case DrawableSpinnerTick d: if (state == ArmedState.Hit) { - using (BeginAbsoluteSequence(d.HitStateUpdateTime, true)) + using (BeginAbsoluteSequence(d.HitStateUpdateTime)) spin.FadeOut(300); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 60f9521996..888f47d341 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { base.UpdateStartTimeStateTransforms(); - using (BeginDelayedSequence(-ring_appear_offset, true)) + using (BeginDelayedSequence(-ring_appear_offset)) targetRing.ScaleTo(target_ring_scale, 400, Easing.OutQuint); } diff --git a/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs index 6c8133660f..9fba0f1668 100644 --- a/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs +++ b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestSingleSpan() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null, default).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestRepeat() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null, default).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestNonEvenTicks() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null, default).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestLegacyLastTickOffset() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100, default).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray(); Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick)); Assert.That(events[2].Time, Is.EqualTo(900)); @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Beatmaps const double velocity = 5; const double min_distance = velocity * 10; - var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0, default).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray(); Assert.Multiple(() => { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs index 4ee48fd853..11bd701e19 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs @@ -114,11 +114,6 @@ namespace osu.Game.Tests.Visual.Gameplay { public bool ResultsCreated { get; private set; } - public FakeRankingPushPlayer() - : base(true, true) - { - } - protected override ResultsScreen CreateResults(ScoreInfo score) { var results = base.CreateResults(score); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index f29fbbf52b..02b1959dab 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Gameplay Children = new[] { new ExposedSkinnableDrawable("default", _ => new DefaultBox()), - new ExposedSkinnableDrawable("available", _ => new DefaultBox(), ConfineMode.ScaleToFit), + new ExposedSkinnableDrawable("available", _ => new DefaultBox()), new ExposedSkinnableDrawable("available", _ => new DefaultBox(), ConfineMode.NoScaling) } }, diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 469f594fdc..bb577886cc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -179,7 +179,7 @@ namespace osu.Game.Tests.Visual.Gameplay foreach (var legacyFrame in frames.Frames) { var frame = new TestReplayFrame(); - frame.FromLegacy(legacyFrame, null, null); + frame.FromLegacy(legacyFrame, null); replay.Frames.Add(frame); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index b8db4067fb..072e32370d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -137,7 +137,7 @@ namespace osu.Game.Tests.Visual.Multiplayer // Send initial frames for both players. A few more for player 1. sendFrames(PLAYER_1_ID, 20); - sendFrames(PLAYER_2_ID, 10); + sendFrames(PLAYER_2_ID); checkPausedInstant(PLAYER_1_ID, false); checkPausedInstant(PLAYER_2_ID, false); @@ -194,7 +194,7 @@ namespace osu.Game.Tests.Visual.Multiplayer assertMuted(PLAYER_1_ID, true); assertMuted(PLAYER_2_ID, true); - sendFrames(PLAYER_1_ID, 10); + sendFrames(PLAYER_1_ID); sendFrames(PLAYER_2_ID, 20); checkPaused(PLAYER_1_ID, false); assertOneNotMuted(); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs index cd226662d7..4ce684d5af 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestInitialVisibility() { - AddStep("Create header with 0 value", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenZero, 0)); + AddStep("Create header with 0 value", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenZero)); AddAssert("Value is 0", () => header.Current.Value == 0); AddAssert("Counter is visible", () => header.ChildrenOfType().First().Alpha == 1); diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index e4ec45c00e..6e4c6784c8 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -172,7 +172,7 @@ namespace osu.Game.Tournament.Screens.Gameplay { chat?.Contract(); - using (BeginDelayedSequence(300, true)) + using (BeginDelayedSequence(300)) { scoreDisplay.FadeIn(100); SongBar.Expanded = true; diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 1c9cdc174a..e2a0c09a6b 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -97,7 +97,7 @@ namespace osu.Game.Graphics.Containers if (timingPoint == lastTimingPoint && beatIndex == lastBeat) return; - using (BeginDelayedSequence(-TimeSinceLastBeat, true)) + using (BeginDelayedSequence(-TimeSinceLastBeat)) OnNewBeat(beatIndex, timingPoint, effectPoint, track?.CurrentAmplitudes ?? ChannelAmplitudes.Empty); lastBeat = beatIndex; diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index 894a21fcf3..32b788b5dc 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -113,7 +113,7 @@ namespace osu.Game.Graphics.UserInterface double delay = (current <= newValue ? Math.Max(i - current, 0) : Math.Max(current - 1 - i, 0)) * AnimationDelay; - using (star.BeginDelayedSequence(delay, true)) + using (star.BeginDelayedSequence(delay)) star.DisplayAt(getStarScale(i, newValue)); } } diff --git a/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs b/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs index 25e6b3f1af..20856c2768 100644 --- a/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs @@ -13,7 +13,7 @@ namespace osu.Game.Online.API.Requests private readonly RankingsSortCriteria sort; public GetSpotlightRankingsRequest(RulesetInfo ruleset, int spotlight, RankingsSortCriteria sort) - : base(ruleset, 1) + : base(ruleset) { this.spotlight = spotlight; this.sort = sort; diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 70e38e421d..4f8b27602b 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -82,7 +82,7 @@ namespace osu.Game.Online.Leaderboards foreach (var s in scrollFlow.Children) { - using (s.BeginDelayedSequence(i++ * 50, true)) + using (s.BeginDelayedSequence(i++ * 50)) s.Show(); } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index e35d3d6461..7108a23e44 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -248,7 +248,7 @@ namespace osu.Game.Online.Leaderboards this.FadeIn(200); content.MoveToY(0, 800, Easing.OutQuint); - using (BeginDelayedSequence(100, true)) + using (BeginDelayedSequence(100)) { avatar.FadeIn(300, Easing.OutQuint); nameLabel.FadeIn(350, Easing.OutQuint); @@ -256,12 +256,12 @@ namespace osu.Game.Online.Leaderboards avatar.MoveToX(0, 300, Easing.OutQuint); nameLabel.MoveToX(0, 350, Easing.OutQuint); - using (BeginDelayedSequence(250, true)) + using (BeginDelayedSequence(250)) { scoreLabel.FadeIn(200); scoreRank.FadeIn(200); - using (BeginDelayedSequence(50, true)) + using (BeginDelayedSequence(50)) { var drawables = new Drawable[] { flagBadgeContainer, modsContainer }.Concat(statisticsLabels).ToArray(); for (int i = 0; i < drawables.Length; i++) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 0feae16b68..e15625a4b3 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -213,7 +213,7 @@ namespace osu.Game.Overlays innerSpin.Spin(20000, RotationDirection.Clockwise); outerSpin.Spin(40000, RotationDirection.Clockwise); - using (BeginDelayedSequence(200, true)) + using (BeginDelayedSequence(200)) { disc.FadeIn(initial_duration) .ScaleTo(1f, initial_duration * 2, Easing.OutElastic); @@ -221,7 +221,7 @@ namespace osu.Game.Overlays particleContainer.FadeIn(initial_duration); outerSpin.FadeTo(0.1f, initial_duration * 2); - using (BeginDelayedSequence(initial_duration + 200, true)) + using (BeginDelayedSequence(initial_duration + 200)) { backgroundStrip.FadeIn(step_duration); leftStrip.ResizeWidthTo(1f, step_duration, Easing.OutQuint); diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index d0bd24496a..572ff0d1aa 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -91,7 +91,7 @@ namespace osu.Game.Overlays.Mods backgroundIcon.Mod = newSelection; - using (BeginDelayedSequence(mod_switch_duration, true)) + using (BeginDelayedSequence(mod_switch_duration)) { foregroundIcon .RotateTo(-rotate_angle * direction) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e4aab978fc..6c47b92d29 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -128,7 +128,7 @@ namespace osu.Game.Overlays.Mods RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 90), - new Dimension(GridSizeMode.Distributed), + new Dimension(), new Dimension(GridSizeMode.AutoSize), }, Content = new[] diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index 94bfd62c32..56c54425bd 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.News public Action ShowFrontPage; - private readonly Bindable article = new Bindable(null); + private readonly Bindable article = new Bindable(); public NewsHeader() { diff --git a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs index e4c0fe3a5a..3cdf110090 100644 --- a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs +++ b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs @@ -57,7 +57,7 @@ namespace osu.Game.Overlays.Profile.Header.Components ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Distributed) + new Dimension() }, Content = new[] { diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 943897581e..e8a76c64ec 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Rankings.Tables private static TableColumn[] mainHeaders => new[] { new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.Absolute, 40)), // place - new TableColumn(string.Empty, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and username (country name) + new TableColumn(string.Empty, Anchor.CentreLeft, new Dimension()), // flag and username (country name) }; protected abstract TableColumn[] CreateAdditionalHeaders(); diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 8a57b4af91..0f22d35bb5 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Judgements LifetimeStart = Result.TimeAbsolute; - using (BeginAbsoluteSequence(Result.TimeAbsolute, true)) + using (BeginAbsoluteSequence(Result.TimeAbsolute)) { // not sure if this should remain going forward. JudgementBody.ResetAnimation(); diff --git a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs index 0d344b5269..872daadd46 100644 --- a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs +++ b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods }; [SettingSource("Direction", "The direction of rotation")] - public Bindable Direction { get; } = new Bindable(RotationDirection.Clockwise); + public Bindable Direction { get; } = new Bindable(); public override string Name => "Barrel Roll"; public override string Acronym => "BR"; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index a0717ec38e..c5db806918 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -404,13 +404,13 @@ namespace osu.Game.Rulesets.Objects.Drawables clearExistingStateTransforms(); - using (BeginAbsoluteSequence(transformTime, true)) + using (BeginAbsoluteSequence(transformTime)) UpdateInitialTransforms(); - using (BeginAbsoluteSequence(StateUpdateTime, true)) + using (BeginAbsoluteSequence(StateUpdateTime)) UpdateStartTimeStateTransforms(); - using (BeginAbsoluteSequence(HitStateUpdateTime, true)) + using (BeginAbsoluteSequence(HitStateUpdateTime)) UpdateHitStateTransforms(newState); state.Value = newState; diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 38290a6530..bdb0157746 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -297,7 +297,7 @@ namespace osu.Game.Screens.Menu Logger.Log($"{nameof(ButtonSystem)}'s state changed from {lastState} to {state}"); - using (buttonArea.BeginDelayedSequence(lastState == ButtonSystemState.Initial ? 150 : 0, true)) + using (buttonArea.BeginDelayedSequence(lastState == ButtonSystemState.Initial ? 150 : 0)) { buttonArea.ButtonSystemState = state; diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index a71b53157a..7f34e1e395 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -181,7 +181,7 @@ namespace osu.Game.Screens.Menu icon.Delay(500).FadeIn(500).ScaleTo(1, 500, Easing.OutQuint); - using (BeginDelayedSequence(3000, true)) + using (BeginDelayedSequence(3000)) { icon.FadeColour(iconColour, 200, Easing.OutQuint); icon.MoveToY(icon_y * 1.3f, 500, Easing.OutCirc) diff --git a/osu.Game/Screens/Menu/IntroSequence.cs b/osu.Game/Screens/Menu/IntroSequence.cs index d92d38da45..3a5cd6857a 100644 --- a/osu.Game/Screens/Menu/IntroSequence.cs +++ b/osu.Game/Screens/Menu/IntroSequence.cs @@ -189,7 +189,7 @@ namespace osu.Game.Screens.Menu double remainingTime() => length - TransformDelay; - using (BeginDelayedSequence(250, true)) + using (BeginDelayedSequence(250)) { welcomeText.FadeIn(700); welcomeText.TransformSpacingTo(new Vector2(20, 0), remainingTime(), Easing.Out); @@ -212,17 +212,17 @@ namespace osu.Game.Screens.Menu lineBottomLeft.MoveTo(new Vector2(-line_end_offset, line_end_offset), line_duration, Easing.OutQuint); lineBottomRight.MoveTo(new Vector2(line_end_offset, line_end_offset), line_duration, Easing.OutQuint); - using (BeginDelayedSequence(length * 0.56, true)) + using (BeginDelayedSequence(length * 0.56)) { bigRing.ResizeTo(logo_size, 500, Easing.InOutQuint); bigRing.Foreground.Delay(250).ResizeTo(1, 850, Easing.OutQuint); - using (BeginDelayedSequence(250, true)) + using (BeginDelayedSequence(250)) { backgroundFill.ResizeHeightTo(1, remainingTime(), Easing.InOutQuart); backgroundFill.RotateTo(-90, remainingTime(), Easing.InOutQuart); - using (BeginDelayedSequence(50, true)) + using (BeginDelayedSequence(50)) { foregroundFill.ResizeWidthTo(1, remainingTime(), Easing.InOutQuart); foregroundFill.RotateTo(-90, remainingTime(), Easing.InOutQuart); @@ -239,19 +239,19 @@ namespace osu.Game.Screens.Menu purpleCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart); purpleCircle.ResizeTo(circle_size, remainingTime(), Easing.InOutQuart); - using (BeginDelayedSequence(appear_delay, true)) + using (BeginDelayedSequence(appear_delay)) { yellowCircle.MoveToY(-circle_size / 2, remainingTime(), Easing.InOutQuart); yellowCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart); yellowCircle.ResizeTo(circle_size, remainingTime(), Easing.InOutQuart); - using (BeginDelayedSequence(appear_delay, true)) + using (BeginDelayedSequence(appear_delay)) { blueCircle.MoveToX(-circle_size / 2, remainingTime(), Easing.InOutQuart); blueCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart); blueCircle.ResizeTo(circle_size, remainingTime(), Easing.InOutQuart); - using (BeginDelayedSequence(appear_delay, true)) + using (BeginDelayedSequence(appear_delay)) { pinkCircle.MoveToX(circle_size / 2, remainingTime(), Easing.InOutQuart); pinkCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart); diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index abe6c62461..0ea83fe5e7 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -172,27 +172,27 @@ namespace osu.Game.Screens.Menu lazerLogo.Hide(); background.ApplyToBackground(b => b.Hide()); - using (BeginAbsoluteSequence(0, true)) + using (BeginAbsoluteSequence(0)) { - using (BeginDelayedSequence(text_1, true)) + using (BeginDelayedSequence(text_1)) welcomeText.FadeIn().OnComplete(t => t.Text = "wel"); - using (BeginDelayedSequence(text_2, true)) + using (BeginDelayedSequence(text_2)) welcomeText.FadeIn().OnComplete(t => t.Text = "welcome"); - using (BeginDelayedSequence(text_3, true)) + using (BeginDelayedSequence(text_3)) welcomeText.FadeIn().OnComplete(t => t.Text = "welcome to"); - using (BeginDelayedSequence(text_4, true)) + using (BeginDelayedSequence(text_4)) { welcomeText.FadeIn().OnComplete(t => t.Text = "welcome to osu!"); welcomeText.TransformTo(nameof(welcomeText.Spacing), new Vector2(50, 0), 5000); } - using (BeginDelayedSequence(text_glitch, true)) + using (BeginDelayedSequence(text_glitch)) triangles.FadeIn(); - using (BeginDelayedSequence(rulesets_1, true)) + using (BeginDelayedSequence(rulesets_1)) { rulesetsScale.ScaleTo(0.8f, 1000); rulesets.FadeIn().ScaleTo(1).TransformSpacingTo(new Vector2(200, 0)); @@ -200,18 +200,18 @@ namespace osu.Game.Screens.Menu triangles.FadeOut(); } - using (BeginDelayedSequence(rulesets_2, true)) + using (BeginDelayedSequence(rulesets_2)) { rulesets.ScaleTo(2).TransformSpacingTo(new Vector2(30, 0)); } - using (BeginDelayedSequence(rulesets_3, true)) + using (BeginDelayedSequence(rulesets_3)) { rulesets.ScaleTo(4).TransformSpacingTo(new Vector2(10, 0)); rulesetsScale.ScaleTo(1.3f, 1000); } - using (BeginDelayedSequence(logo_1, true)) + using (BeginDelayedSequence(logo_1)) { rulesets.FadeOut(); @@ -223,7 +223,7 @@ namespace osu.Game.Screens.Menu logoContainerSecondary.ScaleTo(scale_start).Then().ScaleTo(scale_start - scale_adjust * 0.25f, logo_scale_duration, Easing.InQuad); } - using (BeginDelayedSequence(logo_2, true)) + using (BeginDelayedSequence(logo_2)) { lazerLogo.FadeOut().OnComplete(_ => { diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 521e863683..f74043b045 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Menu { base.LoadComplete(); - using (BeginDelayedSequence(0, true)) + using (BeginDelayedSequence(0)) { scaleContainer.ScaleTo(0.9f).ScaleTo(1, delay_step_two).OnComplete(_ => Expire()); scaleContainer.FadeInFromZero(1800); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index fe9979b161..2e180f31fd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -93,7 +93,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match RelativeSizeAxes = Axes.Both, RowDimensions = new[] { - new Dimension(GridSizeMode.Distributed), + new Dimension(), new Dimension(GridSizeMode.AutoSize), }, Content = new[] diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs index 5062a296a8..5eb2b545cb 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists RelativeSizeAxes = Axes.Both, RowDimensions = new[] { - new Dimension(GridSizeMode.Distributed), + new Dimension(), new Dimension(GridSizeMode.AutoSize), }, Content = new[] diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 36f825b8f6..1665ee83ae 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -126,7 +126,7 @@ namespace osu.Game.Screens.Play if (!b.HasEffect) continue; - using (BeginAbsoluteSequence(b.StartTime, true)) + using (BeginAbsoluteSequence(b.StartTime)) { fadeContainer.FadeIn(BREAK_FADE_DURATION); breakArrows.Show(BREAK_FADE_DURATION); @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Play remainingTimeCounter.CountTo(b.Duration).CountTo(0, b.Duration); - using (BeginDelayedSequence(b.Duration - BREAK_FADE_DURATION, true)) + using (BeginDelayedSequence(b.Duration - BREAK_FADE_DURATION)) { fadeContainer.FadeOut(BREAK_FADE_DURATION); breakArrows.Hide(BREAK_FADE_DURATION); diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index acff949353..edddc06746 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -170,9 +170,9 @@ namespace osu.Game.Screens.Play.HUD popOutCount.FadeTo(0.75f); popOutCount.MoveTo(Vector2.Zero); - popOutCount.ScaleTo(1, pop_out_duration, pop_out_easing); - popOutCount.FadeOut(pop_out_duration, pop_out_easing); - popOutCount.MoveTo(displayedCountSpriteText.Position, pop_out_duration, pop_out_easing); + popOutCount.ScaleTo(1, pop_out_duration); + popOutCount.FadeOut(pop_out_duration); + popOutCount.MoveTo(displayedCountSpriteText.Position, pop_out_duration); } private void transformNoPopOut(int newValue) @@ -186,7 +186,7 @@ namespace osu.Game.Screens.Play.HUD { ((IHasText)displayedCountSpriteText).Text = formatCount(newValue); displayedCountSpriteText.ScaleTo(1.1f); - displayedCountSpriteText.ScaleTo(1, pop_out_duration, pop_out_easing); + displayedCountSpriteText.ScaleTo(1, pop_out_duration); } private void scheduledPopOutSmall(uint id) @@ -261,7 +261,7 @@ namespace osu.Game.Screens.Play.HUD } private void transformRoll(int currentValue, int newValue) => - this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue), Easing.None); + this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue)); private string formatCount(int count) => $@"{count}x"; diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 0a10eee644..4d3f7a4184 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -256,7 +256,7 @@ namespace osu.Game.Screens.Ranking.Expanded // Score counter value setting must be scheduled so it isn't transferred instantaneously ScheduleAfterChildren(() => { - using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DELAY, true)) + using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DELAY)) { scoreCounter.FadeIn(); scoreCounter.Current = scoreManager.GetBindableTotalScore(score); @@ -265,7 +265,7 @@ namespace osu.Game.Screens.Ranking.Expanded foreach (var stat in statisticDisplays) { - using (BeginDelayedSequence(delay, true)) + using (BeginDelayedSequence(delay)) stat.Appear(); delay += 200; diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs index e13138c5a0..b92c244174 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics if (isPerfect) { - using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DURATION / 2, true)) + using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DURATION / 2)) perfectText.FadeIn(50); } } diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index f66a998db6..6ddecf8297 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -234,7 +234,7 @@ namespace osu.Game.Screens.Ranking bool topLayerExpanded = topLayerContainer.Y < 0; // If the top layer was already expanded, then we don't need to wait for the resize and can instead transform immediately. This looks better when changing the panel state. - using (BeginDelayedSequence(topLayerExpanded ? 0 : RESIZE_DURATION + TOP_LAYER_EXPAND_DELAY, true)) + using (BeginDelayedSequence(topLayerExpanded ? 0 : RESIZE_DURATION + TOP_LAYER_EXPAND_DELAY)) { topLayerContainer.FadeIn(); diff --git a/osu.Game/Screens/ScreenWhiteBox.cs b/osu.Game/Screens/ScreenWhiteBox.cs index cf0c183766..8b38b67f5c 100644 --- a/osu.Game/Screens/ScreenWhiteBox.cs +++ b/osu.Game/Screens/ScreenWhiteBox.cs @@ -191,7 +191,7 @@ namespace osu.Game.Screens boxContainer.ScaleTo(0.2f); boxContainer.RotateTo(-20); - using (BeginDelayedSequence(300, true)) + using (BeginDelayedSequence(300)) { boxContainer.ScaleTo(1, transition_time, Easing.OutElastic); boxContainer.RotateTo(0, transition_time / 2, Easing.OutQuint); diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index ca25efaa01..e76f251ce5 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -72,7 +72,7 @@ namespace osu.Game.Skinning if (particles != null) { // start the particles already some way into their animation to break cluster away from centre. - using (particles.BeginDelayedSequence(-100, true)) + using (particles.BeginDelayedSequence(-100)) particles.Restart(); } diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index d7b02ef797..a393802309 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual private readonly WorkingBeatmap testBeatmap; public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host, WorkingBeatmap defaultBeatmap, WorkingBeatmap testBeatmap) - : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap, false) + : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap) { this.testBeatmap = testBeatmap; } diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 98aad821ce..57e400a77e 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -176,7 +176,7 @@ namespace osu.Game.Tests.Visual protected virtual IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset); protected WorkingBeatmap CreateWorkingBeatmap(RulesetInfo ruleset) => - CreateWorkingBeatmap(CreateBeatmap(ruleset), null); + CreateWorkingBeatmap(CreateBeatmap(ruleset)); protected virtual WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, Clock, Audio); From f5ddd2a53bd0db4d49e6352a386ae62bf92d6d31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 01:15:30 +0900 Subject: [PATCH 420/457] Fix critical failure causing scores to not update on the leaderboard --- .../Play/HUD/MultiplayerGameplayLeaderboard.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 0c13e87f9f..4783ce2f40 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -71,11 +71,19 @@ namespace osu.Game.Screens.Play.HUD UserScores[user.Id] = trackedUser; } - - prepareDataStreams(); })); } + protected override void LoadComplete() + { + base.LoadComplete(); + + // this is *required* to be here due to the spectator leaderboard not correctly populating clocks if done later. + // note that running this here is probably not 100% correct (if a user quits before user population occurs for instance, + // an incorrect state will be reached). + prepareDataStreams(); + } + private void prepareDataStreams() { // BindableList handles binding in a really bad way (Clear then AddRange) so we need to do this manually.. From 8e102280b7e289cb7d564b4ae7b8ba8fddde5cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Jul 2021 20:21:44 +0200 Subject: [PATCH 421/457] Revert & disable case with two contradicting inspections Leaving the `false` default value without the suppression triggers `RedundantArgumentDefaultValue`, while removing it triggers `BaseMethodCallWithDefaultParameter`. Disable the former, because a single redundant parameter is less bad than silent breakage if the default value of `propagateChildren` in the base method ever changes. --- osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index 1f988df947..8943a91076 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -86,7 +86,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default public override void ApplyTransformsAt(double time, bool propagateChildren = false) { // For the same reasons as above w.r.t rewinding, we shouldn't propagate to children here either. - base.ApplyTransformsAt(time); + // ReSharper disable once RedundantArgumentDefaultValue - removing the "redundant" default value triggers BaseMethodCallWithDefaultParameter + base.ApplyTransformsAt(time, false); } private bool tracking; From 686dd2b5ce552a02628844f58a34ecbdddf0d27a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Jul 2021 20:35:14 +0200 Subject: [PATCH 422/457] Remove unused constant --- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index edddc06746..5c5b66d496 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -23,8 +23,6 @@ namespace osu.Game.Screens.Play.HUD private const double pop_out_duration = 150; - private const Easing pop_out_easing = Easing.None; - private const double fade_out_duration = 100; /// From af02a1efcbf6841b55b9925d80a8058840ae481e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Jul 2021 21:02:38 +0200 Subject: [PATCH 423/457] Bump redundant argument default value inspection severity to warning --- osu.sln.DotSettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index d2c5b1223c..7284ca1a9a 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -130,7 +130,7 @@ HINT WARNING WARNING - HINT + WARNING WARNING WARNING WARNING From 7f2baef998947dd37accb18c2f94b6db5d66f181 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 14:24:17 +0900 Subject: [PATCH 424/457] Remove all `.resx` files from osu project --- osu.Game/Localisation/ButtonSystem.ja.resx | 38 ---------- osu.Game/Localisation/ButtonSystem.resx | 88 ---------------------- osu.Game/Localisation/Chat.resx | 67 ---------------- osu.Game/Localisation/Common.resx | 64 ---------------- osu.Game/Localisation/Notifications.resx | 67 ---------------- osu.Game/Localisation/NowPlaying.resx | 67 ---------------- osu.Game/Localisation/Settings.resx | 67 ---------------- 7 files changed, 458 deletions(-) delete mode 100644 osu.Game/Localisation/ButtonSystem.ja.resx delete mode 100644 osu.Game/Localisation/ButtonSystem.resx delete mode 100644 osu.Game/Localisation/Chat.resx delete mode 100644 osu.Game/Localisation/Common.resx delete mode 100644 osu.Game/Localisation/Notifications.resx delete mode 100644 osu.Game/Localisation/NowPlaying.resx delete mode 100644 osu.Game/Localisation/Settings.resx diff --git a/osu.Game/Localisation/ButtonSystem.ja.resx b/osu.Game/Localisation/ButtonSystem.ja.resx deleted file mode 100644 index 02f3e7ce2f..0000000000 --- a/osu.Game/Localisation/ButtonSystem.ja.resx +++ /dev/null @@ -1,38 +0,0 @@ - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - ソロ - - - プレイリスト - - - 遊ぶ - - - マルチ - - - エディット - - - ブラウズ - - - 閉じる - - - 設定 - - diff --git a/osu.Game/Localisation/ButtonSystem.resx b/osu.Game/Localisation/ButtonSystem.resx deleted file mode 100644 index d72ffff8be..0000000000 --- a/osu.Game/Localisation/ButtonSystem.resx +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - solo - - - multi - - - playlists - - - play - - - edit - - - browse - - - settings - - - back - - - exit - - \ No newline at end of file diff --git a/osu.Game/Localisation/Chat.resx b/osu.Game/Localisation/Chat.resx deleted file mode 100644 index 055e351463..0000000000 --- a/osu.Game/Localisation/Chat.resx +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - chat - - - join the real-time discussion - - \ No newline at end of file diff --git a/osu.Game/Localisation/Common.resx b/osu.Game/Localisation/Common.resx deleted file mode 100644 index 59de16a037..0000000000 --- a/osu.Game/Localisation/Common.resx +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Cancel - - \ No newline at end of file diff --git a/osu.Game/Localisation/Notifications.resx b/osu.Game/Localisation/Notifications.resx deleted file mode 100644 index 08db240ba2..0000000000 --- a/osu.Game/Localisation/Notifications.resx +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - notifications - - - waiting for 'ya - - \ No newline at end of file diff --git a/osu.Game/Localisation/NowPlaying.resx b/osu.Game/Localisation/NowPlaying.resx deleted file mode 100644 index 40fda3e25b..0000000000 --- a/osu.Game/Localisation/NowPlaying.resx +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - now playing - - - manage the currently playing track - - \ No newline at end of file diff --git a/osu.Game/Localisation/Settings.resx b/osu.Game/Localisation/Settings.resx deleted file mode 100644 index 85c224cedf..0000000000 --- a/osu.Game/Localisation/Settings.resx +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - settings - - - change the way osu! behaves - - \ No newline at end of file From 0ecda98b393c46991893db8e4d89f7dcec976bed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 14:28:52 +0900 Subject: [PATCH 425/457] Update prefixes to match new resx locations --- osu.Game/Localisation/ButtonSystemStrings.cs | 4 ++-- osu.Game/Localisation/ChatStrings.cs | 2 +- osu.Game/Localisation/CommonStrings.cs | 2 +- osu.Game/Localisation/NotificationsStrings.cs | 2 +- osu.Game/Localisation/NowPlayingStrings.cs | 2 +- osu.Game/Localisation/SettingsStrings.cs | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Localisation/ButtonSystemStrings.cs b/osu.Game/Localisation/ButtonSystemStrings.cs index 8083f80782..ba4abf63a6 100644 --- a/osu.Game/Localisation/ButtonSystemStrings.cs +++ b/osu.Game/Localisation/ButtonSystemStrings.cs @@ -7,7 +7,7 @@ namespace osu.Game.Localisation { public static class ButtonSystemStrings { - private const string prefix = @"osu.Game.Localisation.ButtonSystem"; + private const string prefix = @"osu.Game.Resources.Localisation.ButtonSystem"; /// /// "solo" @@ -56,4 +56,4 @@ namespace osu.Game.Localisation private static string getKey(string key) => $@"{prefix}:{key}"; } -} \ No newline at end of file +} diff --git a/osu.Game/Localisation/ChatStrings.cs b/osu.Game/Localisation/ChatStrings.cs index 636351470b..7bd284a94e 100644 --- a/osu.Game/Localisation/ChatStrings.cs +++ b/osu.Game/Localisation/ChatStrings.cs @@ -7,7 +7,7 @@ namespace osu.Game.Localisation { public static class ChatStrings { - private const string prefix = @"osu.Game.Localisation.Chat"; + private const string prefix = @"osu.Game.Resources.Localisation.Chat"; /// /// "chat" diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs index ced0d80955..50e01f06fc 100644 --- a/osu.Game/Localisation/CommonStrings.cs +++ b/osu.Game/Localisation/CommonStrings.cs @@ -7,7 +7,7 @@ namespace osu.Game.Localisation { public static class CommonStrings { - private const string prefix = @"osu.Game.Localisation.Common"; + private const string prefix = @"osu.Game.Resources.Localisation.Common"; /// /// "Cancel" diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index ba28ef5560..382e0d81f4 100644 --- a/osu.Game/Localisation/NotificationsStrings.cs +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -7,7 +7,7 @@ namespace osu.Game.Localisation { public static class NotificationsStrings { - private const string prefix = @"osu.Game.Localisation.Notifications"; + private const string prefix = @"osu.Game.Resources.Localisation.Notifications"; /// /// "notifications" diff --git a/osu.Game/Localisation/NowPlayingStrings.cs b/osu.Game/Localisation/NowPlayingStrings.cs index 47646b0f68..f334637338 100644 --- a/osu.Game/Localisation/NowPlayingStrings.cs +++ b/osu.Game/Localisation/NowPlayingStrings.cs @@ -7,7 +7,7 @@ namespace osu.Game.Localisation { public static class NowPlayingStrings { - private const string prefix = @"osu.Game.Localisation.NowPlaying"; + private const string prefix = @"osu.Game.Resources.Localisation.NowPlaying"; /// /// "now playing" diff --git a/osu.Game/Localisation/SettingsStrings.cs b/osu.Game/Localisation/SettingsStrings.cs index f4b417fa28..aa2e2740eb 100644 --- a/osu.Game/Localisation/SettingsStrings.cs +++ b/osu.Game/Localisation/SettingsStrings.cs @@ -7,7 +7,7 @@ namespace osu.Game.Localisation { public static class SettingsStrings { - private const string prefix = @"osu.Game.Localisation.Settings"; + private const string prefix = @"osu.Game.Resources.Localisation.Settings"; /// /// "settings" From 1a6b8b2c7353a5d3a988db49db138900ac271761 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 14:53:31 +0900 Subject: [PATCH 426/457] Populate `UserScores` as early as possible to avoid weird ordering requirements --- .../HUD/MultiplayerGameplayLeaderboard.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 4783ce2f40..a10c16fcd5 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -53,6 +53,13 @@ namespace osu.Game.Screens.Play.HUD { scoringMode = config.GetBindable(OsuSetting.ScoreDisplayMode); + foreach (var userId in playingUsers) + { + var trackedUser = CreateUserData(userId, scoreProcessor); + trackedUser.ScoringMode.BindTo(scoringMode); + UserScores[userId] = trackedUser; + } + userLookupCache.GetUsersAsync(playingUsers.ToArray()).ContinueWith(users => Schedule(() => { foreach (var user in users.Result) @@ -60,16 +67,13 @@ namespace osu.Game.Screens.Play.HUD if (user == null) continue; - var trackedUser = CreateUserData(user.Id, scoreProcessor); - trackedUser.ScoringMode.BindTo(scoringMode); + var trackedUser = UserScores[user.Id]; var leaderboardScore = AddPlayer(user, user.Id == api.LocalUser.Value.Id); leaderboardScore.Accuracy.BindTo(trackedUser.Accuracy); leaderboardScore.TotalScore.BindTo(trackedUser.Score); leaderboardScore.Combo.BindTo(trackedUser.CurrentCombo); leaderboardScore.HasQuit.BindTo(trackedUser.UserQuit); - - UserScores[user.Id] = trackedUser; } })); } @@ -78,14 +82,6 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - // this is *required* to be here due to the spectator leaderboard not correctly populating clocks if done later. - // note that running this here is probably not 100% correct (if a user quits before user population occurs for instance, - // an incorrect state will be reached). - prepareDataStreams(); - } - - private void prepareDataStreams() - { // BindableList handles binding in a really bad way (Clear then AddRange) so we need to do this manually.. foreach (int userId in playingUsers) { @@ -95,6 +91,8 @@ namespace osu.Game.Screens.Play.HUD usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { userId })); } + // bind here is to support players leaving the match. + // new players are not supported. playingUsers.BindTo(multiplayerClient.CurrentMatchPlayingUserIds); playingUsers.BindCollectionChanged(usersChanged); From 402b527903e92c0332d86f52b6eb5835682ad0bc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Jul 2021 14:54:56 +0900 Subject: [PATCH 427/457] Add .editorconfig for localisation analyser --- osu.Game/.editorconfig | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 osu.Game/.editorconfig diff --git a/osu.Game/.editorconfig b/osu.Game/.editorconfig new file mode 100644 index 0000000000..46a3dafd04 --- /dev/null +++ b/osu.Game/.editorconfig @@ -0,0 +1,2 @@ +[*.cs] +dotnet_diagnostic.OLOC001.prefix_namespace = osu.Game.Resources.Localisation \ No newline at end of file From 0658cfb986b4f8a06fac444075d7fa004be19cf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 14:56:00 +0900 Subject: [PATCH 428/457] Throw exceptions rather than silently failing if attempting to add a clock for a non-tracked user --- .../Multiplayer/Spectate/MultiSpectatorLeaderboard.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs index ab3ead68b5..55c4270c70 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public void AddClock(int userId, IClock clock) { if (!UserScores.TryGetValue(userId, out var data)) - return; + throw new ArgumentException(@"Provided user is not tracked by this leaderboard", nameof(userId)); ((SpectatingTrackedUserData)data).Clock = clock; } @@ -27,7 +27,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public void RemoveClock(int userId) { if (!UserScores.TryGetValue(userId, out var data)) - return; + throw new ArgumentException(@"Provided user is not tracked by this leaderboard", nameof(userId)); ((SpectatingTrackedUserData)data).Clock = null; } From 1beb85a26f699345bad516da6ba35054489d3399 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Jul 2021 14:56:43 +0900 Subject: [PATCH 429/457] Bump localisation analyser packages --- .config/dotnet-tools.json | 2 +- osu.Game/osu.Game.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index b3f7c67c51..97fcb52ab1 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -27,7 +27,7 @@ ] }, "ppy.localisationanalyser.tools": { - "version": "2021.608.0", + "version": "2021.705.0", "commands": [ "localisation" ] diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 357aa89329..626be76a84 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -31,7 +31,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 7f7cf0b927124ede7673b8e28b2cfc6628ed0386 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 15:08:00 +0900 Subject: [PATCH 430/457] Fix potential failure during cleanup of files in migration tests --- .../NonVisual/CustomTourneyDirectoryTest.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 61f8511e3c..d2369056e1 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tournament.Tests.NonVisual [Test] public void TestDefaultDirectory() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestDefaultDirectory))) { try { @@ -139,8 +139,13 @@ namespace osu.Game.Tournament.Tests.NonVisual } finally { - host.Storage.Delete("tournament.ini"); - host.Storage.DeleteDirectory("tournaments"); + try + { + host.Storage.Delete("tournament.ini"); + host.Storage.DeleteDirectory("tournaments"); + } + catch { } + host.Exit(); } } From ae1b1cbec9333887806a2085c833747a628a5710 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 17:33:32 +0900 Subject: [PATCH 431/457] Allow serialization of catch hit objects --- osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 15 +++++++++++++-- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 5 +++++ .../Objects/PalpableCatchHitObject.cs | 2 ++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index ae45182960..cdd8bfbe50 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.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 Newtonsoft.Json; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -20,6 +21,11 @@ namespace osu.Game.Rulesets.Catch.Objects /// /// The horizontal position of the hit object between 0 and . /// + /// + /// Only setter is exposed. + /// Use or to get the horizontal position. + /// + [JsonIgnore] public float X { set => OriginalXBindable.Value = value; @@ -34,6 +40,7 @@ namespace osu.Game.Rulesets.Catch.Objects /// public float XOffset { + get => XOffsetBindable.Value; set => XOffsetBindable.Value = value; } @@ -44,7 +51,11 @@ namespace osu.Game.Rulesets.Catch.Objects /// This value is the original value specified in the beatmap, not affected by the beatmap processing. /// Use for a gameplay. /// - public float OriginalX => OriginalXBindable.Value; + public float OriginalX + { + get => OriginalXBindable.Value; + set => OriginalXBindable.Value = value; + } /// /// The effective horizontal position of the hit object between 0 and . @@ -55,7 +66,7 @@ namespace osu.Game.Rulesets.Catch.Objects /// public float EffectiveX => OriginalXBindable.Value + XOffsetBindable.Value; - public double TimePreempt = 1000; + public double TimePreempt { get; set; } = 1000; public readonly Bindable IndexInBeatmapBindable = new Bindable(); diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 35fd58826e..3088d024d1 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using Newtonsoft.Json; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -25,7 +26,10 @@ namespace osu.Game.Rulesets.Catch.Objects public int RepeatCount { get; set; } + [JsonIgnore] public double Velocity { get; private set; } + + [JsonIgnore] public double TickDistance { get; private set; } /// @@ -113,6 +117,7 @@ namespace osu.Game.Rulesets.Catch.Objects public float EndX => OriginalX + this.CurvePositionAt(1).X; + [JsonIgnore] public double Duration { get => this.SpanCount() * Path.Distance / Velocity; diff --git a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs index 0cd3af01df..aa7cabf38b 100644 --- a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Game.Rulesets.Objects.Types; using osuTK.Graphics; @@ -33,6 +34,7 @@ namespace osu.Game.Rulesets.Catch.Objects /// /// The target fruit if we are to initiate a hyperdash. /// + [JsonIgnore] public CatchHitObject HyperDashTarget { get => hyperDashTarget; From 175d666906bd742d7a47df3081c5dd1c31ef1b0a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 17:35:30 +0900 Subject: [PATCH 432/457] Use getters of `OriginalX` and `XOffset` --- osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs | 4 ++-- osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index c1a491d1ce..d35d74d93d 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Catch.Edit if (hitObject is BananaShower) return; // TODO: confine in bounds - hitObject.OriginalXBindable.Value += deltaX; + hitObject.OriginalX += deltaX; // Move the nested hit objects to give an instant result before nested objects are recreated. foreach (var nested in hitObject.NestedHitObjects.OfType()) - nested.OriginalXBindable.Value += deltaX; + nested.OriginalX += deltaX; }); return true; diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index cdd8bfbe50..0b8c0e28a7 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Catch.Objects /// This value is the original value plus the offset applied by the beatmap processing. /// Use if a value not affected by the offset is desired. /// - public float EffectiveX => OriginalXBindable.Value + XOffsetBindable.Value; + public float EffectiveX => OriginalX + XOffset; public double TimePreempt { get; set; } = 1000; From 93ef783339908aab6e10b0a5dbc530a67c0fa7c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 16:40:23 +0900 Subject: [PATCH 433/457] Remove BindableList usage --- .../Skinning/RulesetSkinProvidingContainer.cs | 18 +++-- osu.Game/Skinning/SkinProvidingContainer.cs | 77 ++++++------------- 2 files changed, 37 insertions(+), 58 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 19efc66814..4dea1ff51e 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.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.Collections.Generic; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -72,31 +73,36 @@ namespace osu.Game.Skinning protected virtual void UpdateSkinSources() { - SkinSources.Clear(); + ResetSources(); + + var skinSources = new List(); foreach (var skin in parentSource.AllSources) { switch (skin) { case LegacySkin legacySkin: - SkinSources.Add(GetLegacyRulesetTransformedSkin(legacySkin)); + skinSources.Add(GetLegacyRulesetTransformedSkin(legacySkin)); break; default: - SkinSources.Add(skin); + skinSources.Add(skin); break; } } - int lastDefaultSkinIndex = SkinSources.IndexOf(SkinSources.OfType().LastOrDefault()); + int lastDefaultSkinIndex = skinSources.IndexOf(skinSources.OfType().LastOrDefault()); // Ruleset resources should be given the ability to override game-wide defaults // This is achieved by placing them before the last instance of DefaultSkin. // Note that DefaultSkin may not be present in some test scenes. if (lastDefaultSkinIndex >= 0) - SkinSources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin); + skinSources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin); else - SkinSources.Add(rulesetResourcesSkin); + skinSources.Add(rulesetResourcesSkin); + + foreach (var skin in skinSources) + AddSource(skin); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index c83c299723..b1ada54a26 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -27,13 +26,13 @@ namespace osu.Game.Skinning /// /// Skins which should be exposed by this container, in order of lookup precedence. /// - protected readonly BindableList SkinSources = new BindableList(); + protected IEnumerable SkinSources => skinSources.Keys; /// /// A dictionary mapping each from the /// to one that performs the "allow lookup" checks before proceeding with a lookup. /// - private readonly Dictionary disableableSkinSources = new Dictionary(); + private readonly Dictionary skinSources = new Dictionary(); [CanBeNull] private ISkinSource fallbackSource; @@ -60,7 +59,7 @@ namespace osu.Game.Skinning : this() { if (skin != null) - SkinSources.Add(skin); + AddSource(skin); } /// @@ -70,61 +69,35 @@ namespace osu.Game.Skinning protected SkinProvidingContainer() { RelativeSizeAxes = Axes.Both; + } - SkinSources.BindCollectionChanged(((_, args) => - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - foreach (var skin in args.NewItems.Cast()) - { - disableableSkinSources.Add(skin, new DisableableSkinSource(skin, this)); + public void AddSource(ISkin skin) + { + skinSources.Add(skin, new DisableableSkinSource(skin, this)); - if (skin is ISkinSource source) - source.SourceChanged += OnSourceChanged; - } + if (skin is ISkinSource source) + source.SourceChanged += OnSourceChanged; + } - break; + public void RemoveSource(ISkin skin) + { + skinSources.Remove(skin); - case NotifyCollectionChangedAction.Reset: - case NotifyCollectionChangedAction.Remove: - foreach (var skin in args.OldItems.Cast()) - { - disableableSkinSources.Remove(skin); + if (skin is ISkinSource source) + source.SourceChanged += OnSourceChanged; + } - if (skin is ISkinSource source) - source.SourceChanged -= OnSourceChanged; - } - - break; - - case NotifyCollectionChangedAction.Replace: - foreach (var skin in args.OldItems.Cast()) - { - disableableSkinSources.Remove(skin); - - if (skin is ISkinSource source) - source.SourceChanged -= OnSourceChanged; - } - - foreach (var skin in args.NewItems.Cast()) - { - disableableSkinSources.Add(skin, new DisableableSkinSource(skin, this)); - - if (skin is ISkinSource source) - source.SourceChanged += OnSourceChanged; - } - - break; - } - }), true); + public void ResetSources() + { + foreach (var skin in AllSources.ToArray()) + RemoveSource(skin); } public ISkin FindProvider(Func lookupFunction) { foreach (var skin in SkinSources) { - if (lookupFunction(disableableSkinSources[skin])) + if (lookupFunction(skinSources[skin])) return skin; } @@ -151,7 +124,7 @@ namespace osu.Game.Skinning foreach (var skin in SkinSources) { Drawable sourceDrawable; - if ((sourceDrawable = disableableSkinSources[skin]?.GetDrawableComponent(component)) != null) + if ((sourceDrawable = skinSources[skin]?.GetDrawableComponent(component)) != null) return sourceDrawable; } @@ -163,7 +136,7 @@ namespace osu.Game.Skinning foreach (var skin in SkinSources) { Texture sourceTexture; - if ((sourceTexture = disableableSkinSources[skin]?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) + if ((sourceTexture = skinSources[skin]?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) return sourceTexture; } @@ -175,7 +148,7 @@ namespace osu.Game.Skinning foreach (var skin in SkinSources) { ISample sourceSample; - if ((sourceSample = disableableSkinSources[skin]?.GetSample(sampleInfo)) != null) + if ((sourceSample = skinSources[skin]?.GetSample(sampleInfo)) != null) return sourceSample; } @@ -187,7 +160,7 @@ namespace osu.Game.Skinning foreach (var skin in SkinSources) { IBindable bindable; - if ((bindable = disableableSkinSources[skin]?.GetConfig(lookup)) != null) + if ((bindable = skinSources[skin]?.GetConfig(lookup)) != null) return bindable; } From 7ef7c5148f21c35426a45f4ae02d6a60755fc584 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 6 Jul 2021 16:19:31 +0900 Subject: [PATCH 434/457] Add `ScrollingPath` for visualization of the real path of a `JuiceStream` --- .../Blueprints/Components/ScrollingPath.cs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs new file mode 100644 index 0000000000..337b8de92e --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs @@ -0,0 +1,79 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Lines; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components +{ + public class ScrollingPath : CompositeDrawable + { + private readonly Path drawablePath; + + private readonly List<(double Distance, float X)> vertices = new List<(double, float)>(); + + public ScrollingPath() + { + Anchor = Anchor.BottomLeft; + + InternalChildren = new Drawable[] + { + drawablePath = new SmoothPath + { + PathRadius = 2, + Alpha = 0.5f + }, + }; + } + + public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject) + { + X = hitObject.OriginalX; + Y = hitObjectContainer.PositionAtTime(hitObject.StartTime); + } + + public void UpdatePathFrom(ScrollingHitObjectContainer hitObjectContainer, JuiceStream hitObject) + { + double distanceToYFactor = -hitObjectContainer.LengthAtTime(hitObject.StartTime, hitObject.StartTime + 1 / hitObject.Velocity); + + computeDistanceXs(hitObject); + drawablePath.Vertices = vertices + .Select(v => new Vector2(v.X, (float)(v.Distance * distanceToYFactor))) + .ToArray(); + drawablePath.OriginPosition = drawablePath.PositionInBoundingBox(Vector2.Zero); + } + + private void computeDistanceXs(JuiceStream hitObject) + { + vertices.Clear(); + + var sliderVertices = new List(); + hitObject.Path.GetPathToProgress(sliderVertices, 0, 1); + + if (sliderVertices.Count == 0) + return; + + double distance = 0; + Vector2 lastPosition = Vector2.Zero; + + for (int repeat = 0; repeat < hitObject.RepeatCount + 1; repeat++) + { + foreach (var position in sliderVertices) + { + distance += Vector2.Distance(lastPosition, position); + lastPosition = position; + + vertices.Add((distance, position.X)); + } + + sliderVertices.Reverse(); + } + } + } +} From 0fa7716ceda7f8de41b1bf744bb5e98de3ff0d49 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 6 Jul 2021 16:46:12 +0900 Subject: [PATCH 435/457] Show path of juice stream in selection blueprint --- .../JuiceStreamSelectionBlueprint.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index d6b8c35a09..9f9d6a0556 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -3,7 +3,9 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Caching; using osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Objects; using osuTK; @@ -17,9 +19,14 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints private float minNestedX; private float maxNestedX; + private readonly ScrollingPath scrollingPath; + + private readonly Cached pathCache = new Cached(); + public JuiceStreamSelectionBlueprint(JuiceStream hitObject) : base(hitObject) { + InternalChild = scrollingPath = new ScrollingPath(); } [BackgroundDependencyLoader] @@ -29,7 +36,25 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints computeObjectBounds(); } - private void onDefaultsApplied(HitObject _) => computeObjectBounds(); + protected override void Update() + { + base.Update(); + + if (!IsSelected) return; + + scrollingPath.UpdatePositionFrom(HitObjectContainer, HitObject); + + if (pathCache.IsValid) return; + + scrollingPath.UpdatePathFrom(HitObjectContainer, HitObject); + pathCache.Validate(); + } + + private void onDefaultsApplied(HitObject _) + { + computeObjectBounds(); + pathCache.Invalidate(); + } private void computeObjectBounds() { From 935fbe7cc627f78deba63f33f1b0057d0470eb7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 16:41:48 +0900 Subject: [PATCH 436/457] Remove double fetch/binding of parent source --- .../Skinning/RulesetSkinProvidingContainer.cs | 17 +++---- osu.Game/Skinning/SkinProvidingContainer.cs | 44 ++++++++++++------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 4dea1ff51e..d5be5d9394 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -47,22 +48,19 @@ namespace osu.Game.Skinning }; } - private ISkinSource parentSource; - private ResourceStoreBackedSkin rulesetResourcesSkin; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - parentSource = parent.Get(); - parentSource.SourceChanged += OnSourceChanged; - if (Ruleset.CreateResourceStore() is IResourceStore resources) rulesetResourcesSkin = new ResourceStoreBackedSkin(resources, parent.Get(), parent.Get()); + var dependencies = base.CreateChildDependencies(parent); + // ensure sources are populated and ready for use before childrens' asynchronous load flow. UpdateSkinSources(); - return base.CreateChildDependencies(parent); + return dependencies; } protected override void OnSourceChanged() @@ -77,7 +75,9 @@ namespace osu.Game.Skinning var skinSources = new List(); - foreach (var skin in parentSource.AllSources) + Debug.Assert(ParentSource != null); + + foreach (var skin in ParentSource.AllSources) { switch (skin) { @@ -121,9 +121,6 @@ namespace osu.Game.Skinning { base.Dispose(isDisposing); - if (parentSource != null) - parentSource.SourceChanged -= OnSourceChanged; - rulesetResourcesSkin?.Dispose(); } } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index b1ada54a26..a1e71502b9 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Skinning private readonly Dictionary skinSources = new Dictionary(); [CanBeNull] - private ISkinSource fallbackSource; + protected ISkinSource ParentSource { get; private set; } /// /// Whether falling back to parent s is allowed in this container. @@ -101,7 +101,10 @@ namespace osu.Game.Skinning return skin; } - return fallbackSource?.FindProvider(lookupFunction); + if (!AllowFallingBackToParent) + return null; + + return ParentSource?.FindProvider(lookupFunction); } public IEnumerable AllSources @@ -111,9 +114,9 @@ namespace osu.Game.Skinning foreach (var skin in SkinSources) yield return skin; - if (fallbackSource != null) + if (AllowFallingBackToParent && ParentSource != null) { - foreach (var skin in fallbackSource.AllSources) + foreach (var skin in ParentSource.AllSources) yield return skin; } } @@ -128,7 +131,10 @@ namespace osu.Game.Skinning return sourceDrawable; } - return fallbackSource?.GetDrawableComponent(component); + if (!AllowFallingBackToParent) + return null; + + return ParentSource?.GetDrawableComponent(component); } public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) @@ -140,7 +146,10 @@ namespace osu.Game.Skinning return sourceTexture; } - return fallbackSource?.GetTexture(componentName, wrapModeS, wrapModeT); + if (!AllowFallingBackToParent) + return null; + + return ParentSource?.GetTexture(componentName, wrapModeS, wrapModeT); } public ISample GetSample(ISampleInfo sampleInfo) @@ -152,7 +161,10 @@ namespace osu.Game.Skinning return sourceSample; } - return fallbackSource?.GetSample(sampleInfo); + if (!AllowFallingBackToParent) + return null; + + return ParentSource?.GetSample(sampleInfo); } public IBindable GetConfig(TLookup lookup) @@ -164,7 +176,10 @@ namespace osu.Game.Skinning return bindable; } - return fallbackSource?.GetConfig(lookup); + if (!AllowFallingBackToParent) + return null; + + return ParentSource?.GetConfig(lookup); } protected virtual void OnSourceChanged() => SourceChanged?.Invoke(); @@ -173,12 +188,9 @@ namespace osu.Game.Skinning { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - if (AllowFallingBackToParent) - { - fallbackSource = dependencies.Get(); - if (fallbackSource != null) - fallbackSource.SourceChanged += OnSourceChanged; - } + ParentSource = dependencies.Get(); + if (ParentSource != null) + ParentSource.SourceChanged += OnSourceChanged; dependencies.CacheAs(this); @@ -192,8 +204,8 @@ namespace osu.Game.Skinning base.Dispose(isDisposing); - if (fallbackSource != null) - fallbackSource.SourceChanged -= OnSourceChanged; + if (ParentSource != null) + ParentSource.SourceChanged -= OnSourceChanged; foreach (var source in SkinSources.OfType()) source.SourceChanged -= OnSourceChanged; From ec1224218c86587eb62137a96f4cd328ff0dcd35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 16:57:19 +0900 Subject: [PATCH 437/457] Localise source changed flow for better clarity --- osu.Game/Skinning/SkinProvidingContainer.cs | 23 +++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index a1e71502b9..5beadd7178 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -76,7 +76,7 @@ namespace osu.Game.Skinning skinSources.Add(skin, new DisableableSkinSource(skin, this)); if (skin is ISkinSource source) - source.SourceChanged += OnSourceChanged; + source.SourceChanged += anySourceChanged; } public void RemoveSource(ISkin skin) @@ -84,7 +84,7 @@ namespace osu.Game.Skinning skinSources.Remove(skin); if (skin is ISkinSource source) - source.SourceChanged += OnSourceChanged; + source.SourceChanged += anySourceChanged; } public void ResetSources() @@ -182,7 +182,10 @@ namespace osu.Game.Skinning return ParentSource?.GetConfig(lookup); } - protected virtual void OnSourceChanged() => SourceChanged?.Invoke(); + /// + /// Invoked when any source has changed (either or + /// + protected virtual void OnSourceChanged() { } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { @@ -190,13 +193,21 @@ namespace osu.Game.Skinning ParentSource = dependencies.Get(); if (ParentSource != null) - ParentSource.SourceChanged += OnSourceChanged; + ParentSource.SourceChanged += anySourceChanged; dependencies.CacheAs(this); return dependencies; } + private void anySourceChanged() + { + // Expose to implementations, giving them a chance to react before notifying external consumers. + OnSourceChanged(); + + SourceChanged?.Invoke(); + } + protected override void Dispose(bool isDisposing) { // Must be done before base.Dispose() @@ -205,10 +216,10 @@ namespace osu.Game.Skinning base.Dispose(isDisposing); if (ParentSource != null) - ParentSource.SourceChanged -= OnSourceChanged; + ParentSource.SourceChanged -= anySourceChanged; foreach (var source in SkinSources.OfType()) - source.SourceChanged -= OnSourceChanged; + source.SourceChanged -= anySourceChanged; } private class DisableableSkinSource : ISkin From b4240d3ca4db7d06599524612486f125f1a01f0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 17:04:59 +0900 Subject: [PATCH 438/457] Simplify lookups to avoid a second dictionary fetch --- osu.Game/Skinning/SkinProvidingContainer.cs | 33 ++++++++------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 5beadd7178..a6debcbf66 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -24,13 +24,7 @@ namespace osu.Game.Skinning public event Action SourceChanged; /// - /// Skins which should be exposed by this container, in order of lookup precedence. - /// - protected IEnumerable SkinSources => skinSources.Keys; - - /// - /// A dictionary mapping each from the - /// to one that performs the "allow lookup" checks before proceeding with a lookup. + /// A dictionary mapping each source to a wrapper which handles lookup allowances. /// private readonly Dictionary skinSources = new Dictionary(); @@ -64,7 +58,6 @@ namespace osu.Game.Skinning /// /// Constructs a new with no sources. - /// Implementations can add or change sources through the list. /// protected SkinProvidingContainer() { @@ -95,9 +88,9 @@ namespace osu.Game.Skinning public ISkin FindProvider(Func lookupFunction) { - foreach (var skin in SkinSources) + foreach (var (skin, lookupWrapper) in skinSources) { - if (lookupFunction(skinSources[skin])) + if (lookupFunction(lookupWrapper)) return skin; } @@ -111,7 +104,7 @@ namespace osu.Game.Skinning { get { - foreach (var skin in SkinSources) + foreach (var skin in skinSources.Keys) yield return skin; if (AllowFallingBackToParent && ParentSource != null) @@ -124,10 +117,10 @@ namespace osu.Game.Skinning public Drawable GetDrawableComponent(ISkinComponent component) { - foreach (var skin in SkinSources) + foreach (var (_, lookupWrapper) in skinSources) { Drawable sourceDrawable; - if ((sourceDrawable = skinSources[skin]?.GetDrawableComponent(component)) != null) + if ((sourceDrawable = lookupWrapper.GetDrawableComponent(component)) != null) return sourceDrawable; } @@ -139,10 +132,10 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) { - foreach (var skin in SkinSources) + foreach (var (_, lookupWrapper) in skinSources) { Texture sourceTexture; - if ((sourceTexture = skinSources[skin]?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) + if ((sourceTexture = lookupWrapper.GetTexture(componentName, wrapModeS, wrapModeT)) != null) return sourceTexture; } @@ -154,10 +147,10 @@ namespace osu.Game.Skinning public ISample GetSample(ISampleInfo sampleInfo) { - foreach (var skin in SkinSources) + foreach (var (_, lookupWrapper) in skinSources) { ISample sourceSample; - if ((sourceSample = skinSources[skin]?.GetSample(sampleInfo)) != null) + if ((sourceSample = lookupWrapper.GetSample(sampleInfo)) != null) return sourceSample; } @@ -169,10 +162,10 @@ namespace osu.Game.Skinning public IBindable GetConfig(TLookup lookup) { - foreach (var skin in SkinSources) + foreach (var (_, lookupWrapper) in skinSources) { IBindable bindable; - if ((bindable = skinSources[skin]?.GetConfig(lookup)) != null) + if ((bindable = lookupWrapper.GetConfig(lookup)) != null) return bindable; } @@ -218,7 +211,7 @@ namespace osu.Game.Skinning if (ParentSource != null) ParentSource.SourceChanged -= anySourceChanged; - foreach (var source in SkinSources.OfType()) + foreach (var source in skinSources.Keys.OfType()) source.SourceChanged -= anySourceChanged; } From 1232925f93113846049c7be94a09b8cd14a76299 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 17:06:00 +0900 Subject: [PATCH 439/457] Make source manipulation methods `protected` --- osu.Game/Skinning/SkinProvidingContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index a6debcbf66..730659eacd 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Skinning RelativeSizeAxes = Axes.Both; } - public void AddSource(ISkin skin) + protected void AddSource(ISkin skin) { skinSources.Add(skin, new DisableableSkinSource(skin, this)); @@ -72,7 +72,7 @@ namespace osu.Game.Skinning source.SourceChanged += anySourceChanged; } - public void RemoveSource(ISkin skin) + protected void RemoveSource(ISkin skin) { skinSources.Remove(skin); @@ -80,7 +80,7 @@ namespace osu.Game.Skinning source.SourceChanged += anySourceChanged; } - public void ResetSources() + protected void ResetSources() { foreach (var skin in AllSources.ToArray()) RemoveSource(skin); From 032c285edefbefa8aa2874bca356f9b295123caf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 17:07:25 +0900 Subject: [PATCH 440/457] Move private downwards --- osu.Game/Skinning/SkinProvidingContainer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 730659eacd..6b6fdce480 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -23,11 +23,6 @@ namespace osu.Game.Skinning { public event Action SourceChanged; - /// - /// A dictionary mapping each source to a wrapper which handles lookup allowances. - /// - private readonly Dictionary skinSources = new Dictionary(); - [CanBeNull] protected ISkinSource ParentSource { get; private set; } @@ -46,6 +41,11 @@ namespace osu.Game.Skinning protected virtual bool AllowColourLookup => true; + /// + /// A dictionary mapping each source to a wrapper which handles lookup allowances. + /// + private readonly Dictionary skinSources = new Dictionary(); + /// /// Constructs a new initialised with a single skin source. /// From 7833a1b09a32b7e7061f44935da8e8107d981489 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 6 Jul 2021 17:15:27 +0900 Subject: [PATCH 441/457] Allow `FruitOutline` to be used for nested hit objects --- .../Edit/Blueprints/Components/FruitOutline.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs index 8769acc382..345b59bdcd 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,7 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components { Anchor = Anchor.BottomLeft; Origin = Anchor.Centre; - Size = new Vector2(2 * CatchHitObject.OBJECT_RADIUS); InternalChild = new BorderPiece(); } @@ -28,10 +28,10 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components Colour = osuColour.Yellow; } - public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject) + public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject, [CanBeNull] CatchHitObject parent = null) { - X = hitObject.EffectiveX; - Y = hitObjectContainer.PositionAtTime(hitObject.StartTime); + X = hitObject.EffectiveX - (parent?.OriginalX ?? 0); + Y = hitObjectContainer.PositionAtTime(hitObject.StartTime, parent?.StartTime ?? hitObjectContainer.Time.Current); Scale = new Vector2(hitObject.Scale); } } From 2ba300393495ab865ffe622a86772903ba809d9d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 6 Jul 2021 17:15:51 +0900 Subject: [PATCH 442/457] Add nested fruit outlines to juice stream selection blueprint --- .../Components/NestedOutlineContainer.cs | 53 +++++++++++++++++++ .../JuiceStreamSelectionBlueprint.cs | 12 ++++- 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs new file mode 100644 index 0000000000..9e1743de98 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs @@ -0,0 +1,53 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components +{ + public class NestedOutlineContainer : CompositeDrawable + { + private readonly Container nestedOutlines; + + private readonly List nestedHitObjects = new List(); + + public NestedOutlineContainer() + { + Anchor = Anchor.BottomLeft; + + InternalChild = nestedOutlines = new Container(); + } + + public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject) + { + X = parentHitObject.OriginalX; + Y = hitObjectContainer.PositionAtTime(parentHitObject.StartTime); + } + + public void UpdateNestedObjectsFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject) + { + nestedHitObjects.Clear(); + nestedHitObjects.AddRange(parentHitObject.NestedHitObjects + .OfType() + .Where(h => !(h is TinyDroplet))); + + while (nestedHitObjects.Count < nestedOutlines.Count) + nestedOutlines.Remove(nestedOutlines[^1]); + + while (nestedOutlines.Count < nestedHitObjects.Count) + nestedOutlines.Add(new FruitOutline()); + + for (int i = 0; i < nestedHitObjects.Count; i++) + { + var hitObject = nestedHitObjects[i]; + nestedOutlines[i].UpdateFrom(hitObjectContainer, hitObject, parentHitObject); + nestedOutlines[i].Scale *= hitObject is Droplet ? 0.5f : 1; + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index 9f9d6a0556..bf7b962e0a 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -4,6 +4,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Caching; +using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; using osu.Game.Rulesets.Catch.Objects; @@ -21,12 +22,18 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints private readonly ScrollingPath scrollingPath; + private readonly NestedOutlineContainer nestedOutlineContainer; + private readonly Cached pathCache = new Cached(); public JuiceStreamSelectionBlueprint(JuiceStream hitObject) : base(hitObject) { - InternalChild = scrollingPath = new ScrollingPath(); + InternalChildren = new Drawable[] + { + scrollingPath = new ScrollingPath(), + nestedOutlineContainer = new NestedOutlineContainer() + }; } [BackgroundDependencyLoader] @@ -43,10 +50,13 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints if (!IsSelected) return; scrollingPath.UpdatePositionFrom(HitObjectContainer, HitObject); + nestedOutlineContainer.UpdatePositionFrom(HitObjectContainer, HitObject); if (pathCache.IsValid) return; scrollingPath.UpdatePathFrom(HitObjectContainer, HitObject); + nestedOutlineContainer.UpdateNestedObjectsFrom(HitObjectContainer, HitObject); + pathCache.Validate(); } From cd4885e4502feb0f28b3ea32c26fa13640e4c224 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 17:18:45 +0900 Subject: [PATCH 443/457] Add xmldoc and remove any question of how the intitial flow is being run --- .../Skinning/RulesetSkinProvidingContainer.cs | 28 ++++++------------- osu.Game/Skinning/SkinProvidingContainer.cs | 21 ++++++++++++-- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index d5be5d9394..f5a7788359 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -55,25 +55,15 @@ namespace osu.Game.Skinning if (Ruleset.CreateResourceStore() is IResourceStore resources) rulesetResourcesSkin = new ResourceStoreBackedSkin(resources, parent.Get(), parent.Get()); - var dependencies = base.CreateChildDependencies(parent); - - // ensure sources are populated and ready for use before childrens' asynchronous load flow. - UpdateSkinSources(); - - return dependencies; + return base.CreateChildDependencies(parent); } protected override void OnSourceChanged() - { - UpdateSkinSources(); - base.OnSourceChanged(); - } - - protected virtual void UpdateSkinSources() { ResetSources(); - var skinSources = new List(); + // Populate a local list first so we can adjust the returned order as we go. + var sources = new List(); Debug.Assert(ParentSource != null); @@ -82,26 +72,26 @@ namespace osu.Game.Skinning switch (skin) { case LegacySkin legacySkin: - skinSources.Add(GetLegacyRulesetTransformedSkin(legacySkin)); + sources.Add(GetLegacyRulesetTransformedSkin(legacySkin)); break; default: - skinSources.Add(skin); + sources.Add(skin); break; } } - int lastDefaultSkinIndex = skinSources.IndexOf(skinSources.OfType().LastOrDefault()); + int lastDefaultSkinIndex = sources.IndexOf(sources.OfType().LastOrDefault()); // Ruleset resources should be given the ability to override game-wide defaults // This is achieved by placing them before the last instance of DefaultSkin. // Note that DefaultSkin may not be present in some test scenes. if (lastDefaultSkinIndex >= 0) - skinSources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin); + sources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin); else - skinSources.Add(rulesetResourcesSkin); + sources.Add(rulesetResourcesSkin); - foreach (var skin in skinSources) + foreach (var skin in sources) AddSource(skin); } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 6b6fdce480..6033776979 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -64,6 +64,10 @@ namespace osu.Game.Skinning RelativeSizeAxes = Axes.Both; } + /// + /// Add a new skin to this provider. Will be added to the end of the lookup order precedence. + /// + /// The skin to add. protected void AddSource(ISkin skin) { skinSources.Add(skin, new DisableableSkinSource(skin, this)); @@ -72,14 +76,22 @@ namespace osu.Game.Skinning source.SourceChanged += anySourceChanged; } + /// + /// Remove a skin from this provider. + /// + /// The skin to remove. protected void RemoveSource(ISkin skin) { - skinSources.Remove(skin); + if (!skinSources.Remove(skin)) + return; if (skin is ISkinSource source) - source.SourceChanged += anySourceChanged; + source.SourceChanged -= anySourceChanged; } + /// + /// Clears all skin sources. + /// protected void ResetSources() { foreach (var skin in AllSources.ToArray()) @@ -176,7 +188,8 @@ namespace osu.Game.Skinning } /// - /// Invoked when any source has changed (either or + /// Invoked when any source has changed (either or a source registered via ). + /// This is also invoked once initially during to ensure sources are ready for children consumption. /// protected virtual void OnSourceChanged() { } @@ -190,6 +203,8 @@ namespace osu.Game.Skinning dependencies.CacheAs(this); + anySourceChanged(); + return dependencies; } From d75d67577a1668749ffcb08600ce1440fc503256 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 17:37:34 +0900 Subject: [PATCH 444/457] Fix regressed tests --- .../Visual/Gameplay/TestSceneSkinnableDrawable.cs | 2 +- osu.Game/Skinning/BeatmapSkinProvidingContainer.cs | 6 +++--- osu.Game/Skinning/SkinProvidingContainer.cs | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 02b1959dab..3e8ba69e01 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -168,7 +168,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void Disable() { allow = false; - OnSourceChanged(); + TriggerSourceChanged(); } public SwitchableSkinProvidingContainer(ISkin skin) diff --git a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs index f12f44e347..57c08a903f 100644 --- a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs +++ b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs @@ -83,9 +83,9 @@ namespace osu.Game.Skinning [BackgroundDependencyLoader] private void load() { - beatmapSkins.BindValueChanged(_ => OnSourceChanged()); - beatmapColours.BindValueChanged(_ => OnSourceChanged()); - beatmapHitsounds.BindValueChanged(_ => OnSourceChanged()); + beatmapSkins.BindValueChanged(_ => TriggerSourceChanged()); + beatmapColours.BindValueChanged(_ => TriggerSourceChanged()); + beatmapHitsounds.BindValueChanged(_ => TriggerSourceChanged()); } } } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 6033776979..d2f38b58a4 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -73,7 +73,7 @@ namespace osu.Game.Skinning skinSources.Add(skin, new DisableableSkinSource(skin, this)); if (skin is ISkinSource source) - source.SourceChanged += anySourceChanged; + source.SourceChanged += TriggerSourceChanged; } /// @@ -86,7 +86,7 @@ namespace osu.Game.Skinning return; if (skin is ISkinSource source) - source.SourceChanged -= anySourceChanged; + source.SourceChanged -= TriggerSourceChanged; } /// @@ -199,16 +199,16 @@ namespace osu.Game.Skinning ParentSource = dependencies.Get(); if (ParentSource != null) - ParentSource.SourceChanged += anySourceChanged; + ParentSource.SourceChanged += TriggerSourceChanged; dependencies.CacheAs(this); - anySourceChanged(); + TriggerSourceChanged(); return dependencies; } - private void anySourceChanged() + protected void TriggerSourceChanged() { // Expose to implementations, giving them a chance to react before notifying external consumers. OnSourceChanged(); @@ -224,10 +224,10 @@ namespace osu.Game.Skinning base.Dispose(isDisposing); if (ParentSource != null) - ParentSource.SourceChanged -= anySourceChanged; + ParentSource.SourceChanged -= TriggerSourceChanged; foreach (var source in skinSources.Keys.OfType()) - source.SourceChanged -= anySourceChanged; + source.SourceChanged -= TriggerSourceChanged; } private class DisableableSkinSource : ISkin From 8b12ec9586019b42b01bd98e861ca0f5f2e31a3c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Jul 2021 17:52:27 +0900 Subject: [PATCH 445/457] Fix intermittent ready button test failures --- .../Multiplayer/TestSceneMultiplayerReadyButton.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 0e036e8868..c36e1200e6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -113,10 +113,10 @@ namespace osu.Game.Tests.Visual.Multiplayer }); addClickButtonStep(); - AddAssert("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready); + AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready); addClickButtonStep(); - AddAssert("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); + AddUntilStep("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); } [TestCase(true)] @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); addClickButtonStep(); - AddAssert("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready); + AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready); verifyGameplayStartFlow(); } @@ -206,8 +206,9 @@ namespace osu.Game.Tests.Visual.Multiplayer private void verifyGameplayStartFlow() { + AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready); addClickButtonStep(); - AddAssert("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad); + AddUntilStep("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad); AddAssert("ready button disabled", () => !button.ChildrenOfType().Single().Enabled.Value); AddStep("transitioned to gameplay", () => readyClickOperation.Dispose()); From b209868d96d277740c8718bb49d3e1110c1d92b5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Jul 2021 17:57:24 +0900 Subject: [PATCH 446/457] Fix another potential failure --- .../Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index c36e1200e6..820b403a10 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -219,7 +219,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Client.ChangeUserState(Client.Room?.Users[0].UserID ?? 0, MultiplayerUserState.FinishedPlay); }); - AddAssert("ready button enabled", () => button.ChildrenOfType().Single().Enabled.Value); + AddUntilStep("ready button enabled", () => button.ChildrenOfType().Single().Enabled.Value); } } } From 7b21d1ecf9ac251e697d65e626044ed3d5244e2c Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 6 Jul 2021 19:50:32 +0900 Subject: [PATCH 447/457] Fix juice stream outline disappears away when start position is outside the screen. --- .../Components/NestedOutlineContainer.cs | 20 +++++++++---------- .../Blueprints/Components/ScrollingPath.cs | 4 ++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs index 9e1743de98..48d90e8b24 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.UI.Scrolling; @@ -12,15 +13,11 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components { public class NestedOutlineContainer : CompositeDrawable { - private readonly Container nestedOutlines; - private readonly List nestedHitObjects = new List(); public NestedOutlineContainer() { Anchor = Anchor.BottomLeft; - - InternalChild = nestedOutlines = new Container(); } public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject) @@ -36,18 +33,21 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components .OfType() .Where(h => !(h is TinyDroplet))); - while (nestedHitObjects.Count < nestedOutlines.Count) - nestedOutlines.Remove(nestedOutlines[^1]); + while (nestedHitObjects.Count < InternalChildren.Count) + RemoveInternal(InternalChildren[^1]); - while (nestedOutlines.Count < nestedHitObjects.Count) - nestedOutlines.Add(new FruitOutline()); + while (InternalChildren.Count < nestedHitObjects.Count) + AddInternal(new FruitOutline()); for (int i = 0; i < nestedHitObjects.Count; i++) { var hitObject = nestedHitObjects[i]; - nestedOutlines[i].UpdateFrom(hitObjectContainer, hitObject, parentHitObject); - nestedOutlines[i].Scale *= hitObject is Droplet ? 0.5f : 1; + var outline = (FruitOutline)InternalChildren[i]; + outline.UpdateFrom(hitObjectContainer, hitObject, parentHitObject); + outline.Scale *= hitObject is Droplet ? 0.5f : 1; } } + + protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; } } diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs index 337b8de92e..96111beda4 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; +using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -75,5 +76,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components sliderVertices.Reverse(); } } + + // Because this has 0x0 size, the contents are otherwise masked away if the start position is outside the screen. + protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; } } From 06d2c6f0a1916dd30c673004713d4515ab2f4944 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 19:51:57 +0900 Subject: [PATCH 448/457] 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 9067ae1cd9..cfc28ffb21 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 626be76a84..646d21dfee 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index e339e49187..f36f7881cd 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 6bc00208251091726aa3285cc6c6863b886942ed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Jul 2021 20:28:49 +0900 Subject: [PATCH 449/457] Fix intermittent spectate button test failures --- .../Multiplayer/TestSceneMultiplayerSpectateButton.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 4966dfbe50..3d08d5da9e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -122,10 +122,10 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestToggleWhenIdle(MultiplayerUserState initialState) { addClickSpectateButtonStep(); - AddAssert("user is spectating", () => Client.Room?.Users[0].State == MultiplayerUserState.Spectating); + AddUntilStep("user is spectating", () => Client.Room?.Users[0].State == MultiplayerUserState.Spectating); addClickSpectateButtonStep(); - AddAssert("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); + AddUntilStep("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); } [TestCase(MultiplayerRoomState.Closed)] @@ -174,9 +174,9 @@ namespace osu.Game.Tests.Visual.Multiplayer }); private void assertSpectateButtonEnablement(bool shouldBeEnabled) - => AddAssert($"spectate button {(shouldBeEnabled ? "is" : "is not")} enabled", () => spectateButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); + => AddUntilStep($"spectate button {(shouldBeEnabled ? "is" : "is not")} enabled", () => spectateButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); private void assertReadyButtonEnablement(bool shouldBeEnabled) - => AddAssert($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => readyButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); + => AddUntilStep($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => readyButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); } } From 255f7b7b532a25276272331a4e224c9389a6931a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 6 Jul 2021 16:32:02 +0300 Subject: [PATCH 450/457] Add failing test scene --- .../Skins/TestSceneSkinProvidingContainer.cs | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs diff --git a/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs b/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs new file mode 100644 index 0000000000..cfc4ccd208 --- /dev/null +++ b/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs @@ -0,0 +1,92 @@ +// 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.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Skins +{ + public class TestSceneSkinProvidingContainer : OsuTestScene + { + /// + /// Ensures that the first inserted skin after resetting (via source change) + /// is always prioritised over others when providing the same resource. + /// + [Test] + public void TestPriorityPreservation() + { + TestSkinProvidingContainer provider = null; + TestSkin mostPrioritisedSource = null; + + AddStep("setup sources", () => + { + var sources = new List(); + for (int i = 0; i < 10; i++) + sources.Add(new TestSkin()); + + mostPrioritisedSource = sources.First(); + + Child = provider = new TestSkinProvidingContainer(sources); + }); + + AddAssert("texture provided by expected skin", () => + { + return provider.FindProvider(s => s.GetTexture(TestSkin.TEXTURE_NAME) != null) == mostPrioritisedSource; + }); + + AddStep("trigger source change", () => provider.TriggerSourceChanged()); + + AddAssert("texture still provided by expected skin", () => + { + return provider.FindProvider(s => s.GetTexture(TestSkin.TEXTURE_NAME) != null) == mostPrioritisedSource; + }); + } + + private class TestSkinProvidingContainer : SkinProvidingContainer + { + private readonly IEnumerable sources; + + public TestSkinProvidingContainer(IEnumerable sources) + { + this.sources = sources; + } + + public new void TriggerSourceChanged() => base.TriggerSourceChanged(); + + protected override void OnSourceChanged() + { + ResetSources(); + sources.ForEach(AddSource); + } + } + + private class TestSkin : ISkin + { + public const string TEXTURE_NAME = "virtual-texture"; + + public Drawable GetDrawableComponent(ISkinComponent component) => throw new System.NotImplementedException(); + + public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) + { + if (componentName == TEXTURE_NAME) + return Texture.WhitePixel; + + return null; + } + + public ISample GetSample(ISampleInfo sampleInfo) => throw new System.NotImplementedException(); + + public IBindable GetConfig(TLookup lookup) => throw new System.NotImplementedException(); + } + } +} From 523546d8b636239a6e6213c0104439eefc9ecbb7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 22:51:56 +0900 Subject: [PATCH 451/457] Use List to guarantee lookup order --- osu.Game/Skinning/SkinProvidingContainer.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index d2f38b58a4..a628e97578 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -44,7 +44,7 @@ namespace osu.Game.Skinning /// /// A dictionary mapping each source to a wrapper which handles lookup allowances. /// - private readonly Dictionary skinSources = new Dictionary(); + private readonly List<(ISkin skin, DisableableSkinSource wrapped)> skinSources = new List<(ISkin, DisableableSkinSource)>(); /// /// Constructs a new initialised with a single skin source. @@ -70,7 +70,7 @@ namespace osu.Game.Skinning /// The skin to add. protected void AddSource(ISkin skin) { - skinSources.Add(skin, new DisableableSkinSource(skin, this)); + skinSources.Add((skin, new DisableableSkinSource(skin, this))); if (skin is ISkinSource source) source.SourceChanged += TriggerSourceChanged; @@ -82,7 +82,7 @@ namespace osu.Game.Skinning /// The skin to remove. protected void RemoveSource(ISkin skin) { - if (!skinSources.Remove(skin)) + if (skinSources.RemoveAll(s => s.skin == skin) == 0) return; if (skin is ISkinSource source) @@ -116,8 +116,8 @@ namespace osu.Game.Skinning { get { - foreach (var skin in skinSources.Keys) - yield return skin; + foreach (var i in skinSources) + yield return i.skin; if (AllowFallingBackToParent && ParentSource != null) { @@ -226,8 +226,11 @@ namespace osu.Game.Skinning if (ParentSource != null) ParentSource.SourceChanged -= TriggerSourceChanged; - foreach (var source in skinSources.Keys.OfType()) - source.SourceChanged -= TriggerSourceChanged; + foreach (var i in skinSources) + { + if (i.skin is ISkinSource source) + source.SourceChanged -= TriggerSourceChanged; + } } private class DisableableSkinSource : ISkin From eb8b14a931cffd9fb99858aecfc06ffaaef30ee8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 13:51:51 +0900 Subject: [PATCH 452/457] Reorder methods to make more sense --- osu.Game/Skinning/SkinProvidingContainer.cs | 84 ++++++++++----------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index a628e97578..f386900f64 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -64,38 +64,19 @@ namespace osu.Game.Skinning RelativeSizeAxes = Axes.Both; } - /// - /// Add a new skin to this provider. Will be added to the end of the lookup order precedence. - /// - /// The skin to add. - protected void AddSource(ISkin skin) + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - skinSources.Add((skin, new DisableableSkinSource(skin, this))); + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - if (skin is ISkinSource source) - source.SourceChanged += TriggerSourceChanged; - } + ParentSource = dependencies.Get(); + if (ParentSource != null) + ParentSource.SourceChanged += TriggerSourceChanged; - /// - /// Remove a skin from this provider. - /// - /// The skin to remove. - protected void RemoveSource(ISkin skin) - { - if (skinSources.RemoveAll(s => s.skin == skin) == 0) - return; + dependencies.CacheAs(this); - if (skin is ISkinSource source) - source.SourceChanged -= TriggerSourceChanged; - } + TriggerSourceChanged(); - /// - /// Clears all skin sources. - /// - protected void ResetSources() - { - foreach (var skin in AllSources.ToArray()) - RemoveSource(skin); + return dependencies; } public ISkin FindProvider(Func lookupFunction) @@ -187,27 +168,46 @@ namespace osu.Game.Skinning return ParentSource?.GetConfig(lookup); } + /// + /// Add a new skin to this provider. Will be added to the end of the lookup order precedence. + /// + /// The skin to add. + protected void AddSource(ISkin skin) + { + skinSources.Add((skin, new DisableableSkinSource(skin, this))); + + if (skin is ISkinSource source) + source.SourceChanged += TriggerSourceChanged; + } + + /// + /// Remove a skin from this provider. + /// + /// The skin to remove. + protected void RemoveSource(ISkin skin) + { + if (skinSources.RemoveAll(s => s.skin == skin) == 0) + return; + + if (skin is ISkinSource source) + source.SourceChanged -= TriggerSourceChanged; + } + + /// + /// Clears all skin sources. + /// + protected void ResetSources() + { + foreach (var skin in AllSources.ToArray()) + RemoveSource(skin); + } + /// /// Invoked when any source has changed (either or a source registered via ). /// This is also invoked once initially during to ensure sources are ready for children consumption. /// protected virtual void OnSourceChanged() { } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - ParentSource = dependencies.Get(); - if (ParentSource != null) - ParentSource.SourceChanged += TriggerSourceChanged; - - dependencies.CacheAs(this); - - TriggerSourceChanged(); - - return dependencies; - } - protected void TriggerSourceChanged() { // Expose to implementations, giving them a chance to react before notifying external consumers. From 35d4b12a4f7bb0bb42b388d6c70cdb2c3d58c4b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 13:52:52 +0900 Subject: [PATCH 453/457] Remove single local usage of `AllSources` --- osu.Game/Skinning/SkinProvidingContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index f386900f64..d8f931da5e 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -198,8 +198,8 @@ namespace osu.Game.Skinning /// protected void ResetSources() { - foreach (var skin in AllSources.ToArray()) - RemoveSource(skin); + foreach (var i in skinSources) + RemoveSource(i.skin); } /// From ca791c2afa550c44b85de3380fa6dfaf6e41f4c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 13:53:00 +0900 Subject: [PATCH 454/457] Remove unused using statement --- osu.Game/Skinning/SkinProvidingContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index d8f931da5e..d1a0187f7b 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; From c18b8ca86c5095fcec39eae075eec175f6648eff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 14:08:29 +0900 Subject: [PATCH 455/457] Add missing `ToArray()` call --- osu.Game/Skinning/SkinProvidingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index d1a0187f7b..7c26fdaf03 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -197,7 +197,7 @@ namespace osu.Game.Skinning /// protected void ResetSources() { - foreach (var i in skinSources) + foreach (var i in skinSources.ToArray()) RemoveSource(i.skin); } From b08fece2d4126ae061e5f5d68fba259015450443 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 14:30:47 +0900 Subject: [PATCH 456/457] 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 cfc28ffb21..9280eaf97c 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 646d21dfee..8a3c69e40c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index f36f7881cd..2eea646c61 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 35672f372a11fd69aa9e3ee67af5b7296131f32e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 14:58:01 +0900 Subject: [PATCH 457/457] Shorten test beatmap to avoid timeouts in score submission test --- .../Gameplay/TestScenePlayerScoreSubmission.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index d9c0544d3c..c3a46ec4ac 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -4,14 +4,18 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; +using osu.Game.Tests.Beatmaps; +using osuTK; namespace osu.Game.Tests.Visual.Gameplay { @@ -25,6 +29,15 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool HasCustomSteps => true; + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = (TestBeatmap)base.CreateBeatmap(ruleset); + + beatmap.HitObjects = beatmap.HitObjects.Take(10).ToList(); + + return beatmap; + } + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false); [Test]