From aab3277024a58e58cc8a31ed2a42c7ce73575413 Mon Sep 17 00:00:00 2001 From: emu1337 Date: Mon, 11 Oct 2021 03:12:57 +0200 Subject: [PATCH 001/803] changed speed acc scaling to be closer to worst case scenario --- .../Difficulty/OsuDifficultyAttributes.cs | 1 + .../Difficulty/OsuDifficultyCalculator.cs | 2 ++ .../Difficulty/OsuPerformanceCalculator.cs | 9 ++++++++- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 16 +++++++++++++++- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index bd4c0f2ad5..1f8b66edb7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public double AimStrain { get; set; } public double SpeedStrain { get; set; } + public double SpeedNoteCount { get; set; } public double FlashlightRating { get; set; } public double ApproachRate { get; set; } public double OverallDifficulty { get; set; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 790aa0eb7d..4160da9d5e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -36,6 +36,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double flashlightRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; + double speedNotes = (skills[1] as Speed).RelevantNoteCount(); if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; @@ -72,6 +73,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty Mods = mods, AimStrain = aimRating, SpeedStrain = speedRating, + SpeedNoteCount = speedNotes, FlashlightRating = flashlightRating, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 4e4dbc02a1..140c49393b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -168,8 +168,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); } + // Calculate accuracy assuming the worst case scenario + double relevantTotalDiff = totalHits - Attributes.SpeedNoteCount; + double relevantCountGreat = Math.Max(0, countGreat - relevantTotalDiff); + double relevantCountOk = Math.Max(0, countOk - Math.Max(0, relevantTotalDiff - countGreat)); + double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk)); + double relevantAccuracy = (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (Attributes.SpeedNoteCount * 6.0); + // Scale the speed value with accuracy and OD. - speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2); + speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2); // Scale the speed value with # of 50s to punish doubletapping. speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index cae6b8e01c..f8c1042a10 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -7,6 +7,8 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; using osu.Framework.Utils; +using System.Collections.Generic; +using System.Linq; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -33,6 +35,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private readonly double greatWindow; + private List objectStrains = new List(); + public Speed(Mod[] mods, double hitWindowGreat) : base(mods) { @@ -170,7 +174,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills currentRhythm = calculateRhythmBonus(current); - return currentStrain * currentRhythm; + double totalStrain = currentStrain * currentRhythm; + + objectStrains.Add(totalStrain); + + return totalStrain; + } + + public double RelevantNoteCount() + { + double maxStrain = objectStrains.Max(); + return objectStrains.Aggregate((total, next) => total + (1.0 / (1.0 + Math.Pow(Math.E, -(next/maxStrain * 12.0 - 6.0))))); } } } From d7483a6c5da7ace483c9e6cac31edf4f3ca0a67c Mon Sep 17 00:00:00 2001 From: emu1337 Date: Mon, 11 Oct 2021 19:58:03 +0200 Subject: [PATCH 002/803] changed pow to exp function --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index f8c1042a10..f477f8e17d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public double RelevantNoteCount() { double maxStrain = objectStrains.Max(); - return objectStrains.Aggregate((total, next) => total + (1.0 / (1.0 + Math.Pow(Math.E, -(next/maxStrain * 12.0 - 6.0))))); + return objectStrains.Aggregate((total, next) => total + (1.0 / (1.0 + Math.Exp(-(next/maxStrain * 12.0 - 6.0))))); } } } From f3eaa950416971ccfd10f8e492c4b8b61b53e92d Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Fri, 14 Jan 2022 23:16:07 +0100 Subject: [PATCH 003/803] Move input handler settings creation to `OsuGameBase` --- osu.Desktop/OsuGameDesktop.cs | 25 +++++++++++ osu.Game/OsuGameBase.cs | 21 ++++++++++ .../Settings/Sections/InputSection.cs | 41 ++----------------- 3 files changed, 49 insertions(+), 38 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index b234207848..a0c2a80bb8 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -20,8 +20,15 @@ using osu.Framework.Screens; using osu.Game.Screens.Menu; using osu.Game.Updater; using osu.Desktop.Windows; +using osu.Framework.Input.Handlers; +using osu.Framework.Input.Handlers.Joystick; +using osu.Framework.Input.Handlers.Mouse; +using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Threading; using osu.Game.IO; +using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections; +using osu.Game.Overlays.Settings.Sections.Input; namespace osu.Desktop { @@ -156,6 +163,24 @@ namespace osu.Desktop desktopWindow.DragDrop += f => fileDrop(new[] { f }); } + public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler) + { + switch (handler) + { + case ITabletHandler th: + return new TabletSettings(th); + + case MouseHandler mh: + return new MouseSettings(mh); + + case JoystickHandler _: + return new InputSection.HandlerSection(handler); + + default: + return base.CreateSettingsSubsectionFor(handler); + } + } + private readonly List importableFiles = new List(); private ScheduledDelegate importSchedule; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 9256514a0a..0059fc2204 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -17,6 +17,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Textures; using osu.Framework.Input; +using osu.Framework.Input.Handlers; +using osu.Framework.Input.Handlers.Midi; using osu.Framework.IO.Stores; using osu.Framework.Logging; using osu.Framework.Platform; @@ -35,6 +37,8 @@ using osu.Game.Online.Chat; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Overlays; +using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections; using osu.Game.Resources; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -448,6 +452,23 @@ namespace osu.Game protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage); + /// + /// Creates an input settings subsection for an . + /// + /// Should be overriden per-platform to provide settings for platform-specific handlers. + public virtual SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler) + { + switch (handler) + { + case MidiHandler _: + return new InputSection.HandlerSection(handler); + + // return null for handlers that shouldn't have settings. + default: + return null; + } + } + private void onRulesetChanged(ValueChangedEvent r) { if (r.NewValue?.Available != true) diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index d282ba5318..77076ae920 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -5,10 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Handlers; -using osu.Framework.Input.Handlers.Joystick; -using osu.Framework.Input.Handlers.Midi; -using osu.Framework.Input.Handlers.Mouse; -using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Localisation; @@ -22,9 +18,6 @@ namespace osu.Game.Overlays.Settings.Sections public override LocalisableString Header => InputSettingsStrings.InputSectionHeader; - [Resolved] - private GameHost host { get; set; } - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.Keyboard @@ -36,7 +29,7 @@ namespace osu.Game.Overlays.Settings.Sections } [BackgroundDependencyLoader] - private void load() + private void load(GameHost host, OsuGameBase game) { Children = new Drawable[] { @@ -45,42 +38,14 @@ namespace osu.Game.Overlays.Settings.Sections foreach (var handler in host.AvailableInputHandlers) { - var handlerSection = createSectionFor(handler); + var handlerSection = game.CreateSettingsSubsectionFor(handler); if (handlerSection != null) Add(handlerSection); } } - private SettingsSubsection createSectionFor(InputHandler handler) - { - SettingsSubsection section; - - switch (handler) - { - // ReSharper disable once SuspiciousTypeConversion.Global (net standard fuckery) - case ITabletHandler th: - section = new TabletSettings(th); - break; - - case MouseHandler mh: - section = new MouseSettings(mh); - break; - - // whitelist the handlers which should be displayed to avoid any weird cases of users touching settings they shouldn't. - case JoystickHandler _: - case MidiHandler _: - section = new HandlerSection(handler); - break; - - default: - return null; - } - - return section; - } - - private class HandlerSection : SettingsSubsection + public class HandlerSection : SettingsSubsection { private readonly InputHandler handler; From 037e56f13eca292e9b8b4a43a0eb4da262ac0fa8 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 15 Jan 2022 14:38:38 +0100 Subject: [PATCH 004/803] Add Android mouse settings --- osu.Android/AndroidMouseSettings.cs | 97 +++++++++++++++++++ osu.Android/OsuGameAndroid.cs | 22 +++++ osu.Android/osu.Android.csproj | 1 + .../Settings/Sections/Input/MouseSettings.cs | 4 +- 4 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 osu.Android/AndroidMouseSettings.cs diff --git a/osu.Android/AndroidMouseSettings.cs b/osu.Android/AndroidMouseSettings.cs new file mode 100644 index 0000000000..7dff929cd4 --- /dev/null +++ b/osu.Android/AndroidMouseSettings.cs @@ -0,0 +1,97 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Android.OS; +using osu.Framework.Allocation; +using osu.Framework.Android.Input; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Game.Configuration; +using osu.Game.Localisation; +using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections.Input; + +namespace osu.Android +{ + public class AndroidMouseSettings : SettingsSubsection + { + private readonly AndroidMouseHandler mouseHandler; + + protected override LocalisableString Header => MouseSettingsStrings.Mouse; + + private Bindable handlerSensitivity; + + private Bindable localSensitivity; + + private Bindable relativeMode; + + public AndroidMouseSettings(AndroidMouseHandler mouseHandler) + { + this.mouseHandler = mouseHandler; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager osuConfig) + { + // use local bindable to avoid changing enabled state of game host's bindable. + handlerSensitivity = mouseHandler.Sensitivity.GetBoundCopy(); + localSensitivity = handlerSensitivity.GetUnboundCopy(); + + relativeMode = mouseHandler.UseRelativeMode.GetBoundCopy(); + + // High precision/pointer capture is only available on Android 8.0 and up + if (Build.VERSION.SdkInt >= BuildVersionCodes.O) + { + AddRange(new Drawable[] + { + new SettingsCheckbox + { + LabelText = MouseSettingsStrings.HighPrecisionMouse, + TooltipText = MouseSettingsStrings.HighPrecisionMouseTooltip, + Current = relativeMode, + Keywords = new[] { @"raw", @"input", @"relative", @"cursor", @"captured", @"pointer" }, + }, + new MouseSettings.SensitivitySetting + { + LabelText = MouseSettingsStrings.CursorSensitivity, + Current = localSensitivity, + }, + }); + } + + AddRange(new Drawable[] + { + new SettingsCheckbox + { + LabelText = MouseSettingsStrings.DisableMouseWheelVolumeAdjust, + TooltipText = MouseSettingsStrings.DisableMouseWheelVolumeAdjustTooltip, + Current = osuConfig.GetBindable(OsuSetting.MouseDisableWheel), + }, + new SettingsCheckbox + { + LabelText = MouseSettingsStrings.DisableMouseButtons, + Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons), + }, + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + relativeMode.BindValueChanged(relative => localSensitivity.Disabled = !relative.NewValue, true); + + handlerSensitivity.BindValueChanged(val => + { + bool disabled = localSensitivity.Disabled; + + localSensitivity.Disabled = false; + localSensitivity.Value = val.NewValue; + localSensitivity.Disabled = disabled; + }, true); + + localSensitivity.BindValueChanged(val => handlerSensitivity.Value = val.NewValue); + } + } +} diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 050bf2b787..ba2527d71e 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -5,7 +5,11 @@ using System; using Android.App; using Android.OS; using osu.Framework.Allocation; +using osu.Framework.Android.Input; +using osu.Framework.Input.Handlers; +using osu.Framework.Platform; using osu.Game; +using osu.Game.Overlays.Settings; using osu.Game.Updater; using osu.Game.Utils; using Xamarin.Essentials; @@ -73,10 +77,28 @@ namespace osu.Android LoadComponentAsync(new GameplayScreenRotationLocker(), Add); } + public override void SetHost(GameHost host) + { + base.SetHost(host); + host.Window.CursorState |= CursorState.Hidden; + } + protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo(); + public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler) + { + switch (handler) + { + case AndroidMouseHandler mh: + return new AndroidMouseSettings(mh); + + default: + return base.CreateSettingsSubsectionFor(handler); + } + } + private class AndroidBatteryInfo : BatteryInfo { public override double ChargeLevel => Battery.ChargeLevel; diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index fc50ca9fa1..90b02c527b 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -26,6 +26,7 @@ true + diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 4235dc0a05..6bb97c1137 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -124,7 +124,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, true); } - private class SensitivitySetting : SettingsSlider + public class SensitivitySetting : SettingsSlider { public SensitivitySetting() { @@ -133,7 +133,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input } } - private class SensitivitySlider : OsuSliderBar + public class SensitivitySlider : OsuSliderBar { public override LocalisableString TooltipText => Current.Disabled ? MouseSettingsStrings.EnableHighPrecisionForSensitivityAdjust : $"{base.TooltipText}x"; } From d4af8498af6795213d648844d1c11da99f65f4e6 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 15 Jan 2022 14:48:41 +0100 Subject: [PATCH 005/803] Add iOS mouse settings Functionality is currently limited to some OsuSettings, but will expand in the future when high precision mouse is added. --- osu.iOS/IOSMouseSettings.cs | 36 ++++++++++++++++++++++++++++++++++++ osu.iOS/OsuGameIOS.cs | 15 +++++++++++++++ osu.iOS/osu.iOS.csproj | 1 + 3 files changed, 52 insertions(+) create mode 100644 osu.iOS/IOSMouseSettings.cs diff --git a/osu.iOS/IOSMouseSettings.cs b/osu.iOS/IOSMouseSettings.cs new file mode 100644 index 0000000000..1979a881f7 --- /dev/null +++ b/osu.iOS/IOSMouseSettings.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Game.Configuration; +using osu.Game.Localisation; +using osu.Game.Overlays.Settings; + +namespace osu.iOS +{ + public class IOSMouseSettings : SettingsSubsection + { + protected override LocalisableString Header => MouseSettingsStrings.Mouse; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager osuConfig) + { + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = MouseSettingsStrings.DisableMouseWheelVolumeAdjust, + TooltipText = MouseSettingsStrings.DisableMouseWheelVolumeAdjustTooltip, + Current = osuConfig.GetBindable(OsuSetting.MouseDisableWheel), + }, + new SettingsCheckbox + { + LabelText = MouseSettingsStrings.DisableMouseButtons, + Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons), + }, + }; + } + } +} diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index 702aef45f5..c3f7033752 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -3,7 +3,10 @@ using System; using Foundation; +using osu.Framework.Input.Handlers; +using osu.Framework.iOS.Input; using osu.Game; +using osu.Game.Overlays.Settings; using osu.Game.Updater; using osu.Game.Utils; using Xamarin.Essentials; @@ -18,6 +21,18 @@ namespace osu.iOS protected override BatteryInfo CreateBatteryInfo() => new IOSBatteryInfo(); + public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler) + { + switch (handler) + { + case IOSMouseHandler _: + return new IOSMouseSettings(); + + default: + return base.CreateSettingsSubsectionFor(handler); + } + } + private class IOSBatteryInfo : BatteryInfo { public override double ChargeLevel => Battery.ChargeLevel; diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index 1203c3659b..b9da874f30 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -23,6 +23,7 @@ + From a25b6e6a099233f02ec88121de7f9b508d32d378 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Thu, 10 Mar 2022 00:42:58 -0800 Subject: [PATCH 006/803] Proof of Concept draft for Taiko touch input --- osu.Game.Rulesets.Taiko/TaikoInputManager.cs | 2 + osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 - .../UI/DrawableTaikoRuleset.cs | 6 + .../UI/DrumTouchInputArea.cs | 141 ++++++++++++++++++ osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 5 +- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 1 + 6 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs diff --git a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs index 058bec5111..20766029bb 100644 --- a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs +++ b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; +using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko { + [Cached] // Used for touch input, see DrumTouchInputArea. public class TaikoInputManager : RulesetInputManager { public TaikoInputManager(RulesetInfo ruleset) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index e56aabaf9d..4a22619f06 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.Taiko public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { - new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), - new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim), new KeyBinding(InputKey.D, TaikoAction.LeftRim), new KeyBinding(InputKey.F, TaikoAction.LeftCentre), new KeyBinding(InputKey.J, TaikoAction.RightCentre), diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 824b95639b..cad134067f 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -52,6 +52,12 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.X, Depth = float.MaxValue }); + + DrumTouchInputArea touchInput = new DrumTouchInputArea(Playfield) { + RelativePositionAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, + }; + Overlays.Add(touchInput); } protected override void UpdateAfterChildren() diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs new file mode 100644 index 0000000000..647187226a --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -0,0 +1,141 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Framework.Logging; +using osu.Game.Graphics; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.UI +{ + /// + /// An overlay that captures and displays Taiko mouse and touch input. + /// The boundaries of this overlay defines the interactable area for touch input. + /// A secondary InputDrum is attached by this overlay, which defines the circulary boundary which distinguishes "centre" from "rim" hits, and also displays input. + /// + public class DrumTouchInputArea : Container + { + // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) + private const float overhangPercent = 0.33f; + private readonly InputDrum touchInputDrum; + + [Resolved(canBeNull: true)] + private TaikoInputManager taikoInputManager { get; set; } + + private KeyBindingContainer keyBindingContainer; + + // Which Taiko action was pressed by the last OnMouseDown event, so that the corresponding action can be released OnMouseUp even if the cursor position moved + private TaikoAction mouseAction; + + // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved + private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + + private Playfield playfield; + + public DrumTouchInputArea(Playfield playfield) { + this.playfield = playfield; + + RelativeSizeAxes = Axes.Both; + RelativePositionAxes = Axes.Both; + Children = new Drawable[] + { + new Box() { + Alpha = 0.0f, + Colour = new OsuColour().Blue, + + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + }, + new Container() { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + + Children = new Drawable[] + { + touchInputDrum = new InputDrum(playfield.HitObjectContainer) { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }, + }; + + } + protected override void LoadComplete() + { + keyBindingContainer = taikoInputManager?.KeyBindingContainer; + + Padding = new MarginPadding { + Top = playfield.ScreenSpaceDrawQuad.BottomLeft.Y, + Bottom = -DrawHeight * overhangPercent, + }; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); + keyBindingContainer?.TriggerPressed(mouseAction); + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + keyBindingContainer?.TriggerReleased(mouseAction); + base.OnMouseUp(e); + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); + if (touchActions.ContainsKey(e.Touch.Source)) { + touchActions[e.Touch.Source] = taikoAction; + } + else { + touchActions.Add(e.Touch.Source, taikoAction); + } + keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); + + return base.OnTouchDown(e); + } + + protected override void OnTouchUp(TouchUpEvent e) + { + keyBindingContainer?.TriggerReleased(touchActions[e.Touch.Source]); + base.OnTouchUp(e); + } + + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { + bool leftSide = inputIsOnLeftSide(inputPosition); + bool centreHit = inputIsCenterHit(inputPosition); + + return leftSide ? + (centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim) : + (centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim); + } + + private bool inputIsOnLeftSide(Vector2 inputPosition) { + Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; + return inputPositionToDrumCentreDelta.X < 0f; + } + + private bool inputIsCenterHit(Vector2 inputPosition) { + Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; + + float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; + float centreRadius = (inputDrumRadius * InputDrum.centre_size); + return inputPositionToDrumCentreDelta.Length <= centreRadius; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 16be20f7f3..1949d09de1 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { + public const float centre_size = 0.7f; private const float middle_split = 0.025f; [Cached] @@ -122,14 +123,14 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.7f) + Size = new Vector2(centre_size) }, centreHit = new Sprite { Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.7f), + Size = new Vector2(centre_size), Alpha = 0, Blending = BlendingParameters.Additive } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index d650cab729..2f7e0dc08f 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -144,6 +144,7 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, }, drumRollHitContainer.CreateProxy(), + // new DrumTouchInputArea(this), }; RegisterPool(50); From 317869078f470889330702b7b99b6146569b4f75 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Thu, 10 Mar 2022 05:09:07 -0800 Subject: [PATCH 007/803] Basic functionality for Taiko touch input now complete --- .../UI/DrawableTaikoRuleset.cs | 12 ++-- .../UI/DrumSamplePlayer.cs | 55 ++++++++++++++++ .../UI/DrumTouchInputArea.cs | 62 ++++++++----------- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 19 +----- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 4 +- 5 files changed, 93 insertions(+), 59 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index cad134067f..9cd5b0ca7f 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects; @@ -35,6 +36,8 @@ namespace osu.Game.Rulesets.Taiko.UI private SkinnableDrawable scroller; + private DrumTouchInputArea drumTouchInputArea; + public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { @@ -53,11 +56,8 @@ namespace osu.Game.Rulesets.Taiko.UI Depth = float.MaxValue }); - DrumTouchInputArea touchInput = new DrumTouchInputArea(Playfield) { - RelativePositionAxes = Axes.Both, - RelativeSizeAxes = Axes.Both, - }; - Overlays.Add(touchInput); + if ((drumTouchInputArea = CreateDrumTouchInputArea()) != null) + KeyBindingInputManager.Add(drumTouchInputArea); } protected override void UpdateAfterChildren() @@ -76,6 +76,8 @@ namespace osu.Game.Rulesets.Taiko.UI return ControlPoints[result]; } + public DrumTouchInputArea CreateDrumTouchInputArea() => new DrumTouchInputArea(); + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer(); protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs new file mode 100644 index 0000000000..458dbc6ca1 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Taiko.UI +{ + internal class DrumSamplePlayer : Container, IKeyBindingHandler { + private DrumSampleTriggerSource leftRimSampleTriggerSource; + private DrumSampleTriggerSource leftCentreSampleTriggerSource; + private DrumSampleTriggerSource rightCentreSampleTriggerSource; + private DrumSampleTriggerSource rightRimSampleTriggerSource; + + public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { + Children = new Drawable[] + { + leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + leftCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + rightCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + rightRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + }; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == TaikoAction.LeftRim) + { + leftRimSampleTriggerSource.Play(HitType.Rim); + } + else if (e.Action == TaikoAction.LeftCentre) + { + leftCentreSampleTriggerSource.Play(HitType.Centre); + } + else if (e.Action == TaikoAction.RightCentre) + { + rightCentreSampleTriggerSource.Play(HitType.Centre); + } + else if (e.Action == TaikoAction.RightRim) + { + rightRimSampleTriggerSource.Play(HitType.Rim); + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 647187226a..d610e84c8d 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -10,9 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Logging; using osu.Game.Graphics; -using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Taiko.UI @@ -25,11 +23,9 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float overhangPercent = 0.33f; - private readonly InputDrum touchInputDrum; - - [Resolved(canBeNull: true)] - private TaikoInputManager taikoInputManager { get; set; } + private const float overhangPercent = 0.35f; + private InputDrum touchInputDrum; + private Circle drumBackground; private KeyBindingContainer keyBindingContainer; @@ -39,55 +35,55 @@ namespace osu.Game.Rulesets.Taiko.UI // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); - private Playfield playfield; - - public DrumTouchInputArea(Playfield playfield) { - this.playfield = playfield; - + public DrumTouchInputArea() { RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; Children = new Drawable[] { - new Box() { - Alpha = 0.0f, - Colour = new OsuColour().Blue, - - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - }, new Container() { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, - Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Children = new Drawable[] { - touchInputDrum = new InputDrum(playfield.HitObjectContainer) { + drumBackground = new Circle() { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fit, + Alpha = 0.9f, + }, + touchInputDrum = new InputDrum() { Anchor = Anchor.Centre, Origin = Anchor.Centre, }, } }, }; - } + protected override void LoadComplete() { - keyBindingContainer = taikoInputManager?.KeyBindingContainer; - Padding = new MarginPadding { - Top = playfield.ScreenSpaceDrawQuad.BottomLeft.Y, - Bottom = -DrawHeight * overhangPercent, + Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield + Bottom = -touchInputDrum.DrawHeight * overhangPercent, // The drum should go past the bottom of the screen so that it can be wider }; } + [BackgroundDependencyLoader] + private void load(TaikoInputManager taikoInputManager, OsuColour colours) + { + keyBindingContainer = taikoInputManager?.KeyBindingContainer; + drumBackground.Colour = colours.Gray0; + } + protected override bool OnMouseDown(MouseDownEvent e) { mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); keyBindingContainer?.TriggerPressed(mouseAction); - return base.OnMouseDown(e); + return true; } protected override void OnMouseUp(MouseUpEvent e) @@ -99,20 +95,16 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnTouchDown(TouchDownEvent e) { TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); - if (touchActions.ContainsKey(e.Touch.Source)) { - touchActions[e.Touch.Source] = taikoAction; - } - else { - touchActions.Add(e.Touch.Source, taikoAction); - } + touchActions.Add(e.Touch.Source, taikoAction); keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); - return base.OnTouchDown(e); + return true; } protected override void OnTouchUp(TouchUpEvent e) { keyBindingContainer?.TriggerReleased(touchActions[e.Touch.Source]); + touchActions.Remove(e.Touch.Source); base.OnTouchUp(e); } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 1949d09de1..76594b86c1 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -10,8 +10,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK; @@ -25,13 +23,8 @@ namespace osu.Game.Rulesets.Taiko.UI public const float centre_size = 0.7f; private const float middle_split = 0.025f; - [Cached] - private DrumSampleTriggerSource sampleTriggerSource; - - public InputDrum(HitObjectContainer hitObjectContainer) + public InputDrum() { - sampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer); - RelativeSizeAxes = Axes.Both; } @@ -70,8 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI CentreAction = TaikoAction.RightCentre } } - }), - sampleTriggerSource + }) }; } @@ -95,9 +87,6 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - [Resolved] - private DrumSampleTriggerSource sampleTriggerSource { get; set; } - public TaikoHalfDrum(bool flipped) { Masking = true; @@ -158,15 +147,11 @@ namespace osu.Game.Rulesets.Taiko.UI { target = centreHit; back = centre; - - sampleTriggerSource.Play(HitType.Centre); } else if (e.Action == RimAction) { target = rimHit; back = rim; - - sampleTriggerSource.Play(HitType.Rim); } if (target != null) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 2f7e0dc08f..71aee107d2 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Taiko.UI Children = new Drawable[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), - new InputDrum(HitObjectContainer) + new InputDrum() { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, }, drumRollHitContainer.CreateProxy(), - // new DrumTouchInputArea(this), + new DrumSamplePlayer(HitObjectContainer), }; RegisterPool(50); From 5ce57fa34a70a23971e56495a96ee86081395166 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Thu, 10 Mar 2022 06:17:06 -0800 Subject: [PATCH 008/803] Improve readability for getTaikoActionFromInput --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index d610e84c8d..a2c33a85e8 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -109,12 +109,12 @@ namespace osu.Game.Rulesets.Taiko.UI } private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { - bool leftSide = inputIsOnLeftSide(inputPosition); bool centreHit = inputIsCenterHit(inputPosition); + bool leftSide = inputIsOnLeftSide(inputPosition); - return leftSide ? - (centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim) : - (centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim); + return centreHit ? + (leftSide ? TaikoAction.LeftCentre : TaikoAction.RightCentre) : + (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); } private bool inputIsOnLeftSide(Vector2 inputPosition) { From 38c61b2c1d088e788953e0246b94923dc9b896eb Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 00:14:33 -0800 Subject: [PATCH 009/803] Fix crash when loading legacy osu!taiko skins with touch input --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 43c5c07f80..321389676a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -8,8 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.Taiko.UI; using osu.Game.Skinning; using osuTK; @@ -112,9 +110,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public readonly Sprite Rim; public readonly Sprite Centre; - [Resolved] - private DrumSampleTriggerSource sampleTriggerSource { get; set; } - public LegacyHalfDrum(bool flipped) { Masking = true; @@ -149,12 +144,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy if (e.Action == CentreAction) { target = Centre; - sampleTriggerSource.Play(HitType.Centre); } else if (e.Action == RimAction) { target = Rim; - sampleTriggerSource.Play(HitType.Rim); } if (target != null) From 7336c2c0bd06010ea7fabc12a91833ab1bd4e3ab Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 02:48:08 -0800 Subject: [PATCH 010/803] Fix osu!taiko alignment issue with legacy skins on Touch Controls --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 321389676a..d3eeb8e16a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { Child = content = new Container { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Size = new Vector2(180, 200), Children = new Drawable[] { From 0f83308f7b1c18ebca3c89333954e26e34be2b56 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 02:49:10 -0800 Subject: [PATCH 011/803] Update osu!taiko TestSceneInputDrum with InputDrum changes for touch controls --- osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs index 24db046748..10fca30865 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200), - Child = new InputDrum(playfield.HitObjectContainer) + Child = new InputDrum() } }); } From 1ed06f30e7feff438354617b55a32f2022728ef7 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 03:33:03 -0800 Subject: [PATCH 012/803] osu!Taiko touch implementation code cleanup --- .../UI/DrumSamplePlayer.cs | 27 +++++++++---------- .../UI/DrumTouchInputArea.cs | 4 +-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index 458dbc6ca1..609da0b389 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -28,21 +28,20 @@ namespace osu.Game.Rulesets.Taiko.UI public bool OnPressed(KeyBindingPressEvent e) { - if (e.Action == TaikoAction.LeftRim) + switch (e.Action) { - leftRimSampleTriggerSource.Play(HitType.Rim); - } - else if (e.Action == TaikoAction.LeftCentre) - { - leftCentreSampleTriggerSource.Play(HitType.Centre); - } - else if (e.Action == TaikoAction.RightCentre) - { - rightCentreSampleTriggerSource.Play(HitType.Centre); - } - else if (e.Action == TaikoAction.RightRim) - { - rightRimSampleTriggerSource.Play(HitType.Rim); + case TaikoAction.LeftRim: + leftRimSampleTriggerSource.Play(HitType.Rim); + break; + case TaikoAction.LeftCentre: + leftCentreSampleTriggerSource.Play(HitType.Centre); + break; + case TaikoAction.RightCentre: + rightCentreSampleTriggerSource.Play(HitType.Centre); + break; + case TaikoAction.RightRim: + rightRimSampleTriggerSource.Play(HitType.Rim); + break; } return false; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index a2c33a85e8..ccf59507d3 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float overhangPercent = 0.35f; + private const float offscreenPercent = 0.35f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.UI { Padding = new MarginPadding { Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield - Bottom = -touchInputDrum.DrawHeight * overhangPercent, // The drum should go past the bottom of the screen so that it can be wider + Bottom = -touchInputDrum.DrawHeight * offscreenPercent, // The drum should go past the bottom of the screen so that it can be wider }; } From c33a661a4990d681ec595a7b35afc9016d6b144c Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 03:36:03 -0800 Subject: [PATCH 013/803] osu!taiko touch implementation syntax formatting cleanup --- osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index 609da0b389..d5215c05e9 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -10,13 +10,15 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.UI { - internal class DrumSamplePlayer : Container, IKeyBindingHandler { + internal class DrumSamplePlayer : Container, IKeyBindingHandler + { private DrumSampleTriggerSource leftRimSampleTriggerSource; private DrumSampleTriggerSource leftCentreSampleTriggerSource; private DrumSampleTriggerSource rightCentreSampleTriggerSource; private DrumSampleTriggerSource rightRimSampleTriggerSource; - public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { + public DrumSamplePlayer(HitObjectContainer hitObjectContainer) + { Children = new Drawable[] { leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), From 35053eaeba38a8e15b609b7ee18421c532a9321a Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 04:43:57 -0800 Subject: [PATCH 014/803] Show and hide osu!taiko touch controls overlay based on most recent input type detected --- .../UI/DrumTouchInputArea.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index ccf59507d3..4b2c9c9936 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -35,12 +35,15 @@ namespace osu.Game.Rulesets.Taiko.UI // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + private Container visibleComponents; + public DrumTouchInputArea() { RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; Children = new Drawable[] { - new Container() { + visibleComponents = new Container() { + Alpha = 0.0f, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Anchor = Anchor.BottomCentre, @@ -81,6 +84,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnMouseDown(MouseDownEvent e) { + ShowTouchControls(); mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); keyBindingContainer?.TriggerPressed(mouseAction); return true; @@ -94,6 +98,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnTouchDown(TouchDownEvent e) { + ShowTouchControls(); TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); touchActions.Add(e.Touch.Source, taikoAction); keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); @@ -108,6 +113,21 @@ namespace osu.Game.Rulesets.Taiko.UI base.OnTouchUp(e); } + protected override bool OnKeyDown(KeyDownEvent e) + { + HideTouchControls(); + return false; + } + + + public void ShowTouchControls() { + visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); + } + + public void HideTouchControls() { + visibleComponents.Animate(components => components.FadeOut(2000, Easing.OutQuint)); + } + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { bool centreHit = inputIsCenterHit(inputPosition); bool leftSide = inputIsOnLeftSide(inputPosition); From 848b005097de38e213d7f33cf474e33840b7af6f Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 04:48:57 -0800 Subject: [PATCH 015/803] Remove unneccessary whitespace --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 4b2c9c9936..a944d3770b 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -119,7 +119,6 @@ namespace osu.Game.Rulesets.Taiko.UI return false; } - public void ShowTouchControls() { visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); } From ac17c047f6264eff3c289810f83f1f5110e495d0 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 05:01:40 -0800 Subject: [PATCH 016/803] Code formatting --- .../UI/DrumTouchInputArea.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index a944d3770b..046816c013 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -37,7 +37,8 @@ namespace osu.Game.Rulesets.Taiko.UI private Container visibleComponents; - public DrumTouchInputArea() { + public DrumTouchInputArea() + { RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; Children = new Drawable[] @@ -102,7 +103,6 @@ namespace osu.Game.Rulesets.Taiko.UI TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); touchActions.Add(e.Touch.Source, taikoAction); keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); - return true; } @@ -119,15 +119,18 @@ namespace osu.Game.Rulesets.Taiko.UI return false; } - public void ShowTouchControls() { + public void ShowTouchControls() + { visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); } - public void HideTouchControls() { + public void HideTouchControls() + { visibleComponents.Animate(components => components.FadeOut(2000, Easing.OutQuint)); } - private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) + { bool centreHit = inputIsCenterHit(inputPosition); bool leftSide = inputIsOnLeftSide(inputPosition); @@ -136,12 +139,14 @@ namespace osu.Game.Rulesets.Taiko.UI (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); } - private bool inputIsOnLeftSide(Vector2 inputPosition) { + private bool inputIsOnLeftSide(Vector2 inputPosition) + { Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; return inputPositionToDrumCentreDelta.X < 0f; } - private bool inputIsCenterHit(Vector2 inputPosition) { + private bool inputIsCenterHit(Vector2 inputPosition) + { Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; From 6f99455d9490469af3938fe19fcef361bdab1098 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 05:17:45 -0800 Subject: [PATCH 017/803] Improve centre input size fitting for legacy skins --- .../Skinning/Legacy/LegacyInputDrum.cs | 1 + osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 2 +- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index d3eeb8e16a..8ab9e56aec 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy /// internal class LegacyInputDrum : Container { + public float centre_size = 0.5f; private LegacyHalfDrum left; private LegacyHalfDrum right; private Container content; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 046816c013..5f4eecd3bb 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.UI Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; - float centreRadius = (inputDrumRadius * InputDrum.centre_size); + float centreRadius = (inputDrumRadius * touchInputDrum.centre_size); return inputPositionToDrumCentreDelta.Length <= centreRadius; } } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 76594b86c1..a7ae763ab5 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { - public const float centre_size = 0.7f; + public float centre_size = 0.7f; private const float middle_split = 0.025f; public InputDrum() @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.UI Scale = new Vector2(0.9f), Children = new Drawable[] { - new TaikoHalfDrum(false) + new TaikoHalfDrum(false, centre_size) { Name = "Left Half", Anchor = Anchor.Centre, @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new TaikoHalfDrum(true) + new TaikoHalfDrum(true, centre_size) { Name = "Right Half", Anchor = Anchor.Centre, @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - public TaikoHalfDrum(bool flipped) + public TaikoHalfDrum(bool flipped, float centre_size) { Masking = true; From 9db80c33350d194fef952c0fb98e0c1252b09097 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 05:32:02 -0800 Subject: [PATCH 018/803] Code cleanup --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 5f4eecd3bb..82b289ef14 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.UI // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) private const float offscreenPercent = 0.35f; private InputDrum touchInputDrum; - private Circle drumBackground; + private Circle drumBackground; private KeyBindingContainer keyBindingContainer; @@ -70,7 +70,8 @@ namespace osu.Game.Rulesets.Taiko.UI protected override void LoadComplete() { - Padding = new MarginPadding { + Padding = new MarginPadding + { Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield Bottom = -touchInputDrum.DrawHeight * offscreenPercent, // The drum should go past the bottom of the screen so that it can be wider }; From 2de6bb033bffdaeeb8a1095264b9ad745b0065c3 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 07:51:40 -0800 Subject: [PATCH 019/803] Adjust default touch drum overlay size to be more comfortable on phones --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 82b289ef14..e501a40d20 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreenPercent = 0.35f; + private const float offscreenPercent = 0.4f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -62,6 +62,7 @@ namespace osu.Game.Rulesets.Taiko.UI touchInputDrum = new InputDrum() { Anchor = Anchor.Centre, Origin = Anchor.Centre, + centre_size = 0.8f, }, } }, From b628a65cfa5e790961ec3db6edc27fe41c89334b Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 08:03:24 -0800 Subject: [PATCH 020/803] Revert "Adjust default touch drum overlay size to be more comfortable on phones" This reverts commit 2de6bb033bffdaeeb8a1095264b9ad745b0065c3. --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index e501a40d20..82b289ef14 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreenPercent = 0.4f; + private const float offscreenPercent = 0.35f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -62,7 +62,6 @@ namespace osu.Game.Rulesets.Taiko.UI touchInputDrum = new InputDrum() { Anchor = Anchor.Centre, Origin = Anchor.Centre, - centre_size = 0.8f, }, } }, From ac329664276eeaec1a41b2284e69dfefc065ac9c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 3 Apr 2022 01:31:51 +0300 Subject: [PATCH 021/803] Replicate osu!(stable)'s hit target position with "Classic" mod --- osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs | 3 +++ osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index 5a6f57bc36..d18b88761d 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -16,6 +16,9 @@ namespace osu.Game.Rulesets.Taiko.Mods { drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset; drawableTaikoRuleset.LockPlayfieldAspect.Value = false; + + var playfield = (TaikoPlayfield)drawableRuleset.Playfield; + playfield.ClassicHitTargetPosition.Value = true; } public void Update(Playfield playfield) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 504b10e9bc..f2fa63c99e 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; @@ -31,6 +32,11 @@ namespace osu.Game.Rulesets.Taiko.UI /// public const float DEFAULT_HEIGHT = 200; + /// + /// Whether the hit target should be nudged further towards the left area, matching the stable "classic" position. + /// + public Bindable ClassicHitTargetPosition = new BindableBool(); + private Container hitExplosionContainer; private Container kiaiExplosionContainer; private JudgementContainer judgementContainer; @@ -190,7 +196,7 @@ namespace osu.Game.Rulesets.Taiko.UI // Padding is required to be updated for elements which are based on "absolute" X sized elements. // This is basically allowing for correct alignment as relative pieces move around them. - rightArea.Padding = new MarginPadding { Left = leftArea.DrawWidth }; + rightArea.Padding = new MarginPadding { Left = leftArea.DrawWidth - (ClassicHitTargetPosition.Value ? 45 : 0) }; hitTargetOffsetContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; mascot.Scale = new Vector2(DrawHeight / DEFAULT_HEIGHT); From 16f626fb646a637cc908a7d1733457008b7d10ad Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 6 Apr 2022 01:21:19 +0300 Subject: [PATCH 022/803] Add legacy classic taiko player test scene --- .../TestSceneLegacyTaikoPlayer.cs | 28 +++++++++++++++++++ .../TestSceneTaikoPlayer.cs | 9 ++++++ 2 files changed, 37 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyTaikoPlayer.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyTaikoPlayer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyTaikoPlayer.cs new file mode 100644 index 0000000000..d1f80768b6 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyTaikoPlayer.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Game.Database; +using osu.Game.Rulesets.Taiko.Mods; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class TestSceneLegacyTaikoPlayer : PlayerTestScene + { + protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset(); + + [BackgroundDependencyLoader] + private void load(SkinManager skins) + { + skins.CurrentSkinInfo.Value = DefaultLegacySkin.CreateInfo().ToLiveUnmanaged(); + } + + protected override TestPlayer CreatePlayer(Ruleset ruleset) + { + SelectedMods.Value = new[] { new TaikoModClassic() }; + return base.CreatePlayer(ruleset); + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs index cd7511241a..8b7a2d0c13 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs @@ -1,6 +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 osu.Framework.Allocation; +using osu.Game.Database; +using osu.Game.Skinning; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests @@ -8,5 +11,11 @@ namespace osu.Game.Rulesets.Taiko.Tests public class TestSceneTaikoPlayer : PlayerTestScene { protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset(); + + [BackgroundDependencyLoader] + private void load(SkinManager skins) + { + skins.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLiveUnmanaged(); + } } } From 55c56c03a59837e6265174c80085ec62c61b0771 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 6 Apr 2022 02:34:07 +0300 Subject: [PATCH 023/803] Propagate legacy input drum size to main piece --- .../Skinning/Legacy/LegacyInputDrum.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 43c5c07f80..0d5ce6201b 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public LegacyInputDrum() { - RelativeSizeAxes = Axes.Both; + Size = new Vector2(180, 200); } [BackgroundDependencyLoader] @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { Child = content = new Container { - Size = new Vector2(180, 200), + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { new Sprite @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy const float ratio = 1.6f; // because the right half is flipped, we need to position using width - position to get the true "topleft" origin position - float negativeScaleAdjust = content.Width / ratio; + float negativeScaleAdjust = Width / ratio; if (skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value >= 2.1m) { @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy // Relying on RelativeSizeAxes.Both + FillMode.Fit doesn't work due to the precise pixel layout requirements. // This is a bit ugly but makes the non-legacy implementations a lot cleaner to implement. - content.Scale = new Vector2(DrawHeight / content.Size.Y); + Scale = new Vector2(Parent.DrawHeight / Size.Y); } /// From e4f6e842b045b6bf09be49dc1821554aa0ed70cc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 6 Apr 2022 02:34:35 +0300 Subject: [PATCH 024/803] Expose input drum `SkinnableDrawable` in `InputDrum` for width consumption This is probably not a good way to approach this, but I'm unsure about any other way. --- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 16be20f7f3..1c76a214ed 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Taiko.UI { private const float middle_split = 0.025f; + public SkinnableDrawable Skinnable { get; private set; } + [Cached] private DrumSampleTriggerSource sampleTriggerSource; @@ -39,7 +41,7 @@ namespace osu.Game.Rulesets.Taiko.UI { Children = new Drawable[] { - new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container + Skinnable = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, @@ -69,7 +71,10 @@ namespace osu.Game.Rulesets.Taiko.UI CentreAction = TaikoAction.RightCentre } } - }), + }) + { + CentreComponent = false, + }, sampleTriggerSource }; } From ec5ad995f830bd74b8f4ecae8d8e5f7898250948 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 6 Apr 2022 02:37:51 +0300 Subject: [PATCH 025/803] Reorder taiko playfield elements to fix hit explosion Z-ordering --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 95 +++++++++++--------- 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index f2fa63c99e..63b9d4d9ad 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -48,8 +48,8 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly IDictionary explosionPools = new Dictionary(); private ProxyContainer topLevelHitContainer; + private InputDrum inputDrum; private Container rightArea; - private Container leftArea; /// /// is purposefully not called on this to prevent i.e. being able to interact @@ -57,14 +57,36 @@ namespace osu.Game.Rulesets.Taiko.UI /// private BarLinePlayfield barLinePlayfield; - private Container hitTargetOffsetContent; + private Container playfieldContent; + private Container playfieldOverlay; [BackgroundDependencyLoader] private void load(OsuColour colours) { + Container leftArea = null; + InternalChildren = new[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundRight), _ => new PlayfieldBackgroundRight()), + leftArea = new Container + { + Name = "Left overlay", + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + BorderColour = colours.Gray0, + Children = new Drawable[] + { + new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), + } + }, + mascot = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Mascot), _ => Empty()) + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.TopLeft, + RelativePositionAxes = Axes.Y, + RelativeSizeAxes = Axes.None, + Y = 0.2f + }, rightArea = new Container { Name = "Right area", @@ -74,7 +96,7 @@ namespace osu.Game.Rulesets.Taiko.UI { new Container { - Name = "Masked elements before hit objects", + Name = "Elements before hit objects", RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, Children = new[] @@ -89,22 +111,28 @@ namespace osu.Game.Rulesets.Taiko.UI } } }, - hitTargetOffsetContent = new Container + new Container { + Name = "Masked hit objects content", + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = playfieldContent = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + barLinePlayfield = new BarLinePlayfield(), + HitObjectContainer, + } + } + }, + playfieldOverlay = new Container + { + Name = "Elements after hit objects", RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - barLinePlayfield = new BarLinePlayfield(), - new Container - { - Name = "Hit objects", - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - HitObjectContainer, - drumRollHitContainer = new DrumRollHitContainer() - } - }, + drumRollHitContainer = new DrumRollHitContainer(), kiaiExplosionContainer = new Container { Name = "Kiai hit explosions", @@ -120,38 +148,21 @@ namespace osu.Game.Rulesets.Taiko.UI }, } }, - leftArea = new Container - { - Name = "Left overlay", - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - BorderColour = colours.Gray0, - Children = new Drawable[] - { - new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), - new InputDrum(HitObjectContainer) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - } - }, - mascot = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Mascot), _ => Empty()) - { - Origin = Anchor.BottomLeft, - Anchor = Anchor.TopLeft, - RelativePositionAxes = Axes.Y, - RelativeSizeAxes = Axes.None, - Y = 0.2f - }, topLevelHitContainer = new ProxyContainer { Name = "Top level hit objects", RelativeSizeAxes = Axes.Both, }, drumRollHitContainer.CreateProxy(), + inputDrum = new InputDrum(HitObjectContainer) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, }; + leftArea.Add(inputDrum.CreateProxy()); + RegisterPool(50); RegisterPool(50); @@ -196,8 +207,10 @@ namespace osu.Game.Rulesets.Taiko.UI // Padding is required to be updated for elements which are based on "absolute" X sized elements. // This is basically allowing for correct alignment as relative pieces move around them. - rightArea.Padding = new MarginPadding { Left = leftArea.DrawWidth - (ClassicHitTargetPosition.Value ? 45 : 0) }; - hitTargetOffsetContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; + var inputDrumSize = inputDrum.Skinnable.Drawable.ToSpaceOfOtherDrawable(inputDrum.Skinnable.Drawable.DrawSize, this); + rightArea.Padding = new MarginPadding { Left = inputDrumSize.X }; + playfieldContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; + playfieldOverlay.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; mascot.Scale = new Vector2(DrawHeight / DEFAULT_HEIGHT); } From ce70957fbfa0d9b6d0d78d3c96e886c98b052cd9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 6 Apr 2022 02:38:56 +0300 Subject: [PATCH 026/803] Remove redundant code --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 3 +-- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 0d5ce6201b..5ff952f929 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { private LegacyHalfDrum left; private LegacyHalfDrum right; - private Container content; public LegacyInputDrum() { @@ -32,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy [BackgroundDependencyLoader] private void load(ISkinSource skin) { - Child = content = new Container + Child = new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 63b9d4d9ad..610078600a 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load(OsuColour colours) { - Container leftArea = null; + Container leftArea; InternalChildren = new[] { From c1693e4387c51be66faf53d38bf0e1706d0a54a1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 7 Apr 2022 00:12:40 +0300 Subject: [PATCH 027/803] Use `LegacySkinPlayerTestScene` instead of reimplementing --- osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs | 9 --------- ...ikoPlayer.cs => TestSceneTaikoPlayerLegacySkin.cs} | 11 +---------- 2 files changed, 1 insertion(+), 19 deletions(-) rename osu.Game.Rulesets.Taiko.Tests/{TestSceneLegacyTaikoPlayer.cs => TestSceneTaikoPlayerLegacySkin.cs} (62%) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs index 8b7a2d0c13..cd7511241a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs @@ -1,9 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Game.Database; -using osu.Game.Skinning; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests @@ -11,11 +8,5 @@ namespace osu.Game.Rulesets.Taiko.Tests public class TestSceneTaikoPlayer : PlayerTestScene { protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset(); - - [BackgroundDependencyLoader] - private void load(SkinManager skins) - { - skins.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLiveUnmanaged(); - } } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyTaikoPlayer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerLegacySkin.cs similarity index 62% rename from osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyTaikoPlayer.cs rename to osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerLegacySkin.cs index d1f80768b6..13df24c988 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyTaikoPlayer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerLegacySkin.cs @@ -1,24 +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.Framework.Allocation; -using osu.Game.Database; using osu.Game.Rulesets.Taiko.Mods; -using osu.Game.Skinning; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { - public class TestSceneLegacyTaikoPlayer : PlayerTestScene + public class TestSceneTaikoPlayerLegacySkin : LegacySkinPlayerTestScene { protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset(); - [BackgroundDependencyLoader] - private void load(SkinManager skins) - { - skins.CurrentSkinInfo.Value = DefaultLegacySkin.CreateInfo().ToLiveUnmanaged(); - } - protected override TestPlayer CreatePlayer(Ruleset ruleset) { SelectedMods.Value = new[] { new TaikoModClassic() }; From b84a3b7834df1604a60e928b4dd211be67904049 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 7 Apr 2022 21:39:53 +0300 Subject: [PATCH 028/803] Rewrite input drum measurements to autosize on X axis --- .../Skinning/Legacy/LegacyInputDrum.cs | 12 +- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 252 ++++++++++-------- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 20 +- 3 files changed, 154 insertions(+), 130 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 5ff952f929..476ad4a75d 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -20,20 +20,22 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy /// internal class LegacyInputDrum : Container { + private Container content; private LegacyHalfDrum left; private LegacyHalfDrum right; public LegacyInputDrum() { - Size = new Vector2(180, 200); + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; } [BackgroundDependencyLoader] private void load(ISkinSource skin) { - Child = new Container + Child = content = new Container { - RelativeSizeAxes = Axes.Both, + Size = new Vector2(180, 200), Children = new Drawable[] { new Sprite @@ -66,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy const float ratio = 1.6f; // because the right half is flipped, we need to position using width - position to get the true "topleft" origin position - float negativeScaleAdjust = Width / ratio; + float negativeScaleAdjust = content.Width / ratio; if (skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value >= 2.1m) { @@ -90,7 +92,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy // Relying on RelativeSizeAxes.Both + FillMode.Fit doesn't work due to the precise pixel layout requirements. // This is a bit ugly but makes the non-legacy implementations a lot cleaner to implement. - Scale = new Vector2(Parent.DrawHeight / Size.Y); + content.Scale = new Vector2(DrawHeight / content.Size.Y); } /// diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 1c76a214ed..ad77eb100b 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -12,6 +12,7 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osuTK; @@ -24,8 +25,6 @@ namespace osu.Game.Rulesets.Taiko.UI { private const float middle_split = 0.025f; - public SkinnableDrawable Skinnable { get; private set; } - [Cached] private DrumSampleTriggerSource sampleTriggerSource; @@ -33,7 +32,8 @@ namespace osu.Game.Rulesets.Taiko.UI { sampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer); - RelativeSizeAxes = Axes.Both; + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; } [BackgroundDependencyLoader] @@ -41,12 +41,32 @@ namespace osu.Game.Rulesets.Taiko.UI { Children = new Drawable[] { - Skinnable = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container + new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new DefaultInputDrum()) { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + }, + sampleTriggerSource + }; + } + + private class DefaultInputDrum : AspectContainer + { + public DefaultInputDrum() + { + RelativeSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, Scale = new Vector2(0.9f), - Children = new Drawable[] + Children = new[] { new TaikoHalfDrum(false) { @@ -71,134 +91,130 @@ namespace osu.Game.Rulesets.Taiko.UI CentreAction = TaikoAction.RightCentre } } - }) - { - CentreComponent = false, - }, - sampleTriggerSource - }; - } - - /// - /// A half-drum. Contains one centre and one rim hit. - /// - private class TaikoHalfDrum : Container, IKeyBindingHandler - { - /// - /// The key to be used for the rim of the half-drum. - /// - public TaikoAction RimAction; - - /// - /// The key to be used for the centre of the half-drum. - /// - public TaikoAction CentreAction; - - private readonly Sprite rim; - private readonly Sprite rimHit; - private readonly Sprite centre; - private readonly Sprite centreHit; - - [Resolved] - private DrumSampleTriggerSource sampleTriggerSource { get; set; } - - public TaikoHalfDrum(bool flipped) - { - Masking = true; - - Children = new Drawable[] - { - rim = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both - }, - rimHit = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Blending = BlendingParameters.Additive, - }, - centre = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.7f) - }, - centreHit = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.7f), - Alpha = 0, - Blending = BlendingParameters.Additive - } }; } - [BackgroundDependencyLoader] - private void load(TextureStore textures, OsuColour colours) + /// + /// A half-drum. Contains one centre and one rim hit. + /// + private class TaikoHalfDrum : Container, IKeyBindingHandler { - rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer"); - rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit"); - centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner"); - centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit"); + /// + /// The key to be used for the rim of the half-drum. + /// + public TaikoAction RimAction; - rimHit.Colour = colours.Blue; - centreHit.Colour = colours.Pink; - } + /// + /// The key to be used for the centre of the half-drum. + /// + public TaikoAction CentreAction; - public bool OnPressed(KeyBindingPressEvent e) - { - Drawable target = null; - Drawable back = null; + private readonly Sprite rim; + private readonly Sprite rimHit; + private readonly Sprite centre; + private readonly Sprite centreHit; - if (e.Action == CentreAction) + [Resolved] + private DrumSampleTriggerSource sampleTriggerSource { get; set; } + + public TaikoHalfDrum(bool flipped) { - target = centreHit; - back = centre; + Masking = true; - sampleTriggerSource.Play(HitType.Centre); - } - else if (e.Action == RimAction) - { - target = rimHit; - back = rim; - - sampleTriggerSource.Play(HitType.Rim); + Children = new Drawable[] + { + rim = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both + }, + rimHit = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Blending = BlendingParameters.Additive, + }, + centre = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.7f) + }, + centreHit = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.7f), + Alpha = 0, + Blending = BlendingParameters.Additive + } + }; } - if (target != null) + [BackgroundDependencyLoader] + private void load(TextureStore textures, OsuColour colours) { - const float scale_amount = 0.05f; - const float alpha_amount = 0.5f; + rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer"); + rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit"); + centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner"); + centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit"); - const float down_time = 40; - const float up_time = 1000; - - back.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint) - .Then() - .ScaleTo(1, up_time, Easing.OutQuint); - - target.Animate( - t => t.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint), - t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, Easing.OutQuint) - ).Then( - t => t.ScaleTo(1, up_time, Easing.OutQuint), - t => t.FadeOut(up_time, Easing.OutQuint) - ); + rimHit.Colour = colours.Blue; + centreHit.Colour = colours.Pink; } - return false; - } + public bool OnPressed(KeyBindingPressEvent e) + { + Drawable target = null; + Drawable back = null; - public void OnReleased(KeyBindingReleaseEvent e) - { + if (e.Action == CentreAction) + { + target = centreHit; + back = centre; + + sampleTriggerSource.Play(HitType.Centre); + } + else if (e.Action == RimAction) + { + target = rimHit; + back = rim; + + sampleTriggerSource.Play(HitType.Rim); + } + + if (target != null) + { + const float scale_amount = 0.05f; + const float alpha_amount = 0.5f; + + const float down_time = 40; + const float up_time = 1000; + + back.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint) + .Then() + .ScaleTo(1, up_time, Easing.OutQuint); + + target.Animate( + t => t.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint), + t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, Easing.OutQuint) + ).Then( + t => t.ScaleTo(1, up_time, Easing.OutQuint), + t => t.FadeOut(up_time, Easing.OutQuint) + ); + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } } } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 610078600a..5891672f29 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -77,6 +77,13 @@ namespace osu.Game.Rulesets.Taiko.UI Children = new Drawable[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), + inputDrum = new InputDrum(HitObjectContainer) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + }, } }, mascot = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Mascot), _ => Empty()) @@ -154,13 +161,13 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, }, drumRollHitContainer.CreateProxy(), - inputDrum = new InputDrum(HitObjectContainer) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, }; + // to prioritise receiving key presses on input drum before objects, move input drum to the end of the hierarchy... + leftArea.Remove(inputDrum); + AddInternal(inputDrum); + + // ...and create a proxy to keep the input drum displayed behind the playfield elements. leftArea.Add(inputDrum.CreateProxy()); RegisterPool(50); @@ -207,8 +214,7 @@ namespace osu.Game.Rulesets.Taiko.UI // Padding is required to be updated for elements which are based on "absolute" X sized elements. // This is basically allowing for correct alignment as relative pieces move around them. - var inputDrumSize = inputDrum.Skinnable.Drawable.ToSpaceOfOtherDrawable(inputDrum.Skinnable.Drawable.DrawSize, this); - rightArea.Padding = new MarginPadding { Left = inputDrumSize.X }; + rightArea.Padding = new MarginPadding { Left = inputDrum.Width }; playfieldContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; playfieldOverlay.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; From f48533b8a292d95202f1a3833e1569391c5a160a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 13 Apr 2022 04:38:07 +0300 Subject: [PATCH 029/803] Inline input drum proxying logic --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 30 +++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 5891672f29..d75e3fb303 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -63,27 +63,27 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load(OsuColour colours) { - Container leftArea; + inputDrum = new InputDrum(HitObjectContainer) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + }; InternalChildren = new[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundRight), _ => new PlayfieldBackgroundRight()), - leftArea = new Container + new Container { Name = "Left overlay", RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, BorderColour = colours.Gray0, - Children = new Drawable[] + Children = new[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), - inputDrum = new InputDrum(HitObjectContainer) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - }, + inputDrum.CreateProxy(), } }, mascot = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Mascot), _ => Empty()) @@ -161,15 +161,11 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, }, drumRollHitContainer.CreateProxy(), + // this is added at the end of the hierarchy to receive input before taiko objects. + // but is proxied below everything to not cover visual effects such as hit explosions. + inputDrum, }; - // to prioritise receiving key presses on input drum before objects, move input drum to the end of the hierarchy... - leftArea.Remove(inputDrum); - AddInternal(inputDrum); - - // ...and create a proxy to keep the input drum displayed behind the playfield elements. - leftArea.Add(inputDrum.CreateProxy()); - RegisterPool(50); RegisterPool(50); From 10287e01505880f236667a636968c814f250c137 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 26 May 2022 00:08:00 -0400 Subject: [PATCH 030/803] initial implementation --- .../Mods/OsuEaseHitObjectPositionsMod.cs | 75 +++++++++++++++++++ .../Mods/OsuModMagnetised.cs | 59 ++------------- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 39 ++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- 4 files changed, 121 insertions(+), 54 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs new file mode 100644 index 0000000000..d81d86dc65 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -0,0 +1,75 @@ +// 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.Framework.Utils; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public abstract class OsuEaseHitObjectPositionsMod : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset + { + public override ModType Type => ModType.Fun; + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; + + protected BindableFloat EasementStrength = new BindableFloat(0.5f); + protected Vector2 CursorPosition; + protected DrawableHitObject WorkingHitObject; + protected abstract Vector2 DestinationVector { get; } + + private IFrameStableClock gameplayClock; + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + gameplayClock = drawableRuleset.FrameStableClock; + + // Hide judgment displays and follow points as they won't make any sense. + // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. + drawableRuleset.Playfield.DisplayJudgements.Value = false; + (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); + } + + public void Update(Playfield playfield) + { + CursorPosition = playfield.Cursor.ActiveCursor.DrawPosition; + + foreach (var drawable in playfield.HitObjectContainer.AliveObjects) + { + WorkingHitObject = drawable; + switch (drawable) + { + case DrawableHitCircle circle: + easeHitObjectPositionToVector(circle, DestinationVector); + break; + + case DrawableSlider slider: + + if (!slider.HeadCircle.Result.HasResult) + easeHitObjectPositionToVector(slider, DestinationVector); + else + easeHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); + + break; + } + } + } + + private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) + { + double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value); + + float x = (float)Interpolation.DampContinuously(hitObject.X, Math.Clamp(destination.X, 0, OsuPlayfield.BASE_SIZE.X), dampLength, gameplayClock.ElapsedFrameTime); + float y = (float)Interpolation.DampContinuously(hitObject.Y, Math.Clamp(destination.Y, 0, OsuPlayfield.BASE_SIZE.Y), dampLength, gameplayClock.ElapsedFrameTime); + + hitObject.Position = new Vector2(x, y); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index ca6e9cfb1d..ca69085e31 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -2,31 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; -using osu.Framework.Utils; using osu.Game.Configuration; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModMagnetised : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset + internal class OsuModMagnetised : OsuEaseHitObjectPositionsMod { public override string Name => "Magnetised"; public override string Acronym => "MG"; public override IconUsage? Icon => FontAwesome.Solid.Magnet; - public override ModType Type => ModType.Fun; public override string Description => "No need to chase the circles – your cursor is a magnet!"; - public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax) }; + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModRelax), typeof(OsuModRepel) }).ToArray(); - private IFrameStableClock gameplayClock; + protected override Vector2 DestinationVector => CursorPosition; [SettingSource("Attraction strength", "How strong the pull is.", 0)] public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f) @@ -36,48 +28,9 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + public OsuModMagnetised() { - gameplayClock = drawableRuleset.FrameStableClock; - - // Hide judgment displays and follow points as they won't make any sense. - // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. - drawableRuleset.Playfield.DisplayJudgements.Value = false; - (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); - } - - public void Update(Playfield playfield) - { - var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - - foreach (var drawable in playfield.HitObjectContainer.AliveObjects) - { - switch (drawable) - { - case DrawableHitCircle circle: - easeTo(circle, cursorPos); - break; - - case DrawableSlider slider: - - if (!slider.HeadCircle.Result.HasResult) - easeTo(slider, cursorPos); - else - easeTo(slider, cursorPos - slider.Ball.DrawPosition); - - break; - } - } - } - - private void easeTo(DrawableHitObject hitObject, Vector2 destination) - { - double dampLength = Interpolation.Lerp(3000, 40, AttractionStrength.Value); - - float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); - float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); - - hitObject.Position = new Vector2(x, y); + EasementStrength.BindTo(AttractionStrength); } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs new file mode 100644 index 0000000000..35f369d9c5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Mods +{ + internal class OsuModRepel : OsuEaseHitObjectPositionsMod + { + public override string Name => "Repel"; + public override string Acronym => "RP"; + public override IconUsage? Icon => FontAwesome.Solid.ExpandArrowsAlt; + public override string Description => "Run away!"; + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); + + [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] + public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f) + { + Precision = 0.05f, + MinValue = 0.05f, + MaxValue = 1.0f, + }; + + protected override Vector2 DestinationVector => new Vector2( + 2 * WorkingHitObject.X - CursorPosition.X, + 2 * WorkingHitObject.Y - CursorPosition.Y + ); + + public OsuModRepel() + { + EasementStrength.BindTo(RepulsionStrength); + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 207e7a4ab0..09b85726ca 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu new OsuModApproachDifferent(), new OsuModMuted(), new OsuModNoScope(), - new OsuModMagnetised(), + new MultiMod(new OsuModMagnetised(), new OsuModRepel()), new ModAdaptiveSpeed() }; From 5d838628d7e49df3b3ce3e1768e6c19c68e366fe Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Fri, 27 May 2022 23:15:19 -0400 Subject: [PATCH 031/803] add test, fix formatting, expose easing function --- .../Mods/TestSceneOsuModRepel.cs | 27 +++++++++++++++++++ .../Mods/OsuEaseHitObjectPositionsMod.cs | 12 ++++----- 2 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs new file mode 100644 index 0000000000..6bd41e2fa5 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.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 NUnit.Framework; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModRepel : OsuModTestScene + { + [TestCase(0.1f)] + [TestCase(0.5f)] + [TestCase(1)] + public void TestRepel(float strength) + { + CreateModTest(new ModTestData + { + Mod = new OsuModRepel + { + RepulsionStrength = { Value = strength }, + }, + PassCondition = () => true, + Autoplay = false, + }); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs index d81d86dc65..2c344b7a68 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -23,8 +23,8 @@ namespace osu.Game.Rulesets.Osu.Mods protected BindableFloat EasementStrength = new BindableFloat(0.5f); protected Vector2 CursorPosition; protected DrawableHitObject WorkingHitObject; - protected abstract Vector2 DestinationVector { get; } - + protected virtual Vector2 DestinationVector => WorkingHitObject.Position; + private IFrameStableClock gameplayClock; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) @@ -47,22 +47,22 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - easeHitObjectPositionToVector(circle, DestinationVector); + EaseHitObjectPositionToVector(circle, DestinationVector); break; case DrawableSlider slider: if (!slider.HeadCircle.Result.HasResult) - easeHitObjectPositionToVector(slider, DestinationVector); + EaseHitObjectPositionToVector(slider, DestinationVector); else - easeHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); + EaseHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); break; } } } - private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) + protected void EaseHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) { double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value); From f7a658450fb2592a171165b57528786d32190d0d Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Wed, 1 Jun 2022 00:54:49 -0700 Subject: [PATCH 032/803] Move DrawableTaikoRulesetTestScene's hardcoded beatmap to CreateBeatmap(..) method instead of load(..) method, so that the class is more extensible and reusable --- .../DrawableTaikoRulesetTestScene.cs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs index f5e7304c12..2853aa32f4 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs @@ -21,13 +21,29 @@ namespace osu.Game.Rulesets.Taiko.Tests protected DrawableTaikoRuleset DrawableRuleset { get; private set; } protected Container PlayfieldContainer { get; private set; } + protected ControlPointInfo controlPointInfo { get; private set; } + [BackgroundDependencyLoader] private void load() { - var controlPointInfo = new ControlPointInfo(); + controlPointInfo = new ControlPointInfo(); controlPointInfo.Add(0, new TimingControlPoint()); - IWorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap + IWorkingBeatmap beatmap = CreateWorkingBeatmap(CreateBeatmap(new TaikoRuleset().RulesetInfo)); + + Add(PlayfieldContainer = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT, + Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset)) } + }); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + return new Beatmap { HitObjects = new List { new Hit { Type = HitType.Centre } }, BeatmapInfo = new BeatmapInfo @@ -39,19 +55,10 @@ namespace osu.Game.Rulesets.Taiko.Tests Title = @"Sample Beatmap", Author = { Username = @"peppy" }, }, - Ruleset = new TaikoRuleset().RulesetInfo + Ruleset = ruleset }, ControlPointInfo = controlPointInfo - }); - - Add(PlayfieldContainer = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT, - Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) } - }); + }; } } } From ae996d1404611ad99c5e66d4ee650ecdbac6a86a Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Wed, 1 Jun 2022 00:56:03 -0700 Subject: [PATCH 033/803] Add manual test scene for DrumTouchInputArea --- .../TestSceneDrumTouchInputArea.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs new file mode 100644 index 0000000000..e82594cf4f --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class TestSceneDrumTouchInputArea : DrawableTaikoRulesetTestScene + { + protected const double NUM_HIT_OBJECTS = 10; + protected const double HIT_OBJECT_TIME_SPACING_MS = 1000; + + [BackgroundDependencyLoader] + private void load() + { + var drumTouchInputArea = new DrumTouchInputArea(); + DrawableRuleset.KeyBindingInputManager.Add(drumTouchInputArea); + drumTouchInputArea.ShowTouchControls(); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + List hitObjects = new List(); + + for (var i = 0; i < NUM_HIT_OBJECTS; i++) { + hitObjects.Add(new Hit + { + StartTime = Time.Current + i * HIT_OBJECT_TIME_SPACING_MS, + IsStrong = isOdd(i), + Type = isOdd(i / 2) ? HitType.Centre : HitType.Rim + }); + } + + var beatmap = new Beatmap + { + BeatmapInfo = { Ruleset = ruleset }, + HitObjects = hitObjects + }; + + return beatmap; + } + + private bool isOdd(int number) { + return number % 2 == 0; + } + } +} From fcc05396bcb2425482741e9cfc55c4bf32a580a7 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Wed, 1 Jun 2022 00:57:24 -0700 Subject: [PATCH 034/803] Remove unused import --- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index b5bf33bc9f..e52c95e34a 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects; From f6e9dfe7bfda690cee565adbbb06cce0fb7cd039 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Wed, 1 Jun 2022 01:03:21 -0700 Subject: [PATCH 035/803] Fix naming rule violations --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 6 +++--- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 8ab9e56aec..7a74567abb 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy /// internal class LegacyInputDrum : Container { - public float centre_size = 0.5f; + public float CentreSize = 0.5f; private LegacyHalfDrum left; private LegacyHalfDrum right; private Container content; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 82b289ef14..959666ffe9 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreenPercent = 0.35f; + private const float offscreen_percent = 0.35f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.UI Padding = new MarginPadding { Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield - Bottom = -touchInputDrum.DrawHeight * offscreenPercent, // The drum should go past the bottom of the screen so that it can be wider + Bottom = -touchInputDrum.DrawHeight * offscreen_percent, // The drum should go past the bottom of the screen so that it can be wider }; } @@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Taiko.UI Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; - float centreRadius = (inputDrumRadius * touchInputDrum.centre_size); + float centreRadius = (inputDrumRadius * touchInputDrum.CentreSize); return inputPositionToDrumCentreDelta.Length <= centreRadius; } } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index a7ae763ab5..f1120e44ab 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { - public float centre_size = 0.7f; + public float CentreSize = 0.7f; private const float middle_split = 0.025f; public InputDrum() @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.UI Scale = new Vector2(0.9f), Children = new Drawable[] { - new TaikoHalfDrum(false, centre_size) + new TaikoHalfDrum(false, CentreSize) { Name = "Left Half", Anchor = Anchor.Centre, @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new TaikoHalfDrum(true, centre_size) + new TaikoHalfDrum(true, CentreSize) { Name = "Right Half", Anchor = Anchor.Centre, From f3d4cd3f9545040d4b62b22a3e32cafaac5bc6e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 14:36:07 +0900 Subject: [PATCH 036/803] Fix various code inspection issues --- .../DrawableTaikoRulesetTestScene.cs | 2 +- .../TestSceneDrumTouchInputArea.cs | 8 ++++--- .../UI/DrumSamplePlayer.cs | 11 ++++++---- .../UI/DrumTouchInputArea.cs | 22 ++++++++++--------- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 6 ++--- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- 6 files changed, 29 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs index 2853aa32f4..a099795d12 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Tests protected DrawableTaikoRuleset DrawableRuleset { get; private set; } protected Container PlayfieldContainer { get; private set; } - protected ControlPointInfo controlPointInfo { get; private set; } + private ControlPointInfo controlPointInfo { get; set; } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs index e82594cf4f..45555a55c1 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs @@ -28,7 +28,8 @@ namespace osu.Game.Rulesets.Taiko.Tests { List hitObjects = new List(); - for (var i = 0; i < NUM_HIT_OBJECTS; i++) { + for (int i = 0; i < NUM_HIT_OBJECTS; i++) + { hitObjects.Add(new Hit { StartTime = Time.Current + i * HIT_OBJECT_TIME_SPACING_MS, @@ -36,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Type = isOdd(i / 2) ? HitType.Centre : HitType.Rim }); } - + var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset }, @@ -46,7 +47,8 @@ namespace osu.Game.Rulesets.Taiko.Tests return beatmap; } - private bool isOdd(int number) { + private bool isOdd(int number) + { return number % 2 == 0; } } diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index d5215c05e9..47a9094ed2 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -12,10 +12,10 @@ namespace osu.Game.Rulesets.Taiko.UI { internal class DrumSamplePlayer : Container, IKeyBindingHandler { - private DrumSampleTriggerSource leftRimSampleTriggerSource; - private DrumSampleTriggerSource leftCentreSampleTriggerSource; - private DrumSampleTriggerSource rightCentreSampleTriggerSource; - private DrumSampleTriggerSource rightRimSampleTriggerSource; + private readonly DrumSampleTriggerSource leftRimSampleTriggerSource; + private readonly DrumSampleTriggerSource leftCentreSampleTriggerSource; + private readonly DrumSampleTriggerSource rightCentreSampleTriggerSource; + private readonly DrumSampleTriggerSource rightRimSampleTriggerSource; public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { @@ -35,12 +35,15 @@ namespace osu.Game.Rulesets.Taiko.UI case TaikoAction.LeftRim: leftRimSampleTriggerSource.Play(HitType.Rim); break; + case TaikoAction.LeftCentre: leftCentreSampleTriggerSource.Play(HitType.Centre); break; + case TaikoAction.RightCentre: rightCentreSampleTriggerSource.Play(HitType.Centre); break; + case TaikoAction.RightRim: rightRimSampleTriggerSource.Play(HitType.Rim); break; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 959666ffe9..8d1b8a4478 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -24,8 +24,9 @@ namespace osu.Game.Rulesets.Taiko.UI { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) private const float offscreen_percent = 0.35f; - private InputDrum touchInputDrum; - private Circle drumBackground; + + private readonly InputDrum touchInputDrum; + private readonly Circle drumBackground; private KeyBindingContainer keyBindingContainer; @@ -33,9 +34,9 @@ namespace osu.Game.Rulesets.Taiko.UI private TaikoAction mouseAction; // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved - private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + private readonly Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); - private Container visibleComponents; + private readonly Container visibleComponents; public DrumTouchInputArea() { @@ -43,7 +44,8 @@ namespace osu.Game.Rulesets.Taiko.UI RelativePositionAxes = Axes.Both; Children = new Drawable[] { - visibleComponents = new Container() { + visibleComponents = new Container + { Alpha = 0.0f, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, @@ -51,7 +53,8 @@ namespace osu.Game.Rulesets.Taiko.UI Origin = Anchor.BottomCentre, Children = new Drawable[] { - drumBackground = new Circle() { + drumBackground = new Circle + { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Anchor = Anchor.Centre, @@ -59,7 +62,8 @@ namespace osu.Game.Rulesets.Taiko.UI FillMode = FillMode.Fit, Alpha = 0.9f, }, - touchInputDrum = new InputDrum() { + touchInputDrum = new InputDrum + { Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -135,9 +139,7 @@ namespace osu.Game.Rulesets.Taiko.UI bool centreHit = inputIsCenterHit(inputPosition); bool leftSide = inputIsOnLeftSide(inputPosition); - return centreHit ? - (leftSide ? TaikoAction.LeftCentre : TaikoAction.RightCentre) : - (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); + return centreHit ? (leftSide ? TaikoAction.LeftCentre : TaikoAction.RightCentre) : (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); } private bool inputIsOnLeftSide(Vector2 inputPosition) diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index f1120e44ab..e984bef3ce 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - public TaikoHalfDrum(bool flipped, float centre_size) + public TaikoHalfDrum(bool flipped, float centreSize) { Masking = true; @@ -112,14 +112,14 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(centre_size) + Size = new Vector2(centreSize) }, centreHit = new Sprite { Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(centre_size), + Size = new Vector2(centreSize), Alpha = 0, Blending = BlendingParameters.Additive } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index e24f861269..b83364ddf2 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Taiko.UI Children = new Drawable[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), - new InputDrum() + new InputDrum { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, From 45adca17da95f3ff1179661a8d87e48e13145806 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 14:39:57 +0900 Subject: [PATCH 037/803] Make `DrumSamplePlayer` a `CompositeDrawable` --- osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index 47a9094ed2..b65e2af3d8 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.UI { - internal class DrumSamplePlayer : Container, IKeyBindingHandler + internal class DrumSamplePlayer : CompositeDrawable, IKeyBindingHandler { private readonly DrumSampleTriggerSource leftRimSampleTriggerSource; private readonly DrumSampleTriggerSource leftCentreSampleTriggerSource; @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.UI public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { - Children = new Drawable[] + InternalChildren = new Drawable[] { leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), leftCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), From 859a83ac900e48466921e3bf869b0d8bc86f9211 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 14:48:26 +0900 Subject: [PATCH 038/803] Remove unused field and fix typo --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 1 - osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 7a74567abb..d3eeb8e16a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -18,7 +18,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy /// internal class LegacyInputDrum : Container { - public float CentreSize = 0.5f; private LegacyHalfDrum left; private LegacyHalfDrum right; private Container content; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 8d1b8a4478..a66c52df1f 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// /// An overlay that captures and displays Taiko mouse and touch input. /// The boundaries of this overlay defines the interactable area for touch input. - /// A secondary InputDrum is attached by this overlay, which defines the circulary boundary which distinguishes "centre" from "rim" hits, and also displays input. + /// A secondary InputDrum is attached by this overlay, which defines the circular boundary which distinguishes "centre" from "rim" hits, and also displays input. /// public class DrumTouchInputArea : Container { From de224e79c79863f268367392574df539d6ddac80 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 7 Jun 2022 10:32:51 +0800 Subject: [PATCH 039/803] Limit slider rotation when the slider is too large --- .../OsuHitObjectGenerationUtils_Reposition.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index a77d1f8b0f..477ef2d55d 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -198,6 +198,27 @@ namespace osu.Game.Rulesets.Osu.Utils var slider = (Slider)workingObject.HitObject; var possibleMovementBounds = calculatePossibleMovementBounds(slider); + // The slider rotation applied in computeModifiedPosition might make it impossible to fit the slider into the playfield + // For example, a long horizontal slider will be off-screen when rotated by 90 degrees + // In this case, limit the rotation to either 0 or 180 degrees + if (possibleMovementBounds.Width < 0 || possibleMovementBounds.Height < 0) + { + float currentRotation = getSliderRotation(slider); + float diff1 = getAngleDifference(workingObject.RotationOriginal, currentRotation); + float diff2 = getAngleDifference(workingObject.RotationOriginal + MathF.PI, currentRotation); + + if (diff1 < diff2) + { + RotateSlider(slider, workingObject.RotationOriginal - getSliderRotation(slider)); + } + else + { + RotateSlider(slider, workingObject.RotationOriginal + MathF.PI - getSliderRotation(slider)); + } + + possibleMovementBounds = calculatePossibleMovementBounds(slider); + } + var previousPosition = workingObject.PositionModified; // Clamp slider position to the placement area @@ -355,6 +376,18 @@ namespace osu.Game.Rulesets.Osu.Utils return MathF.Atan2(endPositionVector.Y, endPositionVector.X); } + /// + /// Get the absolute difference between 2 angles measured in Radians. + /// + /// The first angle + /// The second angle + /// The absolute difference with interval [0, MathF.PI) + private static float getAngleDifference(float angle1, float angle2) + { + float diff = MathF.Abs(angle1 - angle2) % (MathF.PI * 2); + return MathF.Min(diff, MathF.PI * 2 - diff); + } + public class ObjectPositionInfo { /// @@ -397,6 +430,7 @@ namespace osu.Game.Rulesets.Osu.Utils private class WorkingObject { + public float RotationOriginal { get; } public Vector2 PositionOriginal { get; } public Vector2 PositionModified { get; set; } public Vector2 EndPositionModified { get; set; } @@ -407,6 +441,7 @@ namespace osu.Game.Rulesets.Osu.Utils public WorkingObject(ObjectPositionInfo positionInfo) { PositionInfo = positionInfo; + RotationOriginal = HitObject is Slider slider ? getSliderRotation(slider) : 0; PositionModified = PositionOriginal = HitObject.Position; EndPositionModified = HitObject.EndPosition; } From b7bdad407486266b5c5446fde71c7108c0adc225 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:36:44 -0400 Subject: [PATCH 040/803] clamp sliders, expose slider bounds function --- .../Mods/OsuEaseHitObjectPositionsMod.cs | 16 ++++----- .../Mods/OsuModMagnetised.cs | 7 +--- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 33 ++++++++++++++----- .../OsuHitObjectGenerationUtils_Reposition.cs | 22 ++++++++----- 4 files changed, 48 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs index 2c344b7a68..74391e53a2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -20,10 +20,10 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - protected BindableFloat EasementStrength = new BindableFloat(0.5f); + public abstract BindableFloat EasementStrength { get; } protected Vector2 CursorPosition; protected DrawableHitObject WorkingHitObject; - protected virtual Vector2 DestinationVector => WorkingHitObject.Position; + protected abstract Vector2 DestinationVector { get; } private IFrameStableClock gameplayClock; @@ -47,27 +47,27 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - EaseHitObjectPositionToVector(circle, DestinationVector); + easeHitObjectPositionToVector(circle, DestinationVector); break; case DrawableSlider slider: if (!slider.HeadCircle.Result.HasResult) - EaseHitObjectPositionToVector(slider, DestinationVector); + easeHitObjectPositionToVector(slider, DestinationVector); else - EaseHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); + easeHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); break; } } } - protected void EaseHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) + private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) { double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value); - float x = (float)Interpolation.DampContinuously(hitObject.X, Math.Clamp(destination.X, 0, OsuPlayfield.BASE_SIZE.X), dampLength, gameplayClock.ElapsedFrameTime); - float y = (float)Interpolation.DampContinuously(hitObject.Y, Math.Clamp(destination.Y, 0, OsuPlayfield.BASE_SIZE.Y), dampLength, gameplayClock.ElapsedFrameTime); + float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); + float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); hitObject.Position = new Vector2(x, y); } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index ca69085e31..1c10d61c99 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -21,16 +21,11 @@ namespace osu.Game.Rulesets.Osu.Mods protected override Vector2 DestinationVector => CursorPosition; [SettingSource("Attraction strength", "How strong the pull is.", 0)] - public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f) + public override BindableFloat EasementStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; - - public OsuModMagnetised() - { - EasementStrength.BindTo(AttractionStrength); - } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 35f369d9c5..fec66e3ef1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -6,6 +6,9 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; +using osu.Game.Rulesets.Osu.Utils; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods @@ -19,21 +22,35 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] - public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f) + public override BindableFloat EasementStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; - protected override Vector2 DestinationVector => new Vector2( - 2 * WorkingHitObject.X - CursorPosition.X, - 2 * WorkingHitObject.Y - CursorPosition.Y - ); - - public OsuModRepel() + protected override Vector2 DestinationVector { - EasementStrength.BindTo(RepulsionStrength); + get + { + float x = Math.Clamp(2 * WorkingHitObject.X - CursorPosition.X, 0, OsuPlayfield.BASE_SIZE.X); + float y = Math.Clamp(2 * WorkingHitObject.Y - CursorPosition.Y, 0, OsuPlayfield.BASE_SIZE.Y); + + if (WorkingHitObject.HitObject is Slider slider) + { + var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(slider, false); + + x = possibleMovementBounds.Width < 0 + ? x + : Math.Clamp(x, possibleMovementBounds.Left, possibleMovementBounds.Right); + + y = possibleMovementBounds.Height < 0 + ? y + : Math.Clamp(y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); + } + + return new Vector2(x, y); + } } } } diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index d1bc3b45df..765477fba2 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Utils private static Vector2 clampSliderToPlayfield(WorkingObject workingObject) { var slider = (Slider)workingObject.HitObject; - var possibleMovementBounds = calculatePossibleMovementBounds(slider); + var possibleMovementBounds = CalculatePossibleMovementBounds(slider); var previousPosition = workingObject.PositionModified; @@ -212,10 +212,13 @@ namespace osu.Game.Rulesets.Osu.Utils /// Calculates a which contains all of the possible movements of the slider (in relative X/Y coordinates) /// such that the entire slider is inside the playfield. /// + /// The for which to calculate a movement bounding box. + /// Whether the movement bounding box should account for the slider's follow circle. Defaults to true. + /// A which contains all of the possible movements of the slider such that the entire slider is inside the playfield. /// /// If the slider is larger than the playfield, the returned may have negative width/height. /// - private static RectangleF calculatePossibleMovementBounds(Slider slider) + public static RectangleF CalculatePossibleMovementBounds(Slider slider, bool accountForFollowCircleRadius = true) { var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); @@ -236,14 +239,17 @@ namespace osu.Game.Rulesets.Osu.Utils maxY = MathF.Max(maxY, pos.Y); } - // Take the circle radius into account. - float radius = (float)slider.Radius; + if (accountForFollowCircleRadius) + { + // Take the circle radius into account. + float radius = (float)slider.Radius; - minX -= radius; - minY -= radius; + minX -= radius; + minY -= radius; - maxX += radius; - maxY += radius; + maxX += radius; + maxY += radius; + } // Given the bounding box of the slider (via min/max X/Y), // the amount that the slider can move to the left is minX (with the sign flipped, since positive X is to the right), From e5171aaebf60a7ab17b31f7295a71f4278efde4b Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:48:53 -0400 Subject: [PATCH 041/803] update tests to match new bindable names --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs | 2 +- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs index 9b49e60363..29ec91cda8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModMagnetised { - AttractionStrength = { Value = strength }, + EasementStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs index 6bd41e2fa5..bdd4ed7fb9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModRepel { - RepulsionStrength = { Value = strength }, + EasementStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, From f21c9fb520190d51a9e499b57f5410ab16978564 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 7 Jun 2022 12:05:53 -0400 Subject: [PATCH 042/803] add newline --- osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs index 74391e53a2..d0f115def3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -44,6 +44,7 @@ namespace osu.Game.Rulesets.Osu.Mods foreach (var drawable in playfield.HitObjectContainer.AliveObjects) { WorkingHitObject = drawable; + switch (drawable) { case DrawableHitCircle circle: From f54a68f6cae2ff193c6ad94b23d11f918ec3197f Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 8 Jun 2022 01:00:47 -0400 Subject: [PATCH 043/803] scale down repulsion strength --- osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs | 3 ++- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs index d0f115def3..e93863fa96 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -21,6 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; public abstract BindableFloat EasementStrength { get; } + protected virtual float EasementStrengthMultiplier => 1.0f; protected Vector2 CursorPosition; protected DrawableHitObject WorkingHitObject; protected abstract Vector2 DestinationVector { get; } @@ -65,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) { - double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value); + double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value * EasementStrengthMultiplier); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index fec66e3ef1..95242808af 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -22,13 +22,15 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] - public override BindableFloat EasementStrength { get; } = new BindableFloat(0.5f) + public override BindableFloat EasementStrength { get; } = new BindableFloat(0.6f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; + protected override float EasementStrengthMultiplier => 0.8f; + protected override Vector2 DestinationVector { get From 6e883a69d9e98947053646d6f166afe0bf4a277a Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 9 Jun 2022 18:07:37 -0400 Subject: [PATCH 044/803] revert slider radius parameter addition --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 +- .../OsuHitObjectGenerationUtils_Reposition.cs | 18 +++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 95242808af..3261076084 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (WorkingHitObject.HitObject is Slider slider) { - var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(slider, false); + var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(slider); x = possibleMovementBounds.Width < 0 ? x diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 765477fba2..5e11ede91f 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -213,12 +213,11 @@ namespace osu.Game.Rulesets.Osu.Utils /// such that the entire slider is inside the playfield. /// /// The for which to calculate a movement bounding box. - /// Whether the movement bounding box should account for the slider's follow circle. Defaults to true. /// A which contains all of the possible movements of the slider such that the entire slider is inside the playfield. /// /// If the slider is larger than the playfield, the returned may have negative width/height. /// - public static RectangleF CalculatePossibleMovementBounds(Slider slider, bool accountForFollowCircleRadius = true) + public static RectangleF CalculatePossibleMovementBounds(Slider slider) { var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); @@ -239,17 +238,14 @@ namespace osu.Game.Rulesets.Osu.Utils maxY = MathF.Max(maxY, pos.Y); } - if (accountForFollowCircleRadius) - { - // Take the circle radius into account. - float radius = (float)slider.Radius; + // Take the circle radius into account. + float radius = (float)slider.Radius; - minX -= radius; - minY -= radius; + minX -= radius; + minY -= radius; - maxX += radius; - maxY += radius; - } + maxX += radius; + maxY += radius; // Given the bounding box of the slider (via min/max X/Y), // the amount that the slider can move to the left is minX (with the sign flipped, since positive X is to the right), From 4e01db03bb3a6002590dac58e04005cf9c528193 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 9 Jun 2022 18:25:04 -0400 Subject: [PATCH 045/803] don't specify icon --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 3261076084..da6686d314 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Rulesets.Osu.Utils; using osu.Game.Rulesets.Osu.Objects; @@ -17,7 +16,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Name => "Repel"; public override string Acronym => "RP"; - public override IconUsage? Icon => FontAwesome.Solid.ExpandArrowsAlt; public override string Description => "Run away!"; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); From 569c39942a5b74ae51847c64e0f230f8973ec994 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 9 Jun 2022 18:26:18 -0400 Subject: [PATCH 046/803] replace easement with easing --- .../Mods/TestSceneOsuModMagnetised.cs | 2 +- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs | 6 +++--- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs index 29ec91cda8..bab47fa851 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModMagnetised { - EasementStrength = { Value = strength }, + EasingStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs index bdd4ed7fb9..0462f60991 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModRepel { - EasementStrength = { Value = strength }, + EasingStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs index e93863fa96..0ba3a204f9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - public abstract BindableFloat EasementStrength { get; } - protected virtual float EasementStrengthMultiplier => 1.0f; + public abstract BindableFloat EasingStrength { get; } + protected virtual float EasingStrengthMultiplier => 1.0f; protected Vector2 CursorPosition; protected DrawableHitObject WorkingHitObject; protected abstract Vector2 DestinationVector { get; } @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) { - double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value * EasementStrengthMultiplier); + double dampLength = Interpolation.Lerp(3000, 40, EasingStrength.Value * EasingStrengthMultiplier); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index 1c10d61c99..b8c976cf9f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods protected override Vector2 DestinationVector => CursorPosition; [SettingSource("Attraction strength", "How strong the pull is.", 0)] - public override BindableFloat EasementStrength { get; } = new BindableFloat(0.5f) + public override BindableFloat EasingStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, MinValue = 0.05f, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index da6686d314..1622400eac 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -20,14 +20,14 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] - public override BindableFloat EasementStrength { get; } = new BindableFloat(0.6f) + public override BindableFloat EasingStrength { get; } = new BindableFloat(0.6f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; - protected override float EasementStrengthMultiplier => 0.8f; + protected override float EasingStrengthMultiplier => 0.8f; protected override Vector2 DestinationVector { From 2fe34f188f4c6d4294cd98f2f4d18a921f5949a1 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 9 Jun 2022 18:52:10 -0400 Subject: [PATCH 047/803] shamelessly copy osumodmagnetised --- .../Mods/TestSceneOsuModMagnetised.cs | 2 +- .../Mods/TestSceneOsuModRepel.cs | 2 +- .../Mods/OsuEaseHitObjectPositionsMod.cs | 77 ------------------- .../Mods/OsuModMagnetised.cs | 62 +++++++++++++-- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 74 ++++++++++++++---- 5 files changed, 118 insertions(+), 99 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs index bab47fa851..9b49e60363 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModMagnetised { - EasingStrength = { Value = strength }, + AttractionStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs index 0462f60991..6bd41e2fa5 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModRepel { - EasingStrength = { Value = strength }, + RepulsionStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs deleted file mode 100644 index 0ba3a204f9..0000000000 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ /dev/null @@ -1,77 +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.Framework.Utils; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public abstract class OsuEaseHitObjectPositionsMod : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset - { - public override ModType Type => ModType.Fun; - public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - - public abstract BindableFloat EasingStrength { get; } - protected virtual float EasingStrengthMultiplier => 1.0f; - protected Vector2 CursorPosition; - protected DrawableHitObject WorkingHitObject; - protected abstract Vector2 DestinationVector { get; } - - private IFrameStableClock gameplayClock; - - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - gameplayClock = drawableRuleset.FrameStableClock; - - // Hide judgment displays and follow points as they won't make any sense. - // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. - drawableRuleset.Playfield.DisplayJudgements.Value = false; - (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); - } - - public void Update(Playfield playfield) - { - CursorPosition = playfield.Cursor.ActiveCursor.DrawPosition; - - foreach (var drawable in playfield.HitObjectContainer.AliveObjects) - { - WorkingHitObject = drawable; - - switch (drawable) - { - case DrawableHitCircle circle: - easeHitObjectPositionToVector(circle, DestinationVector); - break; - - case DrawableSlider slider: - - if (!slider.HeadCircle.Result.HasResult) - easeHitObjectPositionToVector(slider, DestinationVector); - else - easeHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); - - break; - } - } - } - - private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) - { - double dampLength = Interpolation.Lerp(3000, 40, EasingStrength.Value * EasingStrengthMultiplier); - - float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); - float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); - - hitObject.Position = new Vector2(x, y); - } - } -} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index b8c976cf9f..97a573f1b4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -2,30 +2,82 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModMagnetised : OsuEaseHitObjectPositionsMod + internal class OsuModMagnetised : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Name => "Magnetised"; public override string Acronym => "MG"; public override IconUsage? Icon => FontAwesome.Solid.Magnet; + public override ModType Type => ModType.Fun; public override string Description => "No need to chase the circles – your cursor is a magnet!"; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModRelax), typeof(OsuModRepel) }).ToArray(); + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel) }; - protected override Vector2 DestinationVector => CursorPosition; + private IFrameStableClock gameplayClock; [SettingSource("Attraction strength", "How strong the pull is.", 0)] - public override BindableFloat EasingStrength { get; } = new BindableFloat(0.5f) + public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + gameplayClock = drawableRuleset.FrameStableClock; + + // Hide judgment displays and follow points as they won't make any sense. + // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. + drawableRuleset.Playfield.DisplayJudgements.Value = false; + (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); + } + + public void Update(Playfield playfield) + { + var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; + + foreach (var drawable in playfield.HitObjectContainer.AliveObjects) + { + switch (drawable) + { + case DrawableHitCircle circle: + easeTo(circle, cursorPos); + break; + + case DrawableSlider slider: + + if (!slider.HeadCircle.Result.HasResult) + easeTo(slider, cursorPos); + else + easeTo(slider, cursorPos - slider.Ball.DrawPosition); + + break; + } + } + } + + private void easeTo(DrawableHitObject hitObject, Vector2 destination) + { + double dampLength = Interpolation.Lerp(3000, 40, AttractionStrength.Value); + + float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); + float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); + + hitObject.Position = new Vector2(x, y); + } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 1622400eac..807be997db 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -2,43 +2,61 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Utils; using osu.Game.Configuration; -using osu.Game.Rulesets.Osu.Utils; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Osu.Utils; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModRepel : OsuEaseHitObjectPositionsMod + internal class OsuModRepel : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Name => "Repel"; public override string Acronym => "RP"; + public override ModType Type => ModType.Fun; public override string Description => "Run away!"; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised) }; + + private IFrameStableClock gameplayClock; [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] - public override BindableFloat EasingStrength { get; } = new BindableFloat(0.6f) + public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.6f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; - protected override float EasingStrengthMultiplier => 0.8f; - - protected override Vector2 DestinationVector + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - get - { - float x = Math.Clamp(2 * WorkingHitObject.X - CursorPosition.X, 0, OsuPlayfield.BASE_SIZE.X); - float y = Math.Clamp(2 * WorkingHitObject.Y - CursorPosition.Y, 0, OsuPlayfield.BASE_SIZE.Y); + gameplayClock = drawableRuleset.FrameStableClock; - if (WorkingHitObject.HitObject is Slider slider) + // Hide judgment displays and follow points as they won't make any sense. + // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. + drawableRuleset.Playfield.DisplayJudgements.Value = false; + (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); + } + + public void Update(Playfield playfield) + { + var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; + + foreach (var drawable in playfield.HitObjectContainer.AliveObjects) + { + float x = Math.Clamp(2 * drawable.X - cursorPos.X, 0, OsuPlayfield.BASE_SIZE.X); + float y = Math.Clamp(2 * drawable.Y - cursorPos.Y, 0, OsuPlayfield.BASE_SIZE.Y); + + if (drawable.HitObject is Slider thisSlider) { - var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(slider); + var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(thisSlider); x = possibleMovementBounds.Width < 0 ? x @@ -49,8 +67,34 @@ namespace osu.Game.Rulesets.Osu.Mods : Math.Clamp(y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); } - return new Vector2(x, y); + var destination = new Vector2(x, y); + + switch (drawable) + { + case DrawableHitCircle circle: + easeTo(circle, destination); + break; + + case DrawableSlider slider: + + if (!slider.HeadCircle.Result.HasResult) + easeTo(slider, destination); + else + easeTo(slider, destination - slider.Ball.DrawPosition); + + break; + } } } + + private void easeTo(DrawableHitObject hitObject, Vector2 destination) + { + double dampLength = Interpolation.Lerp(3000, 40, 0.8 * RepulsionStrength.Value); + + float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); + float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); + + hitObject.Position = new Vector2(x, y); + } } } From 7e7716f942a5ef2df2e49c8783c9f1fa829614a3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Jun 2022 15:40:11 +0900 Subject: [PATCH 048/803] Support undo/redo for control points --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 59 ++++++----- .../Edit/LegacyEditorBeatmapPatcher.cs | 100 ++++++++++++------ 3 files changed, 105 insertions(+), 56 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index ff13e61360..25551d1ef6 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -143,7 +143,7 @@ namespace osu.Game.Beatmaps.Formats protected string CleanFilename(string path) => path.Trim('"').ToStandardisedPath(); - protected enum Section + public enum Section { General, Editor, diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index c9449f3259..577a8291ad 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -71,31 +71,7 @@ namespace osu.Game.Screens.Edit public EditorBeatmap(IBeatmap playableBeatmap, ISkin beatmapSkin = null, BeatmapInfo beatmapInfo = null) { PlayableBeatmap = playableBeatmap; - - // ensure we are not working with legacy control points. - // if we leave the legacy points around they will be applied over any local changes on - // ApplyDefaults calls. this should eventually be removed once the default logic is moved to the decoder/converter. - if (PlayableBeatmap.ControlPointInfo is LegacyControlPointInfo) - { - var newControlPoints = new ControlPointInfo(); - - foreach (var controlPoint in PlayableBeatmap.ControlPointInfo.AllControlPoints) - { - switch (controlPoint) - { - case DifficultyControlPoint _: - case SampleControlPoint _: - // skip legacy types. - continue; - - default: - newControlPoints.Add(controlPoint.Time, controlPoint); - break; - } - } - - playableBeatmap.ControlPointInfo = newControlPoints; - } + PlayableBeatmap.ControlPointInfo = ConvertControlPoints(PlayableBeatmap.ControlPointInfo); this.beatmapInfo = beatmapInfo ?? playableBeatmap.BeatmapInfo; @@ -108,6 +84,39 @@ namespace osu.Game.Screens.Edit trackStartTime(obj); } + /// + /// Converts a such that the resultant is non-legacy. + /// + /// The to convert. + /// The non-legacy . is returned if already non-legacy. + public static ControlPointInfo ConvertControlPoints(ControlPointInfo incoming) + { + // ensure we are not working with legacy control points. + // if we leave the legacy points around they will be applied over any local changes on + // ApplyDefaults calls. this should eventually be removed once the default logic is moved to the decoder/converter. + if (!(incoming is LegacyControlPointInfo)) + return incoming; + + var newControlPoints = new ControlPointInfo(); + + foreach (var controlPoint in incoming.AllControlPoints) + { + switch (controlPoint) + { + case DifficultyControlPoint _: + case SampleControlPoint _: + // skip legacy types. + continue; + + default: + newControlPoints.Add(controlPoint.Time, controlPoint); + break; + } + } + + return newControlPoints; + } + public BeatmapInfo BeatmapInfo { get => beatmapInfo; diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index 3ed2a7efe2..ba9a7ed1a3 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -7,9 +7,11 @@ using System.Diagnostics; using System.IO; using System.Text; using DiffPlex; +using DiffPlex.Model; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Skinning; using Decoder = osu.Game.Beatmaps.Formats.Decoder; @@ -32,61 +34,99 @@ namespace osu.Game.Screens.Edit { // Diff the beatmaps var result = new Differ().CreateLineDiffs(readString(currentState), readString(newState), true, false); + IBeatmap newBeatmap = null; + + editorBeatmap.BeginChange(); + processHitObjects(result, () => newBeatmap ??= readBeatmap(newState)); + processTimingPoints(result, () => newBeatmap ??= readBeatmap(newState)); + editorBeatmap.EndChange(); + } + + private void processTimingPoints(DiffResult result, Func getNewBeatmap) + { + findChangedIndices(result, LegacyDecoder.Section.TimingPoints, out var removedIndices, out var addedIndices); + + if (removedIndices.Count == 0 && addedIndices.Count == 0) + return; + + // Due to conversion from legacy to non-legacy control points, it becomes difficult to diff control points correctly. + // So instead _all_ control points are reloaded if _any_ control point is changed. + + var newControlPoints = EditorBeatmap.ConvertControlPoints(getNewBeatmap().ControlPointInfo); + + editorBeatmap.ControlPointInfo.Clear(); + foreach (var point in newControlPoints.AllControlPoints) + editorBeatmap.ControlPointInfo.Add(point.Time, point); + } + + private void processHitObjects(DiffResult result, Func getNewBeatmap) + { + findChangedIndices(result, LegacyDecoder.Section.HitObjects, out var removedIndices, out var addedIndices); + + foreach (int removed in removedIndices) + editorBeatmap.RemoveAt(removed); + + if (addedIndices.Count > 0) + { + var newBeatmap = getNewBeatmap(); + + foreach (int i in addedIndices) + editorBeatmap.Insert(i, newBeatmap.HitObjects[i]); + } + } + + private void findChangedIndices(DiffResult result, LegacyDecoder.Section section, out List removedIndices, out List addedIndices) + { + removedIndices = new List(); + addedIndices = new List(); // Find the index of [HitObject] sections. Lines changed prior to this index are ignored. - int oldHitObjectsIndex = Array.IndexOf(result.PiecesOld, "[HitObjects]"); - int newHitObjectsIndex = Array.IndexOf(result.PiecesNew, "[HitObjects]"); + int oldSectionStartIndex = Array.IndexOf(result.PiecesOld, $"[{section}]"); + int oldSectionEndIndex = Array.FindIndex(result.PiecesOld, oldSectionStartIndex + 1, s => s.StartsWith(@"[", StringComparison.Ordinal)); - Debug.Assert(oldHitObjectsIndex >= 0); - Debug.Assert(newHitObjectsIndex >= 0); + if (oldSectionEndIndex == -1) + oldSectionEndIndex = result.PiecesOld.Length; - var toRemove = new List(); - var toAdd = new List(); + int newSectionStartIndex = Array.IndexOf(result.PiecesNew, $"[{section}]"); + int newSectionEndIndex = Array.FindIndex(result.PiecesNew, newSectionStartIndex + 1, s => s.StartsWith(@"[", StringComparison.Ordinal)); + + if (newSectionEndIndex == -1) + newSectionEndIndex = result.PiecesOld.Length; + + Debug.Assert(oldSectionStartIndex >= 0); + Debug.Assert(newSectionStartIndex >= 0); foreach (var block in result.DiffBlocks) { - // Removed hitobjects + // Removed indices for (int i = 0; i < block.DeleteCountA; i++) { - int hoIndex = block.DeleteStartA + i - oldHitObjectsIndex - 1; + int objectIndex = block.DeleteStartA + i; - if (hoIndex < 0) + if (objectIndex <= oldSectionStartIndex || objectIndex >= oldSectionEndIndex) continue; - toRemove.Add(hoIndex); + removedIndices.Add(objectIndex - oldSectionStartIndex - 1); } - // Added hitobjects + // Added indices for (int i = 0; i < block.InsertCountB; i++) { - int hoIndex = block.InsertStartB + i - newHitObjectsIndex - 1; + int objectIndex = block.InsertStartB + i; - if (hoIndex < 0) + if (objectIndex <= newSectionStartIndex || objectIndex >= newSectionEndIndex) continue; - toAdd.Add(hoIndex); + addedIndices.Add(objectIndex - newSectionStartIndex - 1); } } // Sort the indices to ensure that removal + insertion indices don't get jumbled up post-removal or post-insertion. // This isn't strictly required, but the differ makes no guarantees about order. - toRemove.Sort(); - toAdd.Sort(); + removedIndices.Sort(); + addedIndices.Sort(); - editorBeatmap.BeginChange(); - - // Apply the changes. - for (int i = toRemove.Count - 1; i >= 0; i--) - editorBeatmap.RemoveAt(toRemove[i]); - - if (toAdd.Count > 0) - { - IBeatmap newBeatmap = readBeatmap(newState); - foreach (int i in toAdd) - editorBeatmap.Insert(i, newBeatmap.HitObjects[i]); - } - - editorBeatmap.EndChange(); + removedIndices.Reverse(); } private string readString(byte[] state) => Encoding.UTF8.GetString(state); From 776e7c0c718e531874247c5c5dca8c0294cd2a04 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Jun 2022 15:40:18 +0900 Subject: [PATCH 049/803] Work around performance issues --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index f498aa917e..015e7f5bc1 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -137,8 +137,12 @@ namespace osu.Game.Screens.Edit.Timing controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((sender, args) => { - table.ControlGroups = controlPointGroups; - changeHandler?.SaveState(); + // This AddOnce() works around performance issues from the LegacyEditorBeatmapPatcher re-initialising all control points every undo & redo. + Scheduler.AddOnce(() => + { + table.ControlGroups = controlPointGroups; + changeHandler?.SaveState(); + }); }, true); } From 5a18547342d1c7a242052fb28ecc72159e128f0d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Jun 2022 15:58:11 +0900 Subject: [PATCH 050/803] Compare by `char` Co-authored-by: Berkan Diler --- osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index ba9a7ed1a3..c668778be2 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -82,13 +82,13 @@ namespace osu.Game.Screens.Edit // Find the index of [HitObject] sections. Lines changed prior to this index are ignored. int oldSectionStartIndex = Array.IndexOf(result.PiecesOld, $"[{section}]"); - int oldSectionEndIndex = Array.FindIndex(result.PiecesOld, oldSectionStartIndex + 1, s => s.StartsWith(@"[", StringComparison.Ordinal)); + int oldSectionEndIndex = Array.FindIndex(result.PiecesOld, oldSectionStartIndex + 1, s => s.StartsWith('[')); if (oldSectionEndIndex == -1) oldSectionEndIndex = result.PiecesOld.Length; int newSectionStartIndex = Array.IndexOf(result.PiecesNew, $"[{section}]"); - int newSectionEndIndex = Array.FindIndex(result.PiecesNew, newSectionStartIndex + 1, s => s.StartsWith(@"[", StringComparison.Ordinal)); + int newSectionEndIndex = Array.FindIndex(result.PiecesNew, newSectionStartIndex + 1, s => s.StartsWith('[')); if (newSectionEndIndex == -1) newSectionEndIndex = result.PiecesOld.Length; From 285e5abb417e43fb8b95054edeba2d2a05d6e90d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Jun 2022 16:55:46 +0900 Subject: [PATCH 051/803] Fix incorrect fallback value --- osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index c668778be2..939d319405 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Edit int newSectionEndIndex = Array.FindIndex(result.PiecesNew, newSectionStartIndex + 1, s => s.StartsWith('[')); if (newSectionEndIndex == -1) - newSectionEndIndex = result.PiecesOld.Length; + newSectionEndIndex = result.PiecesNew.Length; Debug.Assert(oldSectionStartIndex >= 0); Debug.Assert(newSectionStartIndex >= 0); From a8286bdf0486b5c18e480afd2facf77c24714b58 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Jun 2022 16:56:08 +0900 Subject: [PATCH 052/803] Fix assertion failures --- osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index 939d319405..b56d990d75 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -82,14 +82,18 @@ namespace osu.Game.Screens.Edit // Find the index of [HitObject] sections. Lines changed prior to this index are ignored. int oldSectionStartIndex = Array.IndexOf(result.PiecesOld, $"[{section}]"); - int oldSectionEndIndex = Array.FindIndex(result.PiecesOld, oldSectionStartIndex + 1, s => s.StartsWith('[')); + if (oldSectionStartIndex == -1) + return; + int oldSectionEndIndex = Array.FindIndex(result.PiecesOld, oldSectionStartIndex + 1, s => s.StartsWith('[')); if (oldSectionEndIndex == -1) oldSectionEndIndex = result.PiecesOld.Length; int newSectionStartIndex = Array.IndexOf(result.PiecesNew, $"[{section}]"); - int newSectionEndIndex = Array.FindIndex(result.PiecesNew, newSectionStartIndex + 1, s => s.StartsWith('[')); + if (newSectionStartIndex == -1) + return; + int newSectionEndIndex = Array.FindIndex(result.PiecesNew, newSectionStartIndex + 1, s => s.StartsWith('[')); if (newSectionEndIndex == -1) newSectionEndIndex = result.PiecesNew.Length; From c178e5d5923cdbcc6ace0adad7b475c3865f1a66 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Jun 2022 16:58:43 +0900 Subject: [PATCH 053/803] Add explanatory comment --- osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index b56d990d75..66ece8fcfa 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -130,6 +130,8 @@ namespace osu.Game.Screens.Edit removedIndices.Sort(); addedIndices.Sort(); + // The expected usage of this returned list is to iterate from the start to the end of the list, such that + // these indices need to appear in reverse order for the usage to not have to deal with decrementing indices. removedIndices.Reverse(); } From f680f4d26bce5cc1d3fbc07946a8256d200b6aab Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Jun 2022 17:36:32 +0900 Subject: [PATCH 054/803] Apply refactorings from review --- .../Screens/Edit/LegacyEditorBeatmapPatcher.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index 66ece8fcfa..00cb95dd79 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Text; using DiffPlex; @@ -63,8 +62,8 @@ namespace osu.Game.Screens.Edit { findChangedIndices(result, LegacyDecoder.Section.HitObjects, out var removedIndices, out var addedIndices); - foreach (int removed in removedIndices) - editorBeatmap.RemoveAt(removed); + for (int i = removedIndices.Count - 1; i >= 0; i--) + editorBeatmap.RemoveAt(removedIndices[i]); if (addedIndices.Count > 0) { @@ -80,7 +79,7 @@ namespace osu.Game.Screens.Edit removedIndices = new List(); addedIndices = new List(); - // Find the index of [HitObject] sections. Lines changed prior to this index are ignored. + // Find the start and end indices of the relevant section headers in both the old and the new beatmap file. Lines changed outside of the modified ranges are ignored. int oldSectionStartIndex = Array.IndexOf(result.PiecesOld, $"[{section}]"); if (oldSectionStartIndex == -1) return; @@ -97,9 +96,6 @@ namespace osu.Game.Screens.Edit if (newSectionEndIndex == -1) newSectionEndIndex = result.PiecesNew.Length; - Debug.Assert(oldSectionStartIndex >= 0); - Debug.Assert(newSectionStartIndex >= 0); - foreach (var block in result.DiffBlocks) { // Removed indices @@ -129,10 +125,6 @@ namespace osu.Game.Screens.Edit // This isn't strictly required, but the differ makes no guarantees about order. removedIndices.Sort(); addedIndices.Sort(); - - // The expected usage of this returned list is to iterate from the start to the end of the list, such that - // these indices need to appear in reverse order for the usage to not have to deal with decrementing indices. - removedIndices.Reverse(); } private string readString(byte[] state) => Encoding.UTF8.GetString(state); From 737197591d812d0abdbf733d05bd4e411b050a9a Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Mon, 13 Jun 2022 17:49:56 +0100 Subject: [PATCH 055/803] Change doubletap algorithm --- .../Difficulty/Evaluators/SpeedEvaluator.cs | 18 ++++++++++++------ .../Difficulty/Skills/Speed.cs | 3 ++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs index 85203dd157..e68e7a8653 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Utils; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; @@ -31,14 +30,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // derive strainTime for calculation var osuCurrObj = (OsuDifficultyHitObject)current; var osuPrevObj = current.Index > 0 ? (OsuDifficultyHitObject)current.Previous(0) : null; + var osuNextObj = (OsuDifficultyHitObject)current.Next(0); double strainTime = osuCurrObj.StrainTime; double greatWindowFull = greatWindow * 2; - double speedWindowRatio = strainTime / greatWindowFull; + double doubletapness = 1; - // Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between) - if (osuPrevObj != null && strainTime < greatWindowFull && osuPrevObj.StrainTime > strainTime) - strainTime = Interpolation.Lerp(osuPrevObj.StrainTime, strainTime, speedWindowRatio); + // Nerf doubletappable doubles. + if (osuNextObj != null) + { + double currDeltaTime = Math.Max(1, osuCurrObj.DeltaTime); + double nextDeltaTime = Math.Max(1, osuNextObj.DeltaTime); + double speedRatio = Math.Min(1, currDeltaTime / nextDeltaTime); + double windowRatio = Math.Min(1, currDeltaTime / greatWindowFull); + doubletapness = Math.Pow(speedRatio, 1 - windowRatio); + } // Cap deltatime to the OD 300 hitwindow. // 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap. @@ -53,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double travelDistance = osuPrevObj?.TravelDistance ?? 0; double distance = Math.Min(single_spacing_threshold, travelDistance + osuCurrObj.MinimumJumpDistance); - return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime; + return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) * doubletapness / strainTime; } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 055e1cb66e..3e4e55262c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -5,6 +5,7 @@ using System; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Evaluators; +using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -35,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double StrainValueAt(DifficultyHitObject current) { - currentStrain *= strainDecay(current.DeltaTime); + currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime); currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current, greatWindow) * skillMultiplier; currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current, greatWindow); From 2634e569440d8ada58be6995f07929d5d42a1ae9 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 14 Jun 2022 16:16:12 +0100 Subject: [PATCH 056/803] Further adjustments --- .../Difficulty/Evaluators/SpeedEvaluator.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs index e68e7a8653..3be4d621eb 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs @@ -41,8 +41,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { double currDeltaTime = Math.Max(1, osuCurrObj.DeltaTime); double nextDeltaTime = Math.Max(1, osuNextObj.DeltaTime); - double speedRatio = Math.Min(1, currDeltaTime / nextDeltaTime); - double windowRatio = Math.Min(1, currDeltaTime / greatWindowFull); + double deltaDifference = Math.Abs(nextDeltaTime - currDeltaTime); + double speedRatio = Math.Min(1, currDeltaTime / deltaDifference); + double windowRatio = Math.Pow(Math.Min(1, currDeltaTime / greatWindowFull), 2); doubletapness = Math.Pow(speedRatio, 1 - windowRatio); } From 1f78a4fadd36ebfd1145ea162d6b47826cec664c Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 17 Jun 2022 16:56:09 +0900 Subject: [PATCH 057/803] New audio feedback for metronome --- .../Screens/Edit/Timing/MetronomeDisplay.cs | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 2ecd66a05f..33325951e5 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -33,7 +33,9 @@ namespace osu.Game.Screens.Edit.Timing private IAdjustableClock metronomeClock; - private Sample clunk; + private Sample tick; + private Sample tickDownbeat; + private Sample latch; [Resolved] private OverlayColourProvider overlayColourProvider { get; set; } @@ -43,7 +45,9 @@ namespace osu.Game.Screens.Edit.Timing [BackgroundDependencyLoader] private void load(AudioManager audio) { - clunk = audio.Samples.Get(@"Multiplayer/countdown-tick"); + tick = audio.Samples.Get(@"UI/metronome-tick"); + tickDownbeat = audio.Samples.Get(@"UI/metronome-tick-downbeat"); + latch = audio.Samples.Get(@"UI/metronome-latch"); const float taper = 25; const float swing_vertical_offset = -23; @@ -248,14 +252,27 @@ namespace osu.Game.Screens.Edit.Timing if (BeatSyncSource.Clock?.IsRunning != true && isSwinging) { swing.ClearTransforms(true); + stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); + isSwinging = false; + + // instantly latch if pendulum arm is close enough to center (to prevent awkward delayed playback of latch sound) + if (Precision.AlmostEquals(swing.Rotation, 0, 1)) + { + swing.RotateTo(0); + latch?.Play(); + return; + } using (swing.BeginDelayedSequence(350)) { swing.RotateTo(0, 1000, Easing.OutQuint); - stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); + swing.Delay(250).Schedule(() => + { + // prevent playing latch sound if metronome has started back up again + if (!isSwinging) + latch?.Play(); + }); } - - isSwinging = false; } } @@ -286,13 +303,13 @@ namespace osu.Game.Screens.Edit.Timing if (!EnableClicking) return; - var channel = clunk?.GetChannel(); + var channel = beatIndex % timingPoint.TimeSignature.Numerator == 0 ? tickDownbeat?.GetChannel() : tick?.GetChannel(); - if (channel != null) - { - channel.Frequency.Value = RNG.NextDouble(0.98f, 1.02f); - channel.Play(); - } + if (channel == null) + return; + + channel.Frequency.Value = RNG.NextDouble(0.98f, 1.02f); + channel.Play(); }); } } From 48ffd6aeb5c131a120a9c82e889348ebf3d21dfb Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Fri, 17 Jun 2022 11:28:58 -0400 Subject: [PATCH 058/803] stop component transform when skineditor is hidden --- .../Skinning/Editor/SkinSelectionHandler.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 2e2122f7c2..2496d7c657 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -11,6 +11,7 @@ using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; @@ -25,8 +26,17 @@ namespace osu.Game.Skinning.Editor [Resolved] private SkinEditor skinEditor { get; set; } + /// + /// Whether the 's state allows selected blueprints to undergo transformations. + /// Used to prevent operations from being performed on unloaded blueprints. + /// + private bool allowHandling => skinEditor.State.Value == Visibility.Visible; + public override bool HandleRotation(float angle) { + if (!allowHandling) + return false; + if (SelectedBlueprints.Count == 1) { // for single items, rotate around the origin rather than the selection centre. @@ -53,6 +63,9 @@ namespace osu.Game.Skinning.Editor public override bool HandleScale(Vector2 scale, Anchor anchor) { + if (!allowHandling) + return false; + // convert scale to screen space scale = ToScreenSpace(scale) - ToScreenSpace(Vector2.Zero); @@ -130,6 +143,9 @@ namespace osu.Game.Skinning.Editor public override bool HandleFlip(Direction direction, bool flipOverOrigin) { + if (!allowHandling) + return false; + var selectionQuad = getSelectionQuad(); Vector2 scaleFactor = direction == Direction.Horizontal ? new Vector2(-1, 1) : new Vector2(1, -1); @@ -150,6 +166,9 @@ namespace osu.Game.Skinning.Editor public override bool HandleMovement(MoveSelectionEvent moveEvent) { + if (!allowHandling) + return false; + foreach (var c in SelectedBlueprints) { var item = c.Item; From 9fe763613868c01e6570bb425615446d7aeceb59 Mon Sep 17 00:00:00 2001 From: 63411 <62799417+molneya@users.noreply.github.com> Date: Fri, 17 Jun 2022 23:38:22 +0800 Subject: [PATCH 059/803] Use accuracy for pp calculation --- .../Difficulty/ManiaPerformanceCalculator.cs | 58 +++---------------- 1 file changed, 9 insertions(+), 49 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index eb58eb7f21..02c118ae1f 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -15,15 +15,14 @@ namespace osu.Game.Rulesets.Mania.Difficulty { public class ManiaPerformanceCalculator : PerformanceCalculator { - // Score after being scaled by non-difficulty-increasing mods - private double scaledScore; - + private int countPerfect; private int countGreat; private int countGood; private int countOk; private int countMeh; private int countMiss; + private double scoreAccuracy; public ManiaPerformanceCalculator() : base(new ManiaRuleset()) @@ -34,23 +33,17 @@ namespace osu.Game.Rulesets.Mania.Difficulty { var maniaAttributes = (ManiaDifficultyAttributes)attributes; - scaledScore = score.TotalScore; countPerfect = score.Statistics.GetValueOrDefault(HitResult.Perfect); countGreat = score.Statistics.GetValueOrDefault(HitResult.Great); countGood = score.Statistics.GetValueOrDefault(HitResult.Good); countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - - if (maniaAttributes.ScoreMultiplier > 0) - { - // Scale score up, so it's comparable to other keymods - scaledScore *= 1.0 / maniaAttributes.ScoreMultiplier; - } + scoreAccuracy = customAccuracy; // Arbitrary initial value for scaling pp in order to standardize distributions across game modes. // The specific number has no intrinsic meaning and can be adjusted as needed. - double multiplier = 0.8; + double multiplier = 8.0; if (score.Mods.Any(m => m is ModNoFail)) multiplier *= 0.9; @@ -58,58 +51,25 @@ namespace osu.Game.Rulesets.Mania.Difficulty multiplier *= 0.5; double difficultyValue = computeDifficultyValue(maniaAttributes); - double accValue = computeAccuracyValue(difficultyValue, maniaAttributes); - double totalValue = - Math.Pow( - Math.Pow(difficultyValue, 1.1) + - Math.Pow(accValue, 1.1), 1.0 / 1.1 - ) * multiplier; + double totalValue = difficultyValue * multiplier; return new ManiaPerformanceAttributes { Difficulty = difficultyValue, - Accuracy = accValue, - ScaledScore = scaledScore, Total = totalValue }; } private double computeDifficultyValue(ManiaDifficultyAttributes attributes) { - double difficultyValue = Math.Pow(5 * Math.Max(1, attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0; - - difficultyValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0); - - if (scaledScore <= 500000) - difficultyValue = 0; - else if (scaledScore <= 600000) - difficultyValue *= (scaledScore - 500000) / 100000 * 0.3; - else if (scaledScore <= 700000) - difficultyValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.25; - else if (scaledScore <= 800000) - difficultyValue *= 0.55 + (scaledScore - 700000) / 100000 * 0.20; - else if (scaledScore <= 900000) - difficultyValue *= 0.75 + (scaledScore - 800000) / 100000 * 0.15; - else - difficultyValue *= 0.90 + (scaledScore - 900000) / 100000 * 0.1; + double difficultyValue = Math.Pow(Math.Max(attributes.StarRating - 0.15, 0.05), 2.2) // Star rating to pp curve + * Math.Max(0, 5 * scoreAccuracy - 4) // Accuracy curve + * (1 + 0.1 * Math.Min(1, totalHits / 1500)); // Length bonus return difficultyValue; } - private double computeAccuracyValue(double difficultyValue, ManiaDifficultyAttributes attributes) - { - if (attributes.GreatHitWindow <= 0) - return 0; - - // Lots of arbitrary values from testing. - // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution - double accuracyValue = Math.Max(0.0, 0.2 - (attributes.GreatHitWindow - 34) * 0.006667) - * difficultyValue - * Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1); - - return accuracyValue; - } - private double totalHits => countPerfect + countOk + countGreat + countGood + countMeh + countMiss; + private double customAccuracy => (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320); } } From e238bcc6c635cb7efd04d790cb92cb540002e9da Mon Sep 17 00:00:00 2001 From: 63411 <62799417+molneya@users.noreply.github.com> Date: Fri, 17 Jun 2022 23:44:10 +0800 Subject: [PATCH 060/803] Remove unneeded attributes --- .../Difficulty/ManiaDifficultyAttributes.cs | 8 ------ .../Difficulty/ManiaDifficultyCalculator.cs | 28 ------------------- .../Difficulty/ManiaPerformanceAttributes.cs | 7 ----- 3 files changed, 43 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index 8a5161be79..ffef8a50ad 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -20,12 +20,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty [JsonProperty("great_hit_window")] public double GreatHitWindow { get; set; } - /// - /// The score multiplier applied via score-reducing mods. - /// - [JsonProperty("score_multiplier")] - public double ScoreMultiplier { get; set; } - public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes() { foreach (var v in base.ToDatabaseAttributes()) @@ -34,7 +28,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty yield return (ATTRIB_ID_MAX_COMBO, MaxCombo); yield return (ATTRIB_ID_DIFFICULTY, StarRating); yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow); - yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier); } public override void FromDatabaseAttributes(IReadOnlyDictionary values) @@ -44,7 +37,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO]; StarRating = values[ATTRIB_ID_DIFFICULTY]; GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW]; - ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER]; } } } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 8002410f70..178094476f 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -53,7 +53,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty // In osu-stable mania, rate-adjustment mods don't affect the hit window. // This is done the way it is to introduce fractional differences in order to match osu-stable for the time being. GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate), - ScoreMultiplier = getScoreMultiplier(mods), MaxCombo = beatmap.HitObjects.Sum(maxComboForObject) }; } @@ -147,32 +146,5 @@ namespace osu.Game.Rulesets.Mania.Difficulty return value; } } - - private double getScoreMultiplier(Mod[] mods) - { - double scoreMultiplier = 1; - - foreach (var m in mods) - { - switch (m) - { - case ManiaModNoFail _: - case ManiaModEasy _: - case ManiaModHalfTime _: - scoreMultiplier *= 0.5; - break; - } - } - - var maniaBeatmap = (ManiaBeatmap)Beatmap; - int diff = maniaBeatmap.TotalColumns - maniaBeatmap.OriginalTotalColumns; - - if (diff > 0) - scoreMultiplier *= 0.9; - else if (diff < 0) - scoreMultiplier *= 0.9 + 0.04 * diff; - - return scoreMultiplier; - } } } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs index f5abb465c4..01474e6e00 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs @@ -14,19 +14,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty [JsonProperty("difficulty")] public double Difficulty { get; set; } - [JsonProperty("accuracy")] - public double Accuracy { get; set; } - - [JsonProperty("scaled_score")] - public double ScaledScore { get; set; } - public override IEnumerable GetAttributesForDisplay() { foreach (var attribute in base.GetAttributesForDisplay()) yield return attribute; yield return new PerformanceDisplayAttribute(nameof(Difficulty), "Difficulty", Difficulty); - yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy); } } } From 451d4154f02b80f89c14db5f3f101a1beee3a52e Mon Sep 17 00:00:00 2001 From: 63411 <62799417+molneya@users.noreply.github.com> Date: Sat, 18 Jun 2022 00:45:34 +0800 Subject: [PATCH 061/803] Fix code style --- osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 02c118ae1f..23a95fe866 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty { public class ManiaPerformanceCalculator : PerformanceCalculator { - private int countPerfect; private int countGreat; private int countGood; From c4d69405bfd5e38d348cabe89112489b14d51c5b Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Fri, 17 Jun 2022 21:18:16 +0100 Subject: [PATCH 062/803] Adjust speed ratio fraction to avoid division by 0 --- osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs index 3be4d621eb..7782aa90f3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double currDeltaTime = Math.Max(1, osuCurrObj.DeltaTime); double nextDeltaTime = Math.Max(1, osuNextObj.DeltaTime); double deltaDifference = Math.Abs(nextDeltaTime - currDeltaTime); - double speedRatio = Math.Min(1, currDeltaTime / deltaDifference); + double speedRatio = currDeltaTime / Math.Max(currDeltaTime, deltaDifference); double windowRatio = Math.Pow(Math.Min(1, currDeltaTime / greatWindowFull), 2); doubletapness = Math.Pow(speedRatio, 1 - windowRatio); } From 204348f32777fda80630530017cdb1dc9e4935fe Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 18 Jun 2022 04:39:53 +0300 Subject: [PATCH 063/803] Fix metronome playing mistimed beat sounds on editor clock resume --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 181f1ecb47..670b7147b3 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -47,6 +47,11 @@ namespace osu.Game.Screens.Edit.Timing public bool EnableClicking { get; set; } = true; + public MetronomeDisplay() + { + AllowMistimedEventFiring = false; + } + [BackgroundDependencyLoader] private void load(AudioManager audio) { From 8ad96fd94baaf489233f88cad238f6044953a452 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 18 Jun 2022 10:55:24 +0900 Subject: [PATCH 064/803] 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 8fcd7ef8c0..c82365d750 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 e9bf000109..d820be4bdc 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 dd344b0cd0..a8c59d676d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From 361a87412bbfc9382e22853a2a659a6450dba677 Mon Sep 17 00:00:00 2001 From: 63411 <62799417+molneya@users.noreply.github.com> Date: Sat, 18 Jun 2022 12:20:47 +0800 Subject: [PATCH 065/803] Update comments --- .../Difficulty/ManiaPerformanceCalculator.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 23a95fe866..ed2654ceda 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -62,13 +62,17 @@ namespace osu.Game.Rulesets.Mania.Difficulty private double computeDifficultyValue(ManiaDifficultyAttributes attributes) { double difficultyValue = Math.Pow(Math.Max(attributes.StarRating - 0.15, 0.05), 2.2) // Star rating to pp curve - * Math.Max(0, 5 * scoreAccuracy - 4) // Accuracy curve - * (1 + 0.1 * Math.Min(1, totalHits / 1500)); // Length bonus + * Math.Max(0, 5 * scoreAccuracy - 4) // From 80% accuracy, 1/20th of total pp is awarded per additional 1% accuracy + * (1 + 0.1 * Math.Min(1, totalHits / 1500)); // Length bonus, capped at 1500 notes return difficultyValue; } private double totalHits => countPerfect + countOk + countGreat + countGood + countMeh + countMiss; + + /// + /// Accuracy used to weight judgements independently from the score's actual accuracy. + /// private double customAccuracy => (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320); } } From 984634c5802c61ba0ece44e2597411d9e8fc2772 Mon Sep 17 00:00:00 2001 From: 63411 <62799417+molneya@users.noreply.github.com> Date: Sat, 18 Jun 2022 22:27:28 +0800 Subject: [PATCH 066/803] Adjust customAccuracy formula slightly --- .../Difficulty/ManiaPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index ed2654ceda..dea02adfbf 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -73,6 +73,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty /// /// Accuracy used to weight judgements independently from the score's actual accuracy. /// - private double customAccuracy => (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320); + private double customAccuracy => Math.Max((countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50 - countMiss * 100) / (totalHits * 320), 0); } } From 56abe54fe6331f88c014baf7e34128bc070e8cec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 18 Jun 2022 22:59:19 +0900 Subject: [PATCH 067/803] Redirect graceful exiting to standard exit flow Rather than doing a completely forced exit as we were, this will now follow a more standard flow with the ability for the user to abort along the way. This is more in line with how I wanted this to work. Note that this means a confirmation is now shown. It has been discussed in the past that this confirmation should only show when there's an ongoing action implies the user may want to cancel the exit. For now I think this is fine. Addresses https://github.com/ppy/osu/discussions/18399#discussioncomment-2811311 --- osu.Game/OsuGame.cs | 6 ++++++ osu.Game/OsuGameBase.cs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 3041761a6b..06ade5a3c9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -637,6 +637,12 @@ namespace osu.Game Add(performFromMainMenuTask = new PerformFromMenuRunner(action, validScreens, () => ScreenStack.CurrentScreen)); } + public override void GracefullyExit() + { + // Using PerformFromScreen gives the user a chance to interrupt the exit process if needed. + PerformFromScreen(menu => menu.Exit()); + } + /// /// Wait for the game (and target component) to become loaded and then run an action. /// diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 9908697bf1..dc7513d3c8 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -419,7 +419,7 @@ namespace osu.Game /// Use to programatically exit the game as if the user was triggering via alt-f4. /// Will keep persisting until an exit occurs (exit may be blocked multiple times). /// - public void GracefullyExit() + public virtual void GracefullyExit() { if (!OnExiting()) Exit(); From b2663d9399fd5b9fd75fef0a4bdf13a33ae29632 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 19 Jun 2022 01:14:48 +0900 Subject: [PATCH 068/803] Update xmldoc to suggest that `GracefullyExit` may be overridden --- 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 dc7513d3c8..4a9f74d195 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -417,7 +417,7 @@ namespace osu.Game /// /// Use to programatically exit the game as if the user was triggering via alt-f4. - /// Will keep persisting until an exit occurs (exit may be blocked multiple times). + /// By default, will keep persisting until an exit occurs (exit may be blocked multiple times). /// public virtual void GracefullyExit() { From 50e40756c462318ddebb812fe317c57ec1a4de70 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 19 Jun 2022 12:34:14 +0900 Subject: [PATCH 069/803] Rename `GracefullyExit` to `AttemptExit` --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 2 +- osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs | 2 +- osu.Game/OsuGame.cs | 2 +- osu.Game/OsuGameBase.cs | 5 +++-- .../Settings/Sections/Maintenance/MigrationSelectScreen.cs | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 59bc2dabb2..4e5f8d37b1 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -154,7 +154,7 @@ namespace osu.Desktop.Updater Activated = () => { updateManager.PrepareUpdateAsync() - .ContinueWith(_ => updateManager.Schedule(() => game?.GracefullyExit())); + .ContinueWith(_ => updateManager.Schedule(() => game?.AttemptExit())); return true; }; } diff --git a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs index fb33b3662a..447d6f44ce 100644 --- a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs +++ b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tournament.Screens.Setup dropdown.Items = storage.ListTournaments(); dropdown.Current.BindValueChanged(v => Button.Enabled.Value = v.NewValue != startupTournament, true); - Action = () => game.GracefullyExit(); + Action = () => game.AttemptExit(); folderButton.Action = () => storage.PresentExternally(); ButtonText = "Close osu!"; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 06ade5a3c9..97476358b1 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -637,7 +637,7 @@ namespace osu.Game Add(performFromMainMenuTask = new PerformFromMenuRunner(action, validScreens, () => ScreenStack.CurrentScreen)); } - public override void GracefullyExit() + public override void AttemptExit() { // Using PerformFromScreen gives the user a chance to interrupt the exit process if needed. PerformFromScreen(menu => menu.Exit()); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 4a9f74d195..1385c06640 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -418,13 +418,14 @@ namespace osu.Game /// /// Use to programatically exit the game as if the user was triggering via alt-f4. /// By default, will keep persisting until an exit occurs (exit may be blocked multiple times). + /// May be interrupted (see 's override. /// - public virtual void GracefullyExit() + public virtual void AttemptExit() { if (!OnExiting()) Exit(); else - Scheduler.AddDelayed(GracefullyExit, 2000); + Scheduler.AddDelayed(AttemptExit, 2000); } public bool Migrate(string path) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index 7e6341cd1b..c51a4db71a 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance dialogOverlay.Push(new ConfirmDialog("To complete this operation, osu! will close. Please open it again to use the new data location.", () => { (storage as OsuStorage)?.ChangeDataPath(target.FullName); - game.GracefullyExit(); + game.AttemptExit(); }, () => { })); }, () => { })); From de61b9e4109e693b96041f2ba0add55fe4e09394 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 17 Jun 2022 22:03:04 -0700 Subject: [PATCH 070/803] Revert "Add automated commit to git-blame-ignore-revs" This reverts commit 11bd87045ef7f18bd7cb5cf88fbd327b15f65257. --- .git-blame-ignore-revs | 2 -- 1 file changed, 2 deletions(-) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 39fa0f9f6c..8be6479043 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,4 +1,2 @@ # Normalize all the line endings 32a74f95a5c80a0ed18e693f13a47522099df5c3 -# Enabled NRT globally -f8830c6850128456266c82de83273204f8b74ac0 From 6515b249ec92964ac2e6c7711f3d336daf3ecc69 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 19 Jun 2022 12:39:58 +0900 Subject: [PATCH 071/803] Change data migration to do an immediate exit, rather than attempted exit --- .../Settings/Sections/Maintenance/MigrationSelectScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index c51a4db71a..0d32e33d87 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance dialogOverlay.Push(new ConfirmDialog("To complete this operation, osu! will close. Please open it again to use the new data location.", () => { (storage as OsuStorage)?.ChangeDataPath(target.FullName); - game.AttemptExit(); + game.Exit(); }, () => { })); }, () => { })); From 98d69c846c5e285e5f41984f0465325aec438699 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 19 Jun 2022 17:12:19 +0900 Subject: [PATCH 072/803] Reset spectator speed at last frame --- osu.Game/Screens/Play/SpectatorPlayer.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index d5aec055fc..72a7d3c8a1 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -83,6 +83,18 @@ namespace osu.Game.Screens.Play SetGameplayStartTime(score.Replay.Frames[0].Time); } + protected override void Update() + { + base.Update(); + + if (HUDOverlay.PlayerSettingsOverlay.PlaybackSettings.UserPlaybackRate.Value > 1 + && score.Replay.Frames.Count > 0 + && DrawableRuleset.FrameStableClock.CurrentTime >= score.Replay.Frames[^1].Time) + { + HUDOverlay.PlayerSettingsOverlay.PlaybackSettings.UserPlaybackRate.Value = 1; + } + } + protected override Score CreateScore(IBeatmap beatmap) => score; protected override ResultsScreen CreateResults(ScoreInfo score) From 495d747da55eab3cbfcc8331ffe5f6e73c6e3618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Jun 2022 13:42:45 +0200 Subject: [PATCH 073/803] Fix mismatching braces 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 1385c06640..f0f364314c 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -418,7 +418,7 @@ namespace osu.Game /// /// Use to programatically exit the game as if the user was triggering via alt-f4. /// By default, will keep persisting until an exit occurs (exit may be blocked multiple times). - /// May be interrupted (see 's override. + /// May be interrupted (see 's override). /// public virtual void AttemptExit() { From 9a6f4ef76d052bf11317df92284022cf8f74d42b Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 19 Jun 2022 23:59:37 +0900 Subject: [PATCH 074/803] Save score button on failed screen --- osu.Game/Screens/Play/FailOverlay.cs | 3 +++ osu.Game/Screens/Play/Player.cs | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index 1a31b0f462..b09a7de631 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using osu.Game.Graphics; using osuTK.Graphics; using osu.Framework.Allocation; @@ -11,12 +12,14 @@ namespace osu.Game.Screens.Play { public class FailOverlay : GameplayMenuOverlay { + public Action SaveReplay; public override string Header => "failed"; public override string Description => "you're dead, try again?"; [BackgroundDependencyLoader] private void load(OsuColour colours) { + AddButton("Save replay and Quit", colours.Blue, () => SaveReplay?.Invoke()); AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4a8460ff46..c404510487 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -266,6 +266,7 @@ namespace osu.Game.Screens.Play }, FailOverlay = new FailOverlay { + SaveReplay = saveReplay, OnRetry = Restart, OnQuit = () => PerformExit(true), }, @@ -1043,6 +1044,21 @@ namespace osu.Game.Screens.Play return base.OnExiting(e); } + // Don't know if prepareScoreForResults useful + private async void saveReplay() + { + var scoreCopy = Score.DeepClone(); + try + { + await ImportScore(scoreCopy).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.Error(ex, @"Score import failed!"); + } + PerformExit(true); + } + /// /// Creates the player's . /// From def87ed7822031164bebbf3ef474286a3533c91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Jun 2022 19:18:29 +0200 Subject: [PATCH 075/803] Add failing test for editor gameplay test using wrong ruleset --- .../Editing/TestSceneEditorNavigation.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs new file mode 100644 index 0000000000..85b50a9b21 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.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 NUnit.Framework; +using osu.Framework.Extensions; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Components.Timelines.Summary; +using osu.Game.Screens.Edit.GameplayTest; +using osu.Game.Screens.Select; +using osu.Game.Tests.Resources; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneEditorNavigation : OsuGameTestScene + { + [Test] + public void TestEditorGameplayTestAlwaysUsesOriginalRuleset() + { + BeatmapSetInfo beatmapSet = null!; + + AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); + AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); + + AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); + AddUntilStep("wait for song select", + () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) + && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + && songSelect.IsLoaded); + AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); + + AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); + AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded); + AddStep("test gameplay", () => + { + var testGameplayButton = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(testGameplayButton); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen is EditorPlayer editorPlayer && editorPlayer.IsLoaded); + AddAssert("current ruleset is osu!", () => Game.Ruleset.Value.Equals(new OsuRuleset().RulesetInfo)); + + AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield())); + AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + AddAssert("previous ruleset restored", () => Game.Ruleset.Value.Equals(new ManiaRuleset().RulesetInfo)); + } + } +} From 93b3ede2a05f06a3b37ab9ce72249c1a232e0f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Jun 2022 19:30:34 +0200 Subject: [PATCH 076/803] Always use beatmap ruleset in editor gameplay test mode Fixes cases where opening a convertible beatmap (so any osu! beatmap) with the game-global ruleset being set to anything but osu! would result in opening the editor gameplay test mode with the game-global ruleset rather than the beatmap's. --- osu.Game/Screens/Edit/EditorLoader.cs | 1 + osu.Game/Screens/Select/SongSelect.cs | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 702e22a272..4450367522 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -65,6 +65,7 @@ namespace osu.Game.Screens.Edit base.LoadComplete(); // will be restored via lease, see `DisallowExternalBeatmapRulesetChanges`. + Ruleset.Value = Beatmap.Value.BeatmapInfo.Ruleset; Mods.Value = Array.Empty(); } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index a17e7823a6..41fb55a856 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -786,7 +786,17 @@ namespace osu.Game.Screens.Select Ruleset.ValueChanged += r => updateSelectedRuleset(r.NewValue); - decoupledRuleset.ValueChanged += r => Ruleset.Value = r.NewValue; + decoupledRuleset.ValueChanged += r => + { + bool wasDisabled = Ruleset.Disabled; + + // a sub-screen may have taken a lease on this decoupled ruleset bindable, + // which would indirectly propagate to the game-global bindable via the `DisabledChanged` callback below. + // to make sure changes sync without crashes, lift the disable for a short while to sync, and then restore the old value. + Ruleset.Disabled = false; + Ruleset.Value = r.NewValue; + Ruleset.Disabled = wasDisabled; + }; decoupledRuleset.DisabledChanged += r => Ruleset.Disabled = r; Beatmap.BindValueChanged(workingBeatmapChanged); From 3ab8158b923d5ae8581c39d9b67b93a804de65b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Jun 2022 20:08:39 +0200 Subject: [PATCH 077/803] Do not try to set ruleset from beatmap if it's a dummy --- osu.Game/Screens/Edit/EditorLoader.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 4450367522..d6af990b52 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -65,7 +65,8 @@ namespace osu.Game.Screens.Edit base.LoadComplete(); // will be restored via lease, see `DisallowExternalBeatmapRulesetChanges`. - Ruleset.Value = Beatmap.Value.BeatmapInfo.Ruleset; + if (!(Beatmap.Value is DummyWorkingBeatmap)) + Ruleset.Value = Beatmap.Value.BeatmapInfo.Ruleset; Mods.Value = Array.Empty(); } From 36df1da57fda5cbfc8c44d97f24c7d4f0d7346f3 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Sun, 19 Jun 2022 14:26:02 -0400 Subject: [PATCH 078/803] Revert "stop component transform when skineditor is hidden" This reverts commit 48ffd6aeb5c131a120a9c82e889348ebf3d21dfb. --- .../Skinning/Editor/SkinSelectionHandler.cs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 2496d7c657..2e2122f7c2 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -11,7 +11,6 @@ using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; @@ -26,17 +25,8 @@ namespace osu.Game.Skinning.Editor [Resolved] private SkinEditor skinEditor { get; set; } - /// - /// Whether the 's state allows selected blueprints to undergo transformations. - /// Used to prevent operations from being performed on unloaded blueprints. - /// - private bool allowHandling => skinEditor.State.Value == Visibility.Visible; - public override bool HandleRotation(float angle) { - if (!allowHandling) - return false; - if (SelectedBlueprints.Count == 1) { // for single items, rotate around the origin rather than the selection centre. @@ -63,9 +53,6 @@ namespace osu.Game.Skinning.Editor public override bool HandleScale(Vector2 scale, Anchor anchor) { - if (!allowHandling) - return false; - // convert scale to screen space scale = ToScreenSpace(scale) - ToScreenSpace(Vector2.Zero); @@ -143,9 +130,6 @@ namespace osu.Game.Skinning.Editor public override bool HandleFlip(Direction direction, bool flipOverOrigin) { - if (!allowHandling) - return false; - var selectionQuad = getSelectionQuad(); Vector2 scaleFactor = direction == Direction.Horizontal ? new Vector2(-1, 1) : new Vector2(1, -1); @@ -166,9 +150,6 @@ namespace osu.Game.Skinning.Editor public override bool HandleMovement(MoveSelectionEvent moveEvent) { - if (!allowHandling) - return false; - foreach (var c in SelectedBlueprints) { var item = c.Item; From a5bc769243f3b0554805fa57855704a8da3b0ac1 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Sun, 19 Jun 2022 14:34:52 -0400 Subject: [PATCH 079/803] clear skineditor selected components on hide --- osu.Game/Skinning/Editor/SkinEditor.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index f41cd63f2d..7e9f1d0e8e 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -322,6 +322,12 @@ namespace osu.Game.Skinning.Editor protected override bool OnMouseDown(MouseDownEvent e) => true; + public override void Hide() + { + base.Hide(); + SelectedComponents.Clear(); + } + protected override void PopIn() { this From 2b0e82be4004c8c2ca894f1a6666de9adf754296 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Sun, 19 Jun 2022 14:35:05 -0400 Subject: [PATCH 080/803] add test coverage --- .../TestSceneSkinEditorNavigation.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 78ab7947a7..843d61ca3b 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -88,6 +88,31 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("value is less than default", () => hitErrorMeter.JudgementLineThickness.Value < hitErrorMeter.JudgementLineThickness.Default); } + [Test] + public void TestComponentsDeselectedOnSkinEditorHide() + { + advanceToSongSelect(); + openSkinEditor(); + switchToGameplayScene(); + AddStep("select all components", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.A); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + AddStep("toggle skin editor", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.S); + InputManager.ReleaseKey(Key.ControlLeft); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + + AddUntilStep("no components selected", () => skinEditor.SelectedComponents.Count == 0); + } + [Test] public void TestAutoplayCompatibleModsRetainedOnEnteringGameplay() { From 26b0815fc862557d4740c79e8fd967643f548a0c Mon Sep 17 00:00:00 2001 From: emu1337 Date: Mon, 20 Jun 2022 04:19:07 +0200 Subject: [PATCH 081/803] fixed casting the wrong skill --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 97899d2db9..fab3fa8425 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; - double speedNotes = (skills[1] as Speed).RelevantNoteCount(); + double speedNotes = (skills[2] as Speed).RelevantNoteCount(); double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; From 5e594e4de0c714ae6855fbf843245c30b40c0ee5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 20 Jun 2022 06:54:15 +0300 Subject: [PATCH 082/803] Work around macOS agent regression affecting iOS CI builds --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef729a779f..c728d89ed1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,6 +126,12 @@ jobs: with: dotnet-version: "6.0.x" + # macOS agents recently have empty NuGet config files, resulting in restore failures, + # see https://github.com/actions/virtual-environments/issues/5768 + # Add the global nuget package source manually for now. + - name: Setup NuGet.Config + run: echo '' > ~/.config/NuGet/NuGet.Config + # Contrary to seemingly any other msbuild, msbuild running on macOS/Mono # cannot accept .sln(f) files as arguments. # Build just the main game for now. From 243806e810de9304e914c9d1c63d1732cafbe15e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 13:29:43 +0900 Subject: [PATCH 083/803] Move common step of toggling skin editor to own method --- .../TestSceneSkinEditorNavigation.cs | 78 +++++++++++-------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 843d61ca3b..7185e5ce49 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -26,29 +26,6 @@ namespace osu.Game.Tests.Visual.Navigation private TestPlaySongSelect songSelect; private SkinEditor skinEditor => Game.ChildrenOfType().FirstOrDefault(); - private void advanceToSongSelect() - { - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - - AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); - - AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); - } - - private void openSkinEditor() - { - AddStep("open skin editor", () => - { - InputManager.PressKey(Key.ControlLeft); - InputManager.PressKey(Key.ShiftLeft); - InputManager.Key(Key.S); - InputManager.ReleaseKey(Key.ControlLeft); - InputManager.ReleaseKey(Key.ShiftLeft); - }); - AddUntilStep("skin editor loaded", () => skinEditor != null); - } - [Test] public void TestEditComponentDuringGameplay() { @@ -88,6 +65,24 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("value is less than default", () => hitErrorMeter.JudgementLineThickness.Value < hitErrorMeter.JudgementLineThickness.Default); } + [Test] + public void TestHideSkinEditorWhileDragging() + { + advanceToSongSelect(); + openSkinEditor(); + switchToGameplayScene(); + AddStep("select all components", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.A); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + toggleSkinEditor(); + + AddUntilStep("no components selected", () => skinEditor.SelectedComponents.Count == 0); + } + [Test] public void TestComponentsDeselectedOnSkinEditorHide() { @@ -101,14 +96,7 @@ namespace osu.Game.Tests.Visual.Navigation InputManager.ReleaseKey(Key.ControlLeft); }); - AddStep("toggle skin editor", () => - { - InputManager.PressKey(Key.ControlLeft); - InputManager.PressKey(Key.ShiftLeft); - InputManager.Key(Key.S); - InputManager.ReleaseKey(Key.ControlLeft); - InputManager.ReleaseKey(Key.ShiftLeft); - }); + toggleSkinEditor(); AddUntilStep("no components selected", () => skinEditor.SelectedComponents.Count == 0); } @@ -171,6 +159,34 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("mod overlay closed", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); } + private void advanceToSongSelect() + { + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + } + + private void openSkinEditor() + { + toggleSkinEditor(); + AddUntilStep("skin editor loaded", () => skinEditor != null); + } + + private void toggleSkinEditor() + { + AddStep("toggle skin editor", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.S); + InputManager.ReleaseKey(Key.ControlLeft); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + } + private void switchToGameplayScene() { AddStep("Click gameplay scene button", () => skinEditor.ChildrenOfType().First(b => b.Text == "Gameplay").TriggerClick()); From 329f1a08222c6ac9017025f484c771bc1c45d7aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 13:45:50 +0900 Subject: [PATCH 084/803] Add test coverage of original fail case and improve test reliability and code quality --- .../TestSceneSkinEditorNavigation.cs | 47 +++++++++++++++---- .../Skinning/Editor/SkinComponentToolbox.cs | 2 +- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 7185e5ce49..0c99cf15eb 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -8,7 +8,9 @@ using NUnit.Framework; using osu.Framework.Extensions; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Framework.Threading; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; @@ -16,6 +18,7 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Skinning.Editor; using osu.Game.Tests.Beatmaps.IO; +using osuTK; using osuTK.Input; using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation; @@ -66,11 +69,14 @@ namespace osu.Game.Tests.Visual.Navigation } [Test] - public void TestHideSkinEditorWhileDragging() + public void TestComponentsDeselectedOnSkinEditorHide() { advanceToSongSelect(); openSkinEditor(); switchToGameplayScene(); + + AddUntilStep("wait for components", () => skinEditor.ChildrenOfType().Any()); + AddStep("select all components", () => { InputManager.PressKey(Key.ControlLeft); @@ -78,27 +84,50 @@ namespace osu.Game.Tests.Visual.Navigation InputManager.ReleaseKey(Key.ControlLeft); }); + AddUntilStep("components selected", () => skinEditor.SelectedComponents.Count > 0); + toggleSkinEditor(); AddUntilStep("no components selected", () => skinEditor.SelectedComponents.Count == 0); } [Test] - public void TestComponentsDeselectedOnSkinEditorHide() + public void TestSwitchScreenWhileDraggingComponent() { + Vector2 firstBlueprintCentre = Vector2.Zero; + ScheduledDelegate movementDelegate = null; + advanceToSongSelect(); + openSkinEditor(); - switchToGameplayScene(); - AddStep("select all components", () => + + AddStep("add skinnable component", () => { - InputManager.PressKey(Key.ControlLeft); - InputManager.Key(Key.A); - InputManager.ReleaseKey(Key.ControlLeft); + skinEditor.ChildrenOfType().First().TriggerClick(); }); - toggleSkinEditor(); + AddUntilStep("newly added component selected", () => skinEditor.SelectedComponents.Count == 1); - AddUntilStep("no components selected", () => skinEditor.SelectedComponents.Count == 0); + AddStep("start drag", () => + { + firstBlueprintCentre = skinEditor.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre; + + InputManager.MoveMouseTo(firstBlueprintCentre); + InputManager.PressButton(MouseButton.Left); + }); + + AddStep("start movement", () => movementDelegate = Scheduler.AddDelayed(() => { InputManager.MoveMouseTo(firstBlueprintCentre += new Vector2(1)); }, 10, true)); + + toggleSkinEditor(); + AddStep("exit song select", () => songSelect.Exit()); + + AddUntilStep("wait for blueprints removed", () => !skinEditor.ChildrenOfType().Any()); + + AddStep("stop drag", () => + { + InputManager.ReleaseButton(MouseButton.Left); + movementDelegate?.Cancel(); + }); } [Test] diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 7de9cd5108..344a659627 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -88,7 +88,7 @@ namespace osu.Game.Skinning.Editor } } - private class ToolboxComponentButton : OsuButton + public class ToolboxComponentButton : OsuButton { protected override bool ShouldBeConsideredForInput(Drawable child) => false; From e4d0c7a0fb179ec5f8c56e1885811de7ec8525ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 13:59:20 +0900 Subject: [PATCH 085/803] Move beatmap import step to only be required when entering gameplay --- .../Visual/Navigation/TestSceneSkinEditorNavigation.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 0c99cf15eb..cc76e530c8 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -192,10 +192,6 @@ namespace osu.Game.Tests.Visual.Navigation { PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - - AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); - - AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); } private void openSkinEditor() @@ -218,6 +214,9 @@ namespace osu.Game.Tests.Visual.Navigation private void switchToGameplayScene() { + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + AddStep("Click gameplay scene button", () => skinEditor.ChildrenOfType().First(b => b.Text == "Gameplay").TriggerClick()); AddUntilStep("wait for player", () => From a6c8a832aaf9d9fd4cda147eef6850d1daa8ff37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 15:14:57 +0900 Subject: [PATCH 086/803] Remove `Import(ArchiveReader)` and redirect existing usages to `Import(ImportTask)` --- .../Database/BeatmapImporterTests.cs | 27 +++------- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 53 +++++++++---------- .../Skins/TestSceneBeatmapSkinResources.cs | 4 +- .../Skins/TestSceneSkinResources.cs | 4 +- osu.Game/Beatmaps/BeatmapManager.cs | 3 -- .../Database/RealmArchiveModelImporter.cs | 4 +- osu.Game/Screens/Menu/IntroScreen.cs | 3 +- osu.Game/Skinning/SkinManager.cs | 4 -- 8 files changed, 39 insertions(+), 63 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index f9cd75d8c3..841622f0db 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -15,7 +15,6 @@ using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Extensions; -using osu.Game.IO.Archives; using osu.Game.Models; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; @@ -39,10 +38,7 @@ namespace osu.Game.Tests.Database using (var importer = new BeatmapImporter(storage, realm)) using (new RealmRulesetStore(realm, storage)) { - Live? beatmapSet; - - using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) - beatmapSet = await importer.Import(reader); + var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz")); Assert.NotNull(beatmapSet); Debug.Assert(beatmapSet != null); @@ -83,10 +79,7 @@ namespace osu.Game.Tests.Database using (var importer = new BeatmapImporter(storage, realm)) using (new RealmRulesetStore(realm, storage)) { - Live? beatmapSet; - - using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) - beatmapSet = await importer.Import(reader); + var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz")); Assert.NotNull(beatmapSet); Debug.Assert(beatmapSet != null); @@ -146,11 +139,8 @@ namespace osu.Game.Tests.Database { Task.Run(async () => { - Live? beatmapSet; - - using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) - // ReSharper disable once AccessToDisposedClosure - beatmapSet = await importer.Import(reader); + // ReSharper disable once AccessToDisposedClosure + var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz")); Assert.NotNull(beatmapSet); Debug.Assert(beatmapSet != null); @@ -173,13 +163,8 @@ namespace osu.Game.Tests.Database using (var importer = new BeatmapImporter(storage, realm)) using (new RealmRulesetStore(realm, storage)) { - Live? imported; - - using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) - { - imported = await importer.Import(reader); - EnsureLoaded(realm.Realm); - } + var imported = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz")); + EnsureLoaded(realm.Realm); Assert.AreEqual(1, realm.Realm.All().Count()); diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 4db88cbe82..f3adb85006 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -15,7 +15,6 @@ using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Extensions; using osu.Game.IO; -using osu.Game.IO.Archives; using osu.Game.Skinning; using SharpCompress.Archives.Zip; @@ -28,7 +27,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestSingleImportDifferentFilename() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk")); // When the import filename doesn't match, it should be appended (and update the skin.ini). assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu); @@ -37,7 +36,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestSingleImportWeirdIniFileCase() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner", iniFilename: "Skin.InI"), "skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner", iniFilename: "Skin.InI"), "skin.osk")); // When the import filename doesn't match, it should be appended (and update the skin.ini). assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu); @@ -46,7 +45,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestSingleImportMissingSectionHeader() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner", includeSectionHeader: false), "skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner", includeSectionHeader: false), "skin.osk")); // When the import filename doesn't match, it should be appended (and update the skin.ini). assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu); @@ -55,7 +54,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestSingleImportMatchingFilename() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "test skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "test skin.osk")); // When the import filename matches it shouldn't be appended. assertCorrectMetadata(import1, "test skin", "skinner", osu); @@ -64,7 +63,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestSingleImportNoIniFile() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithNonIniFile(), "test skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithNonIniFile(), "test skin.osk")); // When the import filename matches it shouldn't be appended. assertCorrectMetadata(import1, "test skin", "Unknown", osu); @@ -73,7 +72,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestEmptyImportImportsWithFilename() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createEmptyOsk(), "test skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createEmptyOsk(), "test skin.osk")); // When the import filename matches it shouldn't be appended. assertCorrectMetadata(import1, "test skin", "Unknown", osu); @@ -86,8 +85,8 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestImportTwiceWithSameMetadataAndFilename() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk")); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk")); assertImportedOnce(import1, import2); }); @@ -96,8 +95,8 @@ namespace osu.Game.Tests.Skins.IO public Task TestImportTwiceWithNoMetadataSameDownloadFilename() => runSkinTest(async osu => { // if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety. - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download.osk")); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk")); assertImportedOnce(import1, import2); }); @@ -105,10 +104,10 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestImportUpperCasedOskArchive() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "name 1.OsK")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "name 1.OsK")); assertCorrectMetadata(import1, "name 1", "author 1", osu); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "name 1.oSK")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "name 1.oSK")); assertImportedOnce(import1, import2); }); @@ -118,7 +117,7 @@ namespace osu.Game.Tests.Skins.IO { MemoryStream exportStream = new MemoryStream(); - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "custom.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "custom.osk")); assertCorrectMetadata(import1, "name 1 [custom]", "author 1", osu); import1.PerformRead(s => @@ -128,7 +127,7 @@ namespace osu.Game.Tests.Skins.IO string exportFilename = import1.GetDisplayString(); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(exportStream, $"{exportFilename}.osk")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(exportStream, $"{exportFilename}.osk")); assertCorrectMetadata(import2, "name 1 [custom]", "author 1", osu); assertImportedOnce(import1, import2); @@ -137,8 +136,8 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestSameMetadataNameSameFolderName() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 1")); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 1")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1")); assertImportedOnce(import1, import2); assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", osu); @@ -151,8 +150,8 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestImportTwiceWithSameMetadataButDifferentFilename() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk")); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin2.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin2.osk")); assertImportedBoth(import1, import2); }); @@ -161,8 +160,8 @@ namespace osu.Game.Tests.Skins.IO public Task TestImportTwiceWithNoMetadataDifferentDownloadFilename() => runSkinTest(async osu => { // if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety. - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download.osk")); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download2.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download2.osk")); assertImportedBoth(import1, import2); }); @@ -170,8 +169,8 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestImportTwiceWithSameFilenameDifferentMetadata() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin v2", "skinner"), "skin.osk")); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin v2.1", "skinner"), "skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin v2", "skinner"), "skin.osk")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin v2.1", "skinner"), "skin.osk")); assertImportedBoth(import1, import2); assertCorrectMetadata(import1, "test skin v2 [skin]", "skinner", osu); @@ -181,8 +180,8 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestSameMetadataNameDifferentFolderName() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 1")); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 2")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 2")); assertImportedBoth(import1, import2); assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", osu); @@ -358,10 +357,10 @@ namespace osu.Game.Tests.Skins.IO } } - private async Task> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null) + private async Task> loadSkinIntoOsu(OsuGameBase osu, ImportTask import = null) { var skinManager = osu.Dependencies.Get(); - return await skinManager.Import(archive); + return await skinManager.Import(import); } } } diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index bd7e1f8ec5..f4cea2c8cc 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -10,7 +10,7 @@ using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.IO.Archives; +using osu.Game.Database; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual; @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Skins [BackgroundDependencyLoader] private void load() { - var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).GetResultSafely(); + var imported = beatmaps.Import(new ImportTask(TestResources.OpenResource("Archives/ogg-beatmap.osz"), "ogg-beatmap.osz")).GetResultSafely(); imported?.PerformRead(s => { diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs index 97588f4053..42c1eeb6d1 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs @@ -8,7 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Audio; -using osu.Game.IO.Archives; +using osu.Game.Database; using osu.Game.Skinning; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual; @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Skins [BackgroundDependencyLoader] private void load() { - var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).GetResultSafely(); + var imported = skins.Import(new ImportTask(TestResources.OpenResource("Archives/ogg-skin.osk"), "ogg-skin.osk")).GetResultSafely(); skin = imported.PerformRead(skinInfo => skins.GetSkin(skinInfo)); } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 1dd3f82426..91e1744258 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -409,9 +409,6 @@ namespace osu.Game.Beatmaps public Task?> Import(ImportTask task, bool batchImport = false, CancellationToken cancellationToken = default) => beatmapImporter.Import(task, batchImport, cancellationToken); - public Task?> Import(ArchiveReader archive, bool batchImport = false, CancellationToken cancellationToken = default) => - beatmapImporter.Import(archive, batchImport, cancellationToken); - public Live? Import(BeatmapSetInfo item, ArchiveReader? archive = null, CancellationToken cancellationToken = default) => beatmapImporter.Import(item, archive, false, cancellationToken); diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index c5e04a8a0f..84decdb3ef 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -188,7 +188,7 @@ namespace osu.Game.Database Live? import; using (ArchiveReader reader = task.GetReader()) - import = await Import(reader, batchImport, cancellationToken).ConfigureAwait(false); + import = await importFromArchive(reader, batchImport, cancellationToken).ConfigureAwait(false); // We may or may not want to delete the file depending on where it is stored. // e.g. reconstructing/repairing database with items from default storage. @@ -213,7 +213,7 @@ namespace osu.Game.Database /// The archive to be imported. /// Whether this import is part of a larger batch. /// An optional cancellation token. - public async Task?> Import(ArchiveReader archive, bool batchImport = false, CancellationToken cancellationToken = default) + private async Task?> importFromArchive(ArchiveReader archive, bool batchImport = false, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 766b5bf2a4..c81195bbd3 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -19,7 +19,6 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; -using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -141,7 +140,7 @@ namespace osu.Game.Screens.Menu { // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. // this could happen if a user has nuked their files store. for now, reimport to repair this. - var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); + var import = beatmaps.Import(new ImportTask(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); import?.PerformWrite(b => b.Protected = true); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index be6528db3f..16a0d84263 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -24,7 +24,6 @@ using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Database; using osu.Game.IO; -using osu.Game.IO.Archives; using osu.Game.Overlays.Notifications; using osu.Game.Utils; @@ -275,9 +274,6 @@ namespace osu.Game.Skinning public Task> Import(ImportTask task, bool batchImport = false, CancellationToken cancellationToken = default) => skinImporter.Import(task, batchImport, cancellationToken); - public Task> Import(ArchiveReader archive, bool batchImport = false, CancellationToken cancellationToken = default) => - skinImporter.Import(archive, batchImport, cancellationToken); - #endregion public void Delete([CanBeNull] Expression> filter = null, bool silent = false) From 92f1a2958c1645dff914fc3f2b0e2a59d803da77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 15:18:07 +0900 Subject: [PATCH 087/803] Rename `Import(TModel)` to `ImportModel` to differentiate from other import methods --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 2 +- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 4 ++-- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++-- osu.Game/Database/RealmArchiveModelImporter.cs | 4 ++-- osu.Game/Scoring/ScoreManager.cs | 2 +- osu.Game/Skinning/SkinManager.cs | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 841622f0db..d8856a2ca7 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -745,7 +745,7 @@ namespace osu.Game.Tests.Database } }; - var imported = importer.Import(toImport); + var imported = importer.ImportModel(toImport); realm.Run(r => r.Refresh()); diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index e502a71f34..1d33a895eb 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -227,10 +227,10 @@ namespace osu.Game.Tests.Online this.testBeatmapManager = testBeatmapManager; } - public override Live Import(BeatmapSetInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default) + public override Live ImportModel(BeatmapSetInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default) { testBeatmapManager.AllowImport.Task.WaitSafely(); - return (testBeatmapManager.CurrentImport = base.Import(item, archive, batchImport, cancellationToken)); + return (testBeatmapManager.CurrentImport = base.ImportModel(item, archive, batchImport, cancellationToken)); } } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 91e1744258..06a8ad5188 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -101,7 +101,7 @@ namespace osu.Game.Beatmaps foreach (BeatmapInfo b in beatmapSet.Beatmaps) b.BeatmapSet = beatmapSet; - var imported = beatmapImporter.Import(beatmapSet); + var imported = beatmapImporter.ImportModel(beatmapSet); if (imported == null) throw new InvalidOperationException("Failed to import new beatmap"); @@ -410,7 +410,7 @@ namespace osu.Game.Beatmaps beatmapImporter.Import(task, batchImport, cancellationToken); public Live? Import(BeatmapSetInfo item, ArchiveReader? archive = null, CancellationToken cancellationToken = default) => - beatmapImporter.Import(item, archive, false, cancellationToken); + beatmapImporter.ImportModel(item, archive, false, cancellationToken); public IEnumerable HandledExtensions => beatmapImporter.HandledExtensions; diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 84decdb3ef..3d61345f34 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -236,7 +236,7 @@ namespace osu.Game.Database return null; } - var scheduledImport = Task.Factory.StartNew(() => Import(model, archive, batchImport, cancellationToken), + var scheduledImport = Task.Factory.StartNew(() => ImportModel(model, archive, batchImport, cancellationToken), cancellationToken, TaskCreationOptions.HideScheduler, batchImport ? import_scheduler_batch : import_scheduler); @@ -251,7 +251,7 @@ namespace osu.Game.Database /// An optional archive to use for model population. /// If true, imports will be skipped before they begin, given an existing model matches on hash and filenames. Should generally only be used for large batch imports, as it may defy user expectations when updating an existing model. /// An optional cancellation token. - public virtual Live? Import(TModel item, ArchiveReader? archive = null, bool batchImport = false, CancellationToken cancellationToken = default) => Realm.Run(realm => + public virtual Live? ImportModel(TModel item, ArchiveReader? archive = null, bool batchImport = false, CancellationToken cancellationToken = default) => Realm.Run(realm => { cancellationToken.ThrowIfCancellationRequested(); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 7ccf7a58b8..44aafbec74 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -265,7 +265,7 @@ namespace osu.Game.Scoring public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) => scoreImporter.Import(notification, tasks); public Live Import(ScoreInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default) => - scoreImporter.Import(item, archive, batchImport, cancellationToken); + scoreImporter.ImportModel(item, archive, batchImport, cancellationToken); #region Implementation of IPresentImports diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 16a0d84263..38eff2f0d6 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -167,7 +167,7 @@ namespace osu.Game.Skinning Name = NamingUtils.GetNextBestName(existingSkinNames, $@"{s.Name} (modified)") }; - var result = skinImporter.Import(skinInfo); + var result = skinImporter.ImportModel(skinInfo); if (result != null) { From dd93c7359e184f1bf630dacfd9dcfaf1b6f6ff7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 15:25:43 +0900 Subject: [PATCH 088/803] Update xmldoc for `importFromArchive` method --- .../Database/RealmArchiveModelImporter.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 3d61345f34..f3174b46b8 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -208,8 +208,11 @@ namespace osu.Game.Database } /// - /// Silently import an item from an . + /// Create and import a model based off the provided . /// + /// + /// This method also handled queueing the import task on a relevant import thread pool. + /// /// The archive to be imported. /// Whether this import is part of a larger batch. /// An optional cancellation token. @@ -225,6 +228,13 @@ namespace osu.Game.Database if (model == null) return null; + + var scheduledImport = Task.Factory.StartNew(() => ImportModel(model, archive, batchImport, cancellationToken), + cancellationToken, + TaskCreationOptions.HideScheduler, + batchImport ? import_scheduler_batch : import_scheduler); + + return await scheduledImport.ConfigureAwait(false); } catch (TaskCanceledException) { @@ -235,13 +245,6 @@ namespace osu.Game.Database LogForModel(model, @$"Model creation of {archive.Name} failed.", e); return null; } - - var scheduledImport = Task.Factory.StartNew(() => ImportModel(model, archive, batchImport, cancellationToken), - cancellationToken, - TaskCreationOptions.HideScheduler, - batchImport ? import_scheduler_batch : import_scheduler); - - return await scheduledImport.ConfigureAwait(false); } /// From 03ab6fc141ea1931bf877ca55fd5aeac4162cf3a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 20 Jun 2022 14:56:04 +0900 Subject: [PATCH 089/803] Implement IEquatable on ControlPoint --- .../Beatmaps/ControlPoints/ControlPoint.cs | 15 ++++++++-- .../ControlPoints/ControlPointGroup.cs | 26 +++++++++++++---- .../ControlPoints/DifficultyControlPoint.cs | 15 ++++++++-- .../ControlPoints/EffectControlPoint.cs | 17 +++++++++-- .../ControlPoints/SampleControlPoint.cs | 16 +++++++++-- .../ControlPoints/TimingControlPoint.cs | 14 +++++++++- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 28 ++++++++++++++++--- 7 files changed, 109 insertions(+), 22 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 5f06e03509..fd06c898ab 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using Newtonsoft.Json; using osu.Game.Graphics; @@ -11,7 +9,7 @@ using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints { - public abstract class ControlPoint : IComparable, IDeepCloneable + public abstract class ControlPoint : IComparable, IDeepCloneable, IEquatable { /// /// The time at which the control point takes effect. @@ -48,5 +46,16 @@ namespace osu.Game.Beatmaps.ControlPoints { Time = other.Time; } + + public sealed override bool Equals(object? obj) + => obj is ControlPoint otherControlPoint + && Equals(otherControlPoint); + + public virtual bool Equals(ControlPoint? other) + => other != null + && Time == other.Time; + + // ReSharper disable once NonReadonlyMemberInGetHashCode + public override int GetHashCode() => Time.GetHashCode(); } } diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs index 9df38b01ee..db479f0e5b 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs @@ -1,18 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Bindables; namespace osu.Game.Beatmaps.ControlPoints { - public class ControlPointGroup : IComparable + public class ControlPointGroup : IComparable, IEquatable { - public event Action ItemAdded; - public event Action ItemRemoved; + public event Action? ItemAdded; + public event Action? ItemRemoved; /// /// The time at which the control point takes effect. @@ -48,5 +46,23 @@ namespace osu.Game.Beatmaps.ControlPoints controlPoints.Remove(point); ItemRemoved?.Invoke(point); } + + public sealed override bool Equals(object? obj) + => obj is ControlPointGroup otherGroup + && Equals(otherGroup); + + public virtual bool Equals(ControlPointGroup? other) + => other != null + && Time == other.Time + && ControlPoints.SequenceEqual(other.ControlPoints); + + public override int GetHashCode() + { + HashCode hashCode = new HashCode(); + hashCode.Add(Time); + foreach (var point in controlPoints) + hashCode.Add(point); + return hashCode.ToHashCode(); + } } } diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 8b3d755fb6..6f624d20c9 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -1,8 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System; using osu.Framework.Bindables; using osu.Game.Graphics; using osuTK.Graphics; @@ -12,7 +11,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// Note that going forward, this control point type should always be assigned directly to HitObjects. /// - public class DifficultyControlPoint : ControlPoint + public class DifficultyControlPoint : ControlPoint, IEquatable { public static readonly DifficultyControlPoint DEFAULT = new DifficultyControlPoint { @@ -51,5 +50,15 @@ namespace osu.Game.Beatmaps.ControlPoints base.CopyFrom(other); } + + public override bool Equals(ControlPoint? other) + => other is DifficultyControlPoint otherDifficultyControlPoint + && Equals(otherDifficultyControlPoint); + + public bool Equals(DifficultyControlPoint? other) + => base.Equals(other) + && SliderVelocity == other.SliderVelocity; + + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), SliderVelocity); } } diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 35425972da..365d024035 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -1,15 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System; using osu.Framework.Bindables; using osu.Game.Graphics; using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints { - public class EffectControlPoint : ControlPoint + public class EffectControlPoint : ControlPoint, IEquatable { public static readonly EffectControlPoint DEFAULT = new EffectControlPoint { @@ -83,5 +82,17 @@ namespace osu.Game.Beatmaps.ControlPoints base.CopyFrom(other); } + + public override bool Equals(ControlPoint? other) + => other is EffectControlPoint otherEffectControlPoint + && Equals(otherEffectControlPoint); + + public bool Equals(EffectControlPoint? other) + => base.Equals(other) + && OmitFirstBarLine == other.OmitFirstBarLine + && ScrollSpeed == other.ScrollSpeed + && KiaiMode == other.KiaiMode; + + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), OmitFirstBarLine, ScrollSpeed, KiaiMode); } } diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index bed499ef3f..ab575dfeda 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -1,8 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System; using osu.Framework.Bindables; using osu.Game.Audio; using osu.Game.Graphics; @@ -13,7 +12,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// Note that going forward, this control point type should always be assigned directly to HitObjects. /// - public class SampleControlPoint : ControlPoint + public class SampleControlPoint : ControlPoint, IEquatable { public const string DEFAULT_BANK = "normal"; @@ -85,5 +84,16 @@ namespace osu.Game.Beatmaps.ControlPoints base.CopyFrom(other); } + + public override bool Equals(ControlPoint? other) + => other is SampleControlPoint otherSampleControlPoint + && Equals(otherSampleControlPoint); + + public bool Equals(SampleControlPoint? other) + => base.Equals(other) + && SampleBank == other.SampleBank + && SampleVolume == other.SampleVolume; + + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), SampleBank, SampleVolume); } } diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 922439fcb8..87ef4e86d5 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.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.Bindables; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; @@ -8,7 +9,7 @@ using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints { - public class TimingControlPoint : ControlPoint + public class TimingControlPoint : ControlPoint, IEquatable { /// /// The time signature at this control point. @@ -77,5 +78,16 @@ namespace osu.Game.Beatmaps.ControlPoints base.CopyFrom(other); } + + public override bool Equals(ControlPoint? other) + => other is TimingControlPoint otherTimingControlPoint + && Equals(otherTimingControlPoint); + + public bool Equals(TimingControlPoint? other) + => base.Equals(other) + && TimeSignature.Equals(other.TimeSignature) + && BeatLength.Equals(other.BeatLength); + + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), TimeSignature, BeatLength); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 0865561854..cc6a9126bd 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Framework.Extensions; @@ -162,7 +160,7 @@ namespace osu.Game.Beatmaps.Formats } [Obsolete("Do not use unless you're a legacy ruleset and 100% sure.")] - public class LegacyDifficultyControlPoint : DifficultyControlPoint + public class LegacyDifficultyControlPoint : DifficultyControlPoint, IEquatable { /// /// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it. @@ -188,9 +186,20 @@ namespace osu.Game.Beatmaps.Formats BpmMultiplier = ((LegacyDifficultyControlPoint)other).BpmMultiplier; } + + public override bool Equals(ControlPoint? other) + => other is LegacyDifficultyControlPoint otherLegacyDifficultyControlPoint + && Equals(otherLegacyDifficultyControlPoint); + + public bool Equals(LegacyDifficultyControlPoint? other) + => base.Equals(other) + && BpmMultiplier == other.BpmMultiplier; + + // ReSharper disable once NonReadonlyMemberInGetHashCode + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), BpmMultiplier); } - internal class LegacySampleControlPoint : SampleControlPoint + internal class LegacySampleControlPoint : SampleControlPoint, IEquatable { public int CustomSampleBank; @@ -215,6 +224,17 @@ namespace osu.Game.Beatmaps.Formats CustomSampleBank = ((LegacySampleControlPoint)other).CustomSampleBank; } + + public override bool Equals(ControlPoint? other) + => other is LegacySampleControlPoint otherLegacySampleControlPoint + && Equals(otherLegacySampleControlPoint); + + public bool Equals(LegacySampleControlPoint? other) + => base.Equals(other) + && CustomSampleBank == other.CustomSampleBank; + + // ReSharper disable once NonReadonlyMemberInGetHashCode + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank); } } } From a922ea9b0163a805c5b2108ddecd22cb6d412c4a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 20 Jun 2022 15:28:36 +0900 Subject: [PATCH 090/803] Fix selection by directly comparing control points Previously, all control points would get replaced, which led to performance issues that was worked around in this PR. By comparing control points, we're able to get good performance without requiring the workaround. --- .../Edit/LegacyEditorBeatmapPatcher.cs | 28 +++++++++++++++---- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 8 ++---- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index 6b18855396..01bc01bf5b 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -6,12 +6,14 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using DiffPlex; using DiffPlex.Model; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Skinning; @@ -50,14 +52,28 @@ namespace osu.Game.Screens.Edit if (removedIndices.Count == 0 && addedIndices.Count == 0) return; - // Due to conversion from legacy to non-legacy control points, it becomes difficult to diff control points correctly. - // So instead _all_ control points are reloaded if _any_ control point is changed. + ControlPointInfo newControlPoints = EditorBeatmap.ConvertControlPoints(getNewBeatmap().ControlPointInfo); - var newControlPoints = EditorBeatmap.ConvertControlPoints(getNewBeatmap().ControlPointInfo); + // Remove all groups from the current beatmap which don't have a corresponding equal group in the new beatmap. + foreach (var oldGroup in editorBeatmap.ControlPointInfo.Groups.ToArray()) + { + var newGroup = newControlPoints.GroupAt(oldGroup.Time); - editorBeatmap.ControlPointInfo.Clear(); - foreach (var point in newControlPoints.AllControlPoints) - editorBeatmap.ControlPointInfo.Add(point.Time, point); + if (!oldGroup.Equals(newGroup)) + editorBeatmap.ControlPointInfo.RemoveGroup(oldGroup); + } + + // Add all groups from the new beatmap which don't have a corresponding equal group in the old beatmap. + foreach (var newGroup in newControlPoints.Groups) + { + var oldGroup = editorBeatmap.ControlPointInfo.GroupAt(newGroup.Time); + + if (!newGroup.Equals(oldGroup)) + { + foreach (var point in newGroup.ControlPoints) + editorBeatmap.ControlPointInfo.Add(newGroup.Time, point); + } + } } private void processHitObjects(DiffResult result, Func getNewBeatmap) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 27b62f1104..60cb263a79 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -139,12 +139,8 @@ namespace osu.Game.Screens.Edit.Timing controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((sender, args) => { - // This AddOnce() works around performance issues from the LegacyEditorBeatmapPatcher re-initialising all control points every undo & redo. - Scheduler.AddOnce(() => - { - table.ControlGroups = controlPointGroups; - changeHandler?.SaveState(); - }); + table.ControlGroups = controlPointGroups; + changeHandler?.SaveState(); }, true); } From 678b18dde68ab4d9ba874edfd6de73bc2b28c2e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 15:29:56 +0900 Subject: [PATCH 091/803] Ensure any non-`MemoryStream` streams are closed as part of `ImportTask` --- osu.Game/Database/ImportTask.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Database/ImportTask.cs b/osu.Game/Database/ImportTask.cs index 0643300fc5..17ca691be3 100644 --- a/osu.Game/Database/ImportTask.cs +++ b/osu.Game/Database/ImportTask.cs @@ -62,6 +62,7 @@ namespace osu.Game.Database { // This isn't used in any current path. May need to reconsider for performance reasons (ie. if we don't expect the incoming stream to be copied out). memoryStream = new MemoryStream(stream.ReadAllBytesToArray()); + stream.Dispose(); } if (ZipUtils.IsZipArchive(memoryStream)) From 882e1c69e50bd53fe87df0525d3f5122517f10dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 15:36:44 +0900 Subject: [PATCH 092/803] Move nested task back outside try-catch to avoid double-error --- osu.Game/Database/RealmArchiveModelImporter.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index f3174b46b8..e6956d3184 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -228,13 +228,6 @@ namespace osu.Game.Database if (model == null) return null; - - var scheduledImport = Task.Factory.StartNew(() => ImportModel(model, archive, batchImport, cancellationToken), - cancellationToken, - TaskCreationOptions.HideScheduler, - batchImport ? import_scheduler_batch : import_scheduler); - - return await scheduledImport.ConfigureAwait(false); } catch (TaskCanceledException) { @@ -245,6 +238,13 @@ namespace osu.Game.Database LogForModel(model, @$"Model creation of {archive.Name} failed.", e); return null; } + + var scheduledImport = Task.Factory.StartNew(() => ImportModel(model, archive, batchImport, cancellationToken), + cancellationToken, + TaskCreationOptions.HideScheduler, + batchImport ? import_scheduler_batch : import_scheduler); + + return await scheduledImport.ConfigureAwait(false); } /// From 4aa32038c3572fb648694a1f16302dea510a365e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 15:38:44 +0900 Subject: [PATCH 093/803] Add note about stream provided to `ImportTask` being disposed implicitly --- osu.Game/Database/ImportTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/ImportTask.cs b/osu.Game/Database/ImportTask.cs index 17ca691be3..e7f599d85f 100644 --- a/osu.Game/Database/ImportTask.cs +++ b/osu.Game/Database/ImportTask.cs @@ -33,7 +33,7 @@ namespace osu.Game.Database } /// - /// Construct a new import task from a stream. + /// Construct a new import task from a stream. The provided stream will be disposed after reading. /// public ImportTask(Stream stream, string filename) { From e82d948acc5d9ead0202f78707ff12d5069d08de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 16:06:22 +0900 Subject: [PATCH 094/803] Add test coverage of import throwing when no valid `.osu` files are found --- .../Database/BeatmapImporterTests.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index f9cd75d8c3..b018a26aec 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -291,6 +291,42 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestImportDirectoryWithEmptyOsuFiles() + { + RunTestWithRealmAsync(async (realm, storage) => + { + using var importer = new BeatmapImporter(storage, realm); + using var store = new RealmRulesetStore(realm, storage); + + string? temp = TestResources.GetTestBeatmapForImport(); + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + foreach (var file in new DirectoryInfo(extractedFolder).GetFiles("*.osu")) + { + using (file.Open(FileMode.Create)) + { + // empty file. + } + } + + var imported = await importer.Import(new ImportTask(extractedFolder)); + Assert.IsNull(imported); + } + finally + { + Directory.Delete(extractedFolder, true); + } + }); + } + [Test] public void TestImportThenImportWithReZip() { From d38defada461430817c8e99d21b49840ef769eb3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 16:07:10 +0900 Subject: [PATCH 095/803] Silence exception and provide more log output when import fails due to empty `.osu` files --- osu.Game/Beatmaps/BeatmapImporter.cs | 9 +++++++++ osu.Game/Beatmaps/Formats/Decoder.cs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index e463492e2b..237088036c 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -177,8 +177,17 @@ namespace osu.Game.Beatmaps } Beatmap beatmap; + using (var stream = new LineBufferedReader(reader.GetStream(mapName))) + { + if (stream.PeekLine() == null) + { + Logger.Log($"No content found in first .osu file of beatmap archive ({reader.Name} / {mapName})", LoggingTarget.Database); + return null; + } + beatmap = Decoder.GetDecoder(stream).Decode(stream); + } return new BeatmapSetInfo { diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index aad29f46fb..ca1bcc97fd 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -74,7 +74,7 @@ namespace osu.Game.Beatmaps.Formats } if (line == null) - throw new IOException("Unknown file format (null)"); + throw new IOException("Unknown file format (no content)"); var decoder = typedDecoders.Where(d => line.StartsWith(d.Key, StringComparison.InvariantCulture)).Select(d => d.Value).FirstOrDefault(); From ca287d093621d3997e1f8abb1b1b60e33a026e94 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 20 Jun 2022 16:38:11 +0900 Subject: [PATCH 096/803] Fix group deselected when table is recreated --- osu.Game/Screens/Edit/EditorTable.cs | 5 ++--- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorTable.cs b/osu.Game/Screens/Edit/EditorTable.cs index 8765dae384..a290cce708 100644 --- a/osu.Game/Screens/Edit/EditorTable.cs +++ b/osu.Game/Screens/Edit/EditorTable.cs @@ -97,7 +97,7 @@ namespace osu.Game.Screens.Edit [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) { - hoveredBackground.Colour = colourHover = colours.Background1; + colourHover = colours.Background1; colourSelected = colours.Colour3; } @@ -105,8 +105,7 @@ namespace osu.Game.Screens.Edit { base.LoadComplete(); - // Reduce flicker of rows when offset is being changed rapidly. - // Probably need to reconsider this. + updateState(); FinishTransforms(true); } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 88c738c1d2..6d46b429f0 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -44,6 +44,7 @@ namespace osu.Game.Screens.Edit.Timing { BackgroundFlow.Add(new RowBackground(group) { + Selected = group.Equals(selectedGroup?.Value), Action = () => { selectedGroup.Value = group; From 16281f4a4831cebda36a6bf669ed59ab99408b40 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 20 Jun 2022 16:52:01 +0900 Subject: [PATCH 097/803] Properly annotate method to allow null --- osu.Game/Beatmaps/ControlPoints/ControlPoint.cs | 2 +- osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs | 2 +- osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs | 2 +- osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs | 2 +- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index fd06c898ab..b91d609849 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -28,7 +28,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// An existing control point to compare with. /// Whether this is redundant when placed alongside . - public abstract bool IsRedundant(ControlPoint existing); + public abstract bool IsRedundant(ControlPoint? existing); /// /// Create an unbound copy of this control point. diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 6f624d20c9..c199d1da59 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -40,7 +40,7 @@ namespace osu.Game.Beatmaps.ControlPoints set => SliderVelocityBindable.Value = value; } - public override bool IsRedundant(ControlPoint existing) + public override bool IsRedundant(ControlPoint? existing) => existing is DifficultyControlPoint existingDifficulty && SliderVelocity == existingDifficulty.SliderVelocity; diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 365d024035..ead07b4eaa 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -67,7 +67,7 @@ namespace osu.Game.Beatmaps.ControlPoints set => KiaiModeBindable.Value = value; } - public override bool IsRedundant(ControlPoint existing) + public override bool IsRedundant(ControlPoint? existing) => !OmitFirstBarLine && existing is EffectControlPoint existingEffect && KiaiMode == existingEffect.KiaiMode diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index ab575dfeda..78dec67937 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -72,7 +72,7 @@ namespace osu.Game.Beatmaps.ControlPoints public virtual HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo) => hitSampleInfo.With(newBank: hitSampleInfo.Bank ?? SampleBank, newVolume: hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume); - public override bool IsRedundant(ControlPoint existing) + public override bool IsRedundant(ControlPoint? existing) => existing is SampleControlPoint existingSample && SampleBank == existingSample.SampleBank && SampleVolume == existingSample.SampleVolume; diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 87ef4e86d5..23d4d10fd8 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -69,7 +69,7 @@ namespace osu.Game.Beatmaps.ControlPoints public double BPM => 60000 / BeatLength; // Timing points are never redundant as they can change the time signature. - public override bool IsRedundant(ControlPoint existing) => false; + public override bool IsRedundant(ControlPoint? existing) => false; public override void CopyFrom(ControlPoint other) { diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index cc6a9126bd..a5e6ac0a1c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -213,7 +213,7 @@ namespace osu.Game.Beatmaps.Formats return baseInfo; } - public override bool IsRedundant(ControlPoint existing) + public override bool IsRedundant(ControlPoint? existing) => base.IsRedundant(existing) && existing is LegacySampleControlPoint existingSample && CustomSampleBank == existingSample.CustomSampleBank; From e0c82d11ab339009718b880c95697ef74ede594c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 20 Jun 2022 16:53:03 +0900 Subject: [PATCH 098/803] Convert == usages to ReferenceEquals --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 2 +- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 8 ++++---- .../Visual/UserInterface/TestSceneBeatSyncedContainer.cs | 4 ++-- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 2 +- osu.Game/Rulesets/Objects/HitObject.cs | 4 ++-- .../Timelines/Summary/Parts/ControlPointPart.cs | 2 +- .../Components/Timeline/TimelineControlPointDisplay.cs | 2 +- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 6 +++--- osu.Game/Screens/Edit/EditorClock.cs | 2 +- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 3 ++- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 2 +- osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs | 2 +- 12 files changed, 20 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 4589a8eca1..f03bcffdc8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -374,7 +374,7 @@ namespace osu.Game.Rulesets.Osu.Mods int i = 0; double currentTime = timingPoint.Time; - while (!definitelyBigger(currentTime, mapEndTime) && controlPointInfo.TimingPointAt(currentTime) == timingPoint) + while (!definitelyBigger(currentTime, mapEndTime) && ReferenceEquals(controlPointInfo.TimingPointAt(currentTime), timingPoint)) { beats.Add(Math.Floor(currentTime)); i++; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index cf1246ef07..e9bbf1e33d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -117,8 +117,8 @@ namespace osu.Game.Tests.Visual.Editing // After placement these must be non-default as defaults are read-only. AddAssert("Placed object has non-default control points", () => - EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT && - EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT); + !ReferenceEquals(EditorBeatmap.HitObjects[0].SampleControlPoint, SampleControlPoint.DEFAULT) && + !ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT)); ReloadEditorToSameBeatmap(); @@ -126,8 +126,8 @@ namespace osu.Game.Tests.Visual.Editing // After placement these must be non-default as defaults are read-only. AddAssert("Placed object still has non-default control points", () => - EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT && - EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT); + !ReferenceEquals(EditorBeatmap.HitObjects[0].SampleControlPoint, SampleControlPoint.DEFAULT) && + !ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT)); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 27c107a2db..6cf66963a5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -269,7 +269,7 @@ namespace osu.Game.Tests.Visual.UserInterface private TimingControlPoint getNextTimingPoint(TimingControlPoint current) { - if (timingPoints[^1] == current) + if (ReferenceEquals(timingPoints[^1], current)) return current; int index = timingPoints.IndexOf(current); // -1 means that this is a "default beat" @@ -281,7 +281,7 @@ namespace osu.Game.Tests.Visual.UserInterface { if (timingPoints.Count == 0) return 0; - if (timingPoints[^1] == current) + if (ReferenceEquals(timingPoints[^1], current)) { Debug.Assert(BeatSyncSource.Clock != null); diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 4146bbcc5e..45a935d165 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -127,7 +127,7 @@ namespace osu.Game.Graphics.Containers TimeSinceLastBeat = beatLength - TimeUntilNextBeat; - if (timingPoint == lastTimingPoint && beatIndex == lastBeat) + if (ReferenceEquals(timingPoint, lastTimingPoint) && beatIndex == lastBeat) return; // as this event is sometimes used for sound triggers where `BeginDelayedSequence` has no effect, avoid firing it if too far away from the beat. diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 30b8fe965a..d20e0616e5 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Objects if (legacyInfo != null) DifficultyControlPoint = (DifficultyControlPoint)legacyInfo.DifficultyPointAt(StartTime).DeepClone(); - else if (DifficultyControlPoint == DifficultyControlPoint.DEFAULT) + else if (ReferenceEquals(DifficultyControlPoint, DifficultyControlPoint.DEFAULT)) DifficultyControlPoint = new DifficultyControlPoint(); DifficultyControlPoint.Time = StartTime; @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Objects // This is done here after ApplyDefaultsToSelf as we may require custom defaults to be applied to have an accurate end time. if (legacyInfo != null) SampleControlPoint = (SampleControlPoint)legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency).DeepClone(); - else if (SampleControlPoint == SampleControlPoint.DEFAULT) + else if (ReferenceEquals(SampleControlPoint, SampleControlPoint.DEFAULT)) SampleControlPoint = new SampleControlPoint(); SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index 4e2c018330..ee82ded39d 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts case NotifyCollectionChangedAction.Remove: foreach (var group in args.OldItems.OfType()) { - var matching = Children.SingleOrDefault(gv => gv.Group == group); + var matching = Children.SingleOrDefault(gv => ReferenceEquals(gv.Group, group)); if (matching != null) matching.Expire(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs index 5d5681ea2b..544c3f8369 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline case NotifyCollectionChangedAction.Remove: foreach (var group in args.OldItems.OfType()) { - var matching = Children.SingleOrDefault(gv => gv.Group == group); + var matching = Children.SingleOrDefault(gv => ReferenceEquals(gv.Group, group)); matching?.Expire(); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 34abe81ae2..32ebc9c3c1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -205,7 +205,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline updateRepeats(repeats); } - if (difficultyControlPoint != Item.DifficultyControlPoint) + if (!ReferenceEquals(difficultyControlPoint, Item.DifficultyControlPoint)) { difficultyControlPoint = Item.DifficultyControlPoint; difficultyOverrideDisplay?.Expire(); @@ -220,7 +220,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - if (sampleControlPoint != Item.SampleControlPoint) + if (!ReferenceEquals(sampleControlPoint, Item.SampleControlPoint)) { sampleControlPoint = Item.SampleControlPoint; sampleOverrideDisplay?.Expire(); @@ -393,7 +393,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (e.CurrentState.Keyboard.ShiftPressed) { - if (hitObject.DifficultyControlPoint == DifficultyControlPoint.DEFAULT) + if (ReferenceEquals(hitObject.DifficultyControlPoint, DifficultyControlPoint.DEFAULT)) hitObject.DifficultyControlPoint = new DifficultyControlPoint(); double newVelocity = hitObject.DifficultyControlPoint.SliderVelocity * (repeatHitObject.Duration / proposedDuration); diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index c7df36be77..c3b29afd30 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -141,7 +141,7 @@ namespace osu.Game.Screens.Edit seekTime = timingPoint.Time + closestBeat * seekAmount; } - if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First()) + if (seekTime < timingPoint.Time && !ReferenceEquals(timingPoint, ControlPointInfo.TimingPoints.First())) seekTime = timingPoint.Time; SeekSmoothlyTo(seekTime); diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 6d46b429f0..b6e3e91c98 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -65,7 +65,8 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(group => { // TODO: This should scroll the selected row into view. - foreach (var b in BackgroundFlow) b.Selected = b.Item == group.NewValue; + foreach (var b in BackgroundFlow) + b.Selected = ReferenceEquals(b.Item, group.NewValue); }, true); } diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 60cb263a79..c33dcc6e91 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -222,7 +222,7 @@ namespace osu.Game.Screens.Edit.Timing // Try and create matching types from the currently selected control point. var selected = selectedGroup.Value; - if (selected != null && selected != group) + if (selected != null && !ReferenceEquals(selected, group)) { foreach (var controlPoint in selected.ControlPoints) { diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 67b7abb25b..1cdcfdf167 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Edit.Timing double? offsetChange = newStartTime - selectedGroupStartTime; var nextGroup = editorBeatmap.ControlPointInfo.TimingPoints - .SkipWhile(g => g != tcp) + .SkipWhile(g => !ReferenceEquals(g, tcp)) .Skip(1) .FirstOrDefault(); From b0b3ea42ccf6330125d94da23ef26110f2a3a61a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Jun 2022 10:44:14 +0200 Subject: [PATCH 099/803] Remove null default value in `ImportSkinTest` helper method --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index f3adb85006..8b7fcae1a9 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -357,7 +357,7 @@ namespace osu.Game.Tests.Skins.IO } } - private async Task> loadSkinIntoOsu(OsuGameBase osu, ImportTask import = null) + private async Task> loadSkinIntoOsu(OsuGameBase osu, ImportTask import) { var skinManager = osu.Dependencies.Get(); return await skinManager.Import(import); From ebcee21f6f50d640a00badab6013ea332d14338d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 17:52:42 +0900 Subject: [PATCH 100/803] Remove necessity to provide `BeatmapDifficultyCache` as a function to `ScoreManager` --- osu.Game/OsuGameBase.cs | 6 ++++-- osu.Game/Scoring/ScoreManager.cs | 12 +++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f0f364314c..a09588516d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -264,14 +264,16 @@ namespace osu.Game var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); + dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); + // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, () => difficultyCache, LocalConfig)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API)); dependencies.Cache(ScoreDownloader = new ScoreModelDownloader(ScoreManager, API)); - dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); + // Add after all the above cache operations as it depends on them. AddInternal(difficultyCache); dependencies.Cache(userCache = new UserLookupCache()); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 44aafbec74..027e25d3b3 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -27,16 +27,16 @@ namespace osu.Game.Scoring public class ScoreManager : ModelManager, IModelImporter { private readonly Scheduler scheduler; - private readonly Func difficulties; + private readonly BeatmapDifficultyCache difficultyCache; private readonly OsuConfigManager configManager; private readonly ScoreImporter scoreImporter; public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler, - Func difficulties = null, OsuConfigManager configManager = null) + BeatmapDifficultyCache difficultyCache = null, OsuConfigManager configManager = null) : base(storage, realm) { this.scheduler = scheduler; - this.difficulties = difficulties; + this.difficultyCache = difficultyCache; this.configManager = configManager; scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm) @@ -65,8 +65,6 @@ namespace osu.Game.Scoring /// The given ordered by decreasing total score. public async Task OrderByTotalScoreAsync(ScoreInfo[] scores, CancellationToken cancellationToken = default) { - var difficultyCache = difficulties?.Invoke(); - if (difficultyCache != null) { // Compute difficulties asynchronously first to prevent blocking via the GetTotalScore() call below. @@ -168,11 +166,11 @@ namespace osu.Game.Scoring return score.BeatmapInfo.MaxCombo.Value; #pragma warning restore CS0618 - if (difficulties == null) + if (difficultyCache == null) return null; // We can compute the max combo locally after the async beatmap difficulty computation. - var difficulty = await difficulties().GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false); + var difficulty = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false); return difficulty?.MaxCombo; } From dbae4c6f5a9f307bbb4aad65f4fa841b6523e097 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 18:21:37 +0900 Subject: [PATCH 101/803] `PostImport` -> `PresentImport` --- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++-- osu.Game/Database/IPostImports.cs | 2 +- osu.Game/Database/RealmArchiveModelImporter.cs | 6 +++--- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Scoring/ScoreManager.cs | 4 ++-- osu.Game/Skinning/SkinManager.cs | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 06a8ad5188..816808638f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -454,9 +454,9 @@ namespace osu.Game.Beatmaps #region Implementation of IPostImports - public Action>>? PostImport + public Action>>? PresentImport { - set => beatmapImporter.PostImport = value; + set => beatmapImporter.PresentImport = value; } #endregion diff --git a/osu.Game/Database/IPostImports.cs b/osu.Game/Database/IPostImports.cs index 83a211f6e6..d8493d5e18 100644 --- a/osu.Game/Database/IPostImports.cs +++ b/osu.Game/Database/IPostImports.cs @@ -12,6 +12,6 @@ namespace osu.Game.Database /// /// Fired when the user requests to view the resulting import. /// - public Action>>? PostImport { set; } + public Action>>? PresentImport { set; } } } diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index e6956d3184..6d5a10682f 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -65,7 +65,7 @@ namespace osu.Game.Database /// /// Fired when the user requests to view the resulting import. /// - public Action>>? PostImport { get; set; } + public Action>>? PresentImport { get; set; } /// /// Set an endpoint for notifications to be posted to. @@ -158,12 +158,12 @@ namespace osu.Game.Database ? $"Imported {imported.First().GetDisplayString()}!" : $"Imported {imported.Count} {HumanisedModelName}s!"; - if (imported.Count > 0 && PostImport != null) + if (imported.Count > 0 && PresentImport != null) { notification.CompletionText += " Click to view."; notification.CompletionClickAction = () => { - PostImport?.Invoke(imported); + PresentImport?.Invoke(imported); return true; }; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 97476358b1..4732a991a2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -711,13 +711,13 @@ namespace osu.Game SkinManager.PostNotification = n => Notifications.Post(n); BeatmapManager.PostNotification = n => Notifications.Post(n); - BeatmapManager.PostImport = items => PresentBeatmap(items.First().Value); + BeatmapManager.PresentImport = items => PresentBeatmap(items.First().Value); BeatmapDownloader.PostNotification = n => Notifications.Post(n); ScoreDownloader.PostNotification = n => Notifications.Post(n); ScoreManager.PostNotification = n => Notifications.Post(n); - ScoreManager.PostImport = items => PresentScore(items.First().Value); + ScoreManager.PresentImport = items => PresentScore(items.First().Value); // make config aware of how to lookup skins for on-screen display purposes. // if this becomes a more common thing, tracked settings should be reconsidered to allow local DI. diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 44aafbec74..139675711f 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -269,9 +269,9 @@ namespace osu.Game.Scoring #region Implementation of IPresentImports - public Action>> PostImport + public Action>> PresentImport { - set => scoreImporter.PostImport = value; + set => scoreImporter.PresentImport = value; } #endregion diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 38eff2f0d6..dc0197e613 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -259,9 +259,9 @@ namespace osu.Game.Skinning #region Implementation of IModelImporter - public Action>> PostImport + public Action>> PresentImport { - set => skinImporter.PostImport = value; + set => skinImporter.PresentImport = value; } public Task Import(params string[] paths) => skinImporter.Import(paths); From 45c5013f09d5467d8087e7bd24661cf929aee91d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 18:38:02 +0900 Subject: [PATCH 102/803] Remove default value of `RealmArchiveModelImporter.HandledExtensions` Not used anywhere and probably not wanted ever. --- osu.Game/Database/RealmArchiveModelImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 6d5a10682f..584dda7264 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -56,7 +56,7 @@ namespace osu.Game.Database /// private static readonly ThreadedTaskScheduler import_scheduler_batch = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(RealmArchiveModelImporter)); - public virtual IEnumerable HandledExtensions => new[] { @".zip" }; + public abstract IEnumerable HandledExtensions { get; } protected readonly RealmFileStore Files; From e732c5a2d750ed0ae6687eb3277d0fc44f8c8578 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 18:38:37 +0900 Subject: [PATCH 103/803] Add `PostImport` method matching `PreImport` --- osu.Game/Database/RealmArchiveModelImporter.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 584dda7264..27bfaf05be 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -338,6 +338,8 @@ namespace osu.Game.Database transaction.Commit(); } + PostImport(item, realm); + LogForModel(item, @"Import successfully completed!"); } catch (Exception e) @@ -473,6 +475,15 @@ namespace osu.Game.Database { } + /// + /// Perform any final actions after the import has been committed to the database. + /// + /// The model prepared for import. + /// The current realm context. + protected virtual void PostImport(TModel model, Realm realm) + { + } + /// /// Check whether an existing model already exists for a new import item. /// From ba394f283111e19ff274bae8cc4cc93a0cd46110 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 18:53:48 +0900 Subject: [PATCH 104/803] Remove `IPostsImports` interface (and move to `IModelImporter`) --- osu.Game/Database/IModelImporter.cs | 8 +++++++- osu.Game/Database/IPostImports.cs | 17 ----------------- 2 files changed, 7 insertions(+), 18 deletions(-) delete mode 100644 osu.Game/Database/IPostImports.cs diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index bf9f2f9c75..26342cb5fe 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.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.Threading.Tasks; using osu.Game.Overlays.Notifications; @@ -11,7 +12,7 @@ namespace osu.Game.Database /// A class which handles importing of associated models to the game store. /// /// The model type. - public interface IModelImporter : IPostNotifications, IPostImports, ICanAcceptFiles + public interface IModelImporter : IPostNotifications, ICanAcceptFiles where TModel : class, IHasGuidPrimaryKey { /// @@ -26,5 +27,10 @@ namespace osu.Game.Database /// A user displayable name for the model type associated with this manager. /// string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}"; + + /// + /// Fired when the user requests to view the resulting import. + /// + public Action>>? PresentImport { set; } } } diff --git a/osu.Game/Database/IPostImports.cs b/osu.Game/Database/IPostImports.cs deleted file mode 100644 index d8493d5e18..0000000000 --- a/osu.Game/Database/IPostImports.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 System; -using System.Collections.Generic; - -namespace osu.Game.Database -{ - public interface IPostImports - where TModel : class, IHasGuidPrimaryKey - { - /// - /// Fired when the user requests to view the resulting import. - /// - public Action>>? PresentImport { set; } - } -} From 58d6fa46453a0787d11ee1fcf02ff26c99ffb9a1 Mon Sep 17 00:00:00 2001 From: 63411 <62799417+molneya@users.noreply.github.com> Date: Mon, 20 Jun 2022 19:55:26 +0800 Subject: [PATCH 105/803] Reduce NoFail pp multiplier --- .../Difficulty/ManiaPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index dea02adfbf..216dfe6c47 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty double multiplier = 8.0; if (score.Mods.Any(m => m is ModNoFail)) - multiplier *= 0.9; + multiplier *= 0.75; if (score.Mods.Any(m => m is ModEasy)) multiplier *= 0.5; From 5a5cc523ce78421a32cb18ec84bb9e0c75e12275 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Mon, 20 Jun 2022 23:43:33 +0800 Subject: [PATCH 106/803] Let F to -1 temporary --- osu.Game/Scoring/ScoreRank.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Scoring/ScoreRank.cs b/osu.Game/Scoring/ScoreRank.cs index 9de8561b4a..dc90e417cd 100644 --- a/osu.Game/Scoring/ScoreRank.cs +++ b/osu.Game/Scoring/ScoreRank.cs @@ -11,6 +11,11 @@ namespace osu.Game.Scoring { public enum ScoreRank { + // TODO: reconsider changing later on + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.RankD))] + [Description(@"F")] + F = -1, + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.RankD))] [Description(@"D")] D, From 43ead5820a9db5c1d2403398509f94d7017c4713 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Tue, 21 Jun 2022 00:54:50 +0900 Subject: [PATCH 107/803] deal with test --- osu.Game/Screens/Play/FailOverlay.cs | 2 +- osu.Game/Screens/Play/Player.cs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index b09a7de631..1b9d81d33b 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -19,9 +19,9 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load(OsuColour colours) { - AddButton("Save replay and Quit", colours.Blue, () => SaveReplay?.Invoke()); AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); + AddButton("Save replay and Quit", colours.Blue, () => SaveReplay?.Invoke()); } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c404510487..d38c10c071 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1044,18 +1044,19 @@ namespace osu.Game.Screens.Play return base.OnExiting(e); } - // Don't know if prepareScoreForResults useful - private async void saveReplay() + private void saveReplay() { var scoreCopy = Score.DeepClone(); + try { - await ImportScore(scoreCopy).ConfigureAwait(false); + ImportScore(scoreCopy).ConfigureAwait(false); } catch (Exception ex) { Logger.Error(ex, @"Score import failed!"); } + PerformExit(true); } From 34f1c80b7c2dbe6125510f274b42f251be0fb8dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Jun 2022 20:04:21 +0200 Subject: [PATCH 108/803] Add and use `ILinkHandler` interface --- .../Graphics/Containers/LinkFlowContainer.cs | 7 +++-- .../Markdown/OsuMarkdownLinkText.cs | 5 ++-- osu.Game/Online/ILinkHandler.cs | 30 +++++++++++++++++++ osu.Game/OsuGame.cs | 3 +- 4 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Online/ILinkHandler.cs diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index a67e5ae66f..bf96695fd3 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Framework.Platform; +using osu.Game.Online; using osu.Game.Users; namespace osu.Game.Graphics.Containers @@ -25,7 +26,7 @@ namespace osu.Game.Graphics.Containers } [Resolved(CanBeNull = true)] - private OsuGame game { get; set; } + private ILinkHandler linkHandler { get; set; } [Resolved] private GameHost host { get; set; } @@ -81,8 +82,8 @@ namespace osu.Game.Graphics.Containers { if (action != null) action(); - else if (game != null) - game.HandleLink(link); + else if (linkHandler != null) + linkHandler.HandleLink(link); // fallback to handle cases where OsuGame is not available, ie. tournament client. else if (link.Action == LinkAction.External) host.OpenUrlExternally(link.Argument.ToString()); diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs index dca9e3de6e..3a16eb0a0f 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs @@ -8,6 +8,7 @@ using Markdig.Syntax.Inlines; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers.Markdown; +using osu.Game.Online; using osu.Game.Online.Chat; using osu.Game.Overlays; @@ -16,7 +17,7 @@ namespace osu.Game.Graphics.Containers.Markdown public class OsuMarkdownLinkText : MarkdownLinkText { [Resolved(canBeNull: true)] - private OsuGame game { get; set; } + private ILinkHandler linkHandler { get; set; } private readonly string text; private readonly string title; @@ -51,7 +52,7 @@ namespace osu.Game.Graphics.Containers.Markdown }; } - protected override void OnLinkPressed() => game?.HandleLink(Url); + protected override void OnLinkPressed() => linkHandler?.HandleLink(Url); private class OsuMarkdownLinkCompiler : DrawableLinkCompiler { diff --git a/osu.Game/Online/ILinkHandler.cs b/osu.Game/Online/ILinkHandler.cs new file mode 100644 index 0000000000..1b8fad4bd9 --- /dev/null +++ b/osu.Game/Online/ILinkHandler.cs @@ -0,0 +1,30 @@ +// 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.Online.Chat; + +namespace osu.Game.Online +{ + /// + /// Handle an arbitrary URL. Displays via in-game overlays where possible. + /// Methods can be called from a non-thread-safe non-game-loaded state. + /// + [Cached] + public interface ILinkHandler + { + /// + /// Handle an arbitrary URL. Displays via in-game overlays where possible. + /// This can be called from a non-thread-safe non-game-loaded state. + /// + /// The URL to load. + void HandleLink(string url); + + /// + /// Handle a specific . + /// This can be called from a non-thread-safe non-game-loaded state. + /// + /// The link to load. + void HandleLink(LinkDetails link); + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4732a991a2..f51a18b36f 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -39,6 +39,7 @@ using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; using osu.Game.Localisation; +using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Overlays; @@ -69,7 +70,7 @@ namespace osu.Game /// The full osu! experience. Builds on top of to add menus and binding logic /// for initial components that are generally retrieved via DI. /// - public class OsuGame : OsuGameBase, IKeyBindingHandler, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager + public class OsuGame : OsuGameBase, IKeyBindingHandler, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager, ILinkHandler { /// /// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications). From 92011124d146be3074b8a1df18e1b0a0f8f5cb35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Jun 2022 20:08:36 +0200 Subject: [PATCH 109/803] Implement IPC channel for `osu://` scheme links --- osu.Game/IPC/OsuSchemeLinkIPCChannel.cs | 49 +++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 osu.Game/IPC/OsuSchemeLinkIPCChannel.cs diff --git a/osu.Game/IPC/OsuSchemeLinkIPCChannel.cs b/osu.Game/IPC/OsuSchemeLinkIPCChannel.cs new file mode 100644 index 0000000000..33318e329c --- /dev/null +++ b/osu.Game/IPC/OsuSchemeLinkIPCChannel.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 System.Diagnostics; +using System.Threading.Tasks; +using osu.Framework.Platform; +using osu.Game.Online; + +namespace osu.Game.IPC +{ + public class OsuSchemeLinkIPCChannel : IpcChannel + { + private readonly ILinkHandler? linkHandler; + + public OsuSchemeLinkIPCChannel(IIpcHost host, ILinkHandler? linkHandler = null) + : base(host) + { + this.linkHandler = linkHandler; + + MessageReceived += msg => + { + Debug.Assert(linkHandler != null); + linkHandler.HandleLink(msg.Link); + return null; + }; + } + + public async Task HandleLinkAsync(string url) + { + if (linkHandler == null) + { + await SendMessageAsync(new OsuSchemeLinkMessage(url)).ConfigureAwait(false); + return; + } + + linkHandler.HandleLink(url); + } + } + + public class OsuSchemeLinkMessage + { + public string Link { get; } + + public OsuSchemeLinkMessage(string link) + { + Link = link; + } + } +} From 330af7ec7460c0cc49ff7a13f7597c18459650f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Jun 2022 21:21:16 +0200 Subject: [PATCH 110/803] Handle `osu://` scheme links via IPC in desktop game --- osu.Desktop/OsuGameDesktop.cs | 31 +++++++++++++-------- osu.Desktop/Program.cs | 42 ++++++++++++++++++++--------- osu.Game/IPC/IPCTimeoutException.cs | 15 +++++++++++ 3 files changed, 65 insertions(+), 23 deletions(-) create mode 100644 osu.Game/IPC/IPCTimeoutException.cs diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 6713136343..873be052d7 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; @@ -22,23 +20,26 @@ using osu.Game.Updater; using osu.Desktop.Windows; using osu.Framework.Threading; using osu.Game.IO; +using osu.Game.IPC; namespace osu.Desktop { internal class OsuGameDesktop : OsuGame { - public OsuGameDesktop(string[] args = null) + private OsuSchemeLinkIPCChannel? osuSchemeLinkIPCChannel; + + public OsuGameDesktop(string[]? args = null) : base(args) { } - public override StableStorage GetStorageForStableInstall() + public override StableStorage? GetStorageForStableInstall() { try { if (Host is DesktopGameHost desktopHost) { - string stablePath = getStableInstallPath(); + string? stablePath = getStableInstallPath(); if (!string.IsNullOrEmpty(stablePath)) return new StableStorage(stablePath, desktopHost); } @@ -51,11 +52,11 @@ namespace osu.Desktop return null; } - private string getStableInstallPath() + private string? getStableInstallPath() { static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs")) || File.Exists(Path.Combine(p, "osu!.cfg")); - string stableInstallPath; + string? stableInstallPath; if (OperatingSystem.IsWindows()) { @@ -83,15 +84,15 @@ namespace osu.Desktop } [SupportedOSPlatform("windows")] - private string getStableInstallPathFromRegistry() + private string? getStableInstallPathFromRegistry() { - using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) + using (RegistryKey? key = Registry.ClassesRoot.OpenSubKey("osu")) return key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", ""); } protected override UpdateManager CreateUpdateManager() { - string packageManaged = Environment.GetEnvironmentVariable("OSU_EXTERNAL_UPDATE_PROVIDER"); + string? packageManaged = Environment.GetEnvironmentVariable("OSU_EXTERNAL_UPDATE_PROVIDER"); if (!string.IsNullOrEmpty(packageManaged)) return new NoActionUpdateManager(); @@ -118,6 +119,8 @@ namespace osu.Desktop LoadComponentAsync(new GameplayWinKeyBlocker(), Add); LoadComponentAsync(new ElevatedPrivilegesChecker(), Add); + + osuSchemeLinkIPCChannel = new OsuSchemeLinkIPCChannel(Host, this); } public override void SetHost(GameHost host) @@ -135,7 +138,7 @@ namespace osu.Desktop } private readonly List importableFiles = new List(); - private ScheduledDelegate importSchedule; + private ScheduledDelegate? importSchedule; private void fileDrop(string[] filePaths) { @@ -168,5 +171,11 @@ namespace osu.Desktop Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning); } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + osuSchemeLinkIPCChannel?.Dispose(); + } } } diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 4ba9cc6a90..7a5809e915 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -11,6 +11,7 @@ using osu.Framework; using osu.Framework.Development; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Game; using osu.Game.IPC; using osu.Game.Tournament; using Squirrel; @@ -65,19 +66,8 @@ namespace osu.Desktop { if (!host.IsPrimaryInstance) { - if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args - { - var importer = new ArchiveImportIPCChannel(host); - - foreach (string file in args) - { - Console.WriteLine(@"Importing {0}", file); - if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000)) - throw new TimeoutException(@"IPC took too long to send"); - } - + if (trySendIPCMessage(host, cwd, args)) return; - } // we want to allow multiple instances to be started when in debug. if (!DebugUtils.IsDebugBuild) @@ -108,6 +98,34 @@ namespace osu.Desktop } } + private static bool trySendIPCMessage(IIpcHost host, string cwd, string[] args) + { + if (args.Length == 1 && args[0].StartsWith(OsuGameBase.OSU_PROTOCOL, StringComparison.Ordinal)) + { + var osuSchemeLinkHandler = new OsuSchemeLinkIPCChannel(host); + if (!osuSchemeLinkHandler.HandleLinkAsync(args[0]).Wait(3000)) + throw new IPCTimeoutException(osuSchemeLinkHandler.GetType()); + + return true; + } + + if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args + { + var importer = new ArchiveImportIPCChannel(host); + + foreach (string file in args) + { + Console.WriteLine(@"Importing {0}", file); + if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000)) + throw new IPCTimeoutException(importer.GetType()); + } + + return true; + } + + return false; + } + [SupportedOSPlatform("windows")] private static void setupSquirrel() { diff --git a/osu.Game/IPC/IPCTimeoutException.cs b/osu.Game/IPC/IPCTimeoutException.cs new file mode 100644 index 0000000000..d820184468 --- /dev/null +++ b/osu.Game/IPC/IPCTimeoutException.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 System; + +namespace osu.Game.IPC +{ + public class IPCTimeoutException : TimeoutException + { + public IPCTimeoutException(Type channelType) + : base($@"IPC took too long to send message via channel {channelType}") + { + } + } +} From 21d60231b6eecb087d5d10873e80aaf208115c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Jun 2022 22:10:25 +0200 Subject: [PATCH 111/803] Add limited test coverage of `osu://` scheme link IPC flow --- .../TestSceneInterProcessCommunication.cs | 86 +++++++++++++++++++ osu.Game/Tests/Visual/OsuGameTestScene.cs | 2 +- 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs new file mode 100644 index 0000000000..f5af077696 --- /dev/null +++ b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs @@ -0,0 +1,86 @@ +// 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; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.IPC; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.Navigation +{ + [TestFixture] + [Ignore("This test cannot be run headless, as it requires the game host running the nested game to have IPC bound.")] + public class TestSceneInterProcessCommunication : OsuGameTestScene + { + private HeadlessGameHost ipcSenderHost = null!; + + private OsuSchemeLinkIPCChannel osuSchemeLinkIPCReceiver = null!; + private OsuSchemeLinkIPCChannel osuSchemeLinkIPCSender = null!; + + private const int requested_beatmap_set_id = 1; + + [Resolved] + private GameHost gameHost { get; set; } = null!; + + public override void SetUpSteps() + { + base.SetUpSteps(); + AddStep("set up request handling", () => + { + ((DummyAPIAccess)API).HandleRequest = request => + { + switch (request) + { + case GetBeatmapSetRequest gbr: + + var apiBeatmapSet = CreateAPIBeatmapSet(); + apiBeatmapSet.OnlineID = requested_beatmap_set_id; + apiBeatmapSet.Beatmaps = apiBeatmapSet.Beatmaps.Append(new APIBeatmap + { + DifficultyName = "Target difficulty", + OnlineID = 75, + }).ToArray(); + gbr.TriggerSuccess(apiBeatmapSet); + return true; + } + + return false; + }; + }); + AddStep("create IPC receiver channel", () => osuSchemeLinkIPCReceiver = new OsuSchemeLinkIPCChannel(gameHost, Game)); + AddStep("create IPC sender channel", () => + { + ipcSenderHost = new HeadlessGameHost(gameHost.Name, new HostOptions { BindIPC = true }); + osuSchemeLinkIPCSender = new OsuSchemeLinkIPCChannel(ipcSenderHost); + }); + } + + [Test] + public void TestOsuSchemeLinkIPCChannel() + { + AddStep("open beatmap via IPC", () => osuSchemeLinkIPCSender.HandleLinkAsync($@"osu://s/{requested_beatmap_set_id}").WaitSafely()); + AddUntilStep("beatmap overlay displayed", () => Game.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Visible); + AddUntilStep("beatmap overlay showing content", () => Game.ChildrenOfType().FirstOrDefault()?.Header.BeatmapSet.Value.OnlineID == requested_beatmap_set_id); + } + + public override void TearDownSteps() + { + AddStep("dispose IPC receiver", () => osuSchemeLinkIPCReceiver.Dispose()); + AddStep("dispose IPC sender", () => + { + osuSchemeLinkIPCReceiver.Dispose(); + ipcSenderHost.Dispose(); + }); + base.TearDownSteps(); + } + } +} diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index df0bdb0a50..b9f6183869 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual } [TearDownSteps] - public void TearDownSteps() + public virtual void TearDownSteps() { if (DebugUtils.IsNUnitRunning && Game != null) { From 345ae7bbc3ec287f60d5a38fb2202d674aad98b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 00:46:52 +0200 Subject: [PATCH 112/803] Fix build errors after implicit NRT enable --- osu.Android/AndroidMouseSettings.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android/AndroidMouseSettings.cs b/osu.Android/AndroidMouseSettings.cs index 7dff929cd4..54b787fd17 100644 --- a/osu.Android/AndroidMouseSettings.cs +++ b/osu.Android/AndroidMouseSettings.cs @@ -20,11 +20,11 @@ namespace osu.Android protected override LocalisableString Header => MouseSettingsStrings.Mouse; - private Bindable handlerSensitivity; + private Bindable handlerSensitivity = null!; - private Bindable localSensitivity; + private Bindable localSensitivity = null!; - private Bindable relativeMode; + private Bindable relativeMode = null!; public AndroidMouseSettings(AndroidMouseHandler mouseHandler) { From 6a2646168334d55582a380860aaa6e61270e6f45 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 12:05:28 +0900 Subject: [PATCH 113/803] Compare by reference in ControlPoint.Equals() --- osu.Game/Beatmaps/ControlPoints/ControlPoint.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index b91d609849..56a432aec4 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -52,8 +52,12 @@ namespace osu.Game.Beatmaps.ControlPoints && Equals(otherControlPoint); public virtual bool Equals(ControlPoint? other) - => other != null - && Time == other.Time; + { + if (ReferenceEquals(other, null)) return false; + if (ReferenceEquals(other, this)) return true; + + return Time == other.Time; + } // ReSharper disable once NonReadonlyMemberInGetHashCode public override int GetHashCode() => Time.GetHashCode(); From 9763a583924d0a68d3c70e07a144d480f8bf9b01 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 12:05:52 +0900 Subject: [PATCH 114/803] Change to use ReferenceEquals --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index b6e3e91c98..b923957273 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Timing { BackgroundFlow.Add(new RowBackground(group) { - Selected = group.Equals(selectedGroup?.Value), + Selected = ReferenceEquals(group, selectedGroup?.Value), Action = () => { selectedGroup.Value = group; From 93ce6fc98149a28ecc45de1a6dfba7be38fc6ec7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 12:11:44 +0900 Subject: [PATCH 115/803] Remove redundant diff processing --- osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index 01bc01bf5b..b4647c2b64 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -41,17 +41,12 @@ namespace osu.Game.Screens.Edit editorBeatmap.BeginChange(); processHitObjects(result, () => newBeatmap ??= readBeatmap(newState)); - processTimingPoints(result, () => newBeatmap ??= readBeatmap(newState)); + processTimingPoints(() => newBeatmap ??= readBeatmap(newState)); editorBeatmap.EndChange(); } - private void processTimingPoints(DiffResult result, Func getNewBeatmap) + private void processTimingPoints(Func getNewBeatmap) { - findChangedIndices(result, LegacyDecoder.Section.TimingPoints, out var removedIndices, out var addedIndices); - - if (removedIndices.Count == 0 && addedIndices.Count == 0) - return; - ControlPointInfo newControlPoints = EditorBeatmap.ConvertControlPoints(getNewBeatmap().ControlPointInfo); // Remove all groups from the current beatmap which don't have a corresponding equal group in the new beatmap. From b3e5642dfdd7298bebfb51d0a9b17211c940d80c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 12:23:41 +0900 Subject: [PATCH 116/803] Use WaitingOnFrames instead --- osu.Game/Screens/Play/SpectatorPlayer.cs | 26 +++++++++++++----------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 72a7d3c8a1..797787b194 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -45,6 +45,20 @@ namespace osu.Game.Screens.Play }); } + protected override void LoadComplete() + { + base.LoadComplete(); + + DrawableRuleset.FrameStableClock.WaitingOnFrames.BindValueChanged(waiting => + { + if (GameplayClockContainer is MasterGameplayClockContainer master) + { + if (master.UserPlaybackRate.Value > 1 && waiting.NewValue) + master.UserPlaybackRate.Value = 1; + } + }, true); + } + protected override void StartGameplay() { base.StartGameplay(); @@ -83,18 +97,6 @@ namespace osu.Game.Screens.Play SetGameplayStartTime(score.Replay.Frames[0].Time); } - protected override void Update() - { - base.Update(); - - if (HUDOverlay.PlayerSettingsOverlay.PlaybackSettings.UserPlaybackRate.Value > 1 - && score.Replay.Frames.Count > 0 - && DrawableRuleset.FrameStableClock.CurrentTime >= score.Replay.Frames[^1].Time) - { - HUDOverlay.PlayerSettingsOverlay.PlaybackSettings.UserPlaybackRate.Value = 1; - } - } - protected override Score CreateScore(IBeatmap beatmap) => score; protected override ResultsScreen CreateResults(ScoreInfo score) From 046b848bcd1c37a18b8ddf2141642a28668f5a6e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 12:53:06 +0900 Subject: [PATCH 117/803] Split group selection to separate method --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index b923957273..b5c162ab11 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -44,7 +44,6 @@ namespace osu.Game.Screens.Edit.Timing { BackgroundFlow.Add(new RowBackground(group) { - Selected = ReferenceEquals(group, selectedGroup?.Value), Action = () => { selectedGroup.Value = group; @@ -55,6 +54,8 @@ namespace osu.Game.Screens.Edit.Timing Columns = createHeaders(); Content = value.Select(createContent).ToArray().ToRectangular(); + + updateSelectedGroup(); } } @@ -65,11 +66,17 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(group => { // TODO: This should scroll the selected row into view. - foreach (var b in BackgroundFlow) - b.Selected = ReferenceEquals(b.Item, group.NewValue); + updateSelectedGroup(); }, true); } + private void updateSelectedGroup() + { + // TODO: This should scroll the selected row into view. + foreach (var b in BackgroundFlow) + b.Selected = ReferenceEquals(b.Item, selectedGroup?.Value); + } + private TableColumn[] createHeaders() { var columns = new List From c61e90d7685a9709a74ebcf077b99f66da09a6a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 13:07:50 +0900 Subject: [PATCH 118/803] Change editor seek-while-playing modifier to be BPM agnostic --- osu.Game/Screens/Edit/Editor.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d0d4c0ba1e..7a9dae33b4 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -798,10 +798,11 @@ namespace osu.Game.Screens.Edit if (trackPlaying) { - // generally users are not looking to perform tiny seeks when the track is playing, - // so seeks should always be by one full beat, bypassing the beatDivisor. + // generally users are not looking to perform tiny seeks when the track is playing. // this multiplication undoes the division that will be applied in the underlying seek operation. - amount *= beatDivisor.Value; + // scale by BPM to keep the seek amount constant across all BPMs. + var timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(clock.CurrentTimeAccurate); + amount *= beatDivisor.Value * (timingPoint.BPM / 120); } if (direction < 1) From 10efb4bc6c0976866261c584aa10885162727bca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 14:08:30 +0900 Subject: [PATCH 119/803] Fix incorrect disposal in teardown steps --- .../Visual/Navigation/TestSceneInterProcessCommunication.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs index f5af077696..1c2b1fe37d 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("dispose IPC receiver", () => osuSchemeLinkIPCReceiver.Dispose()); AddStep("dispose IPC sender", () => { - osuSchemeLinkIPCReceiver.Dispose(); + osuSchemeLinkIPCSender.Dispose(); ipcSenderHost.Dispose(); }); base.TearDownSteps(); From 88f450e1d0b3e0beb982f74cb1fa4936694098fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 14:40:21 +0900 Subject: [PATCH 120/803] Remove accidental `nullable enable` spec --- .../Screens/Utility/SampleComponents/LatencyCursorContainer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Utility/SampleComponents/LatencyCursorContainer.cs b/osu.Game/Screens/Utility/SampleComponents/LatencyCursorContainer.cs index 656aa6a8b3..aaf837eb60 100644 --- a/osu.Game/Screens/Utility/SampleComponents/LatencyCursorContainer.cs +++ b/osu.Game/Screens/Utility/SampleComponents/LatencyCursorContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable enable - using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; From 42701757c340037116441a31b80c2aff9ff2a866 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 14:52:57 +0900 Subject: [PATCH 121/803] Fix latch reset logic --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 33325951e5..cdfdc096d4 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; @@ -219,6 +220,8 @@ namespace osu.Game.Screens.Edit.Timing private readonly BindableInt interpolatedBpm = new BindableInt(); + private ScheduledDelegate latchDelegate; + protected override void LoadComplete() { base.LoadComplete(); @@ -266,12 +269,9 @@ namespace osu.Game.Screens.Edit.Timing using (swing.BeginDelayedSequence(350)) { swing.RotateTo(0, 1000, Easing.OutQuint); - swing.Delay(250).Schedule(() => - { - // prevent playing latch sound if metronome has started back up again - if (!isSwinging) - latch?.Play(); - }); + + using (swing.BeginDelayedSequence(250)) + latchDelegate = Schedule(() => latch?.Play()); } } } @@ -287,6 +287,9 @@ namespace osu.Game.Screens.Edit.Timing isSwinging = true; + latchDelegate?.Cancel(); + latchDelegate = null; + float currentAngle = swing.Rotation; float targetAngle = currentAngle > 0 ? -angle : angle; From 7b46d383186eb28e3bfd43cbfd2971fd37cb69a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 15:02:43 +0900 Subject: [PATCH 122/803] Move colour fade back to where it was --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 5a6a478ca1..4aed7527af 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -266,7 +266,6 @@ namespace osu.Game.Screens.Edit.Timing if (BeatSyncSource.Clock?.IsRunning != true && isSwinging) { swing.ClearTransforms(true); - stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); isSwinging = false; @@ -284,6 +283,7 @@ namespace osu.Game.Screens.Edit.Timing using (swing.BeginDelayedSequence(350)) { swing.RotateTo(0, 1000, Easing.OutQuint); + stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); using (swing.BeginDelayedSequence(250)) latchDelegate = Schedule(() => latch?.Play()); From 16a9e1881503dbb10f8d4cf885c76ec1a46ef7b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 15:02:53 +0900 Subject: [PATCH 123/803] Adjust timing of latch to be more in sync with visuals --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 4aed7527af..62580ac527 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -280,12 +280,12 @@ namespace osu.Game.Screens.Edit.Timing return; } - using (swing.BeginDelayedSequence(350)) + using (BeginDelayedSequence(350)) { swing.RotateTo(0, 1000, Easing.OutQuint); stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); - using (swing.BeginDelayedSequence(250)) + using (BeginDelayedSequence(380)) latchDelegate = Schedule(() => latch?.Play()); } } From 82c4d855d493f6a36cba8b2c7436a1ecf92c059e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 15:05:17 +0900 Subject: [PATCH 124/803] Rename sample variable to be easier to discern from other fields --- .../Screens/Edit/Timing/MetronomeDisplay.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 62580ac527..22a15a291e 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -37,12 +37,12 @@ namespace osu.Game.Screens.Edit.Timing private IAdjustableClock metronomeClock; - private Sample tick; - private Sample tickDownbeat; - private Sample latch; + private Sample sampleTick; + private Sample sampleTickDownbeat; + private Sample sampleLatch; [CanBeNull] - private ScheduledDelegate clunkDelegate; + private ScheduledDelegate tickPlaybackDelegate; [Resolved] private OverlayColourProvider overlayColourProvider { get; set; } @@ -57,9 +57,9 @@ namespace osu.Game.Screens.Edit.Timing [BackgroundDependencyLoader] private void load(AudioManager audio) { - tick = audio.Samples.Get(@"UI/metronome-tick"); - tickDownbeat = audio.Samples.Get(@"UI/metronome-tick-downbeat"); - latch = audio.Samples.Get(@"UI/metronome-latch"); + sampleTick = audio.Samples.Get(@"UI/metronome-tick"); + sampleTickDownbeat = audio.Samples.Get(@"UI/metronome-tick-downbeat"); + sampleLatch = audio.Samples.Get(@"UI/metronome-latch"); const float taper = 25; const float swing_vertical_offset = -23; @@ -269,14 +269,14 @@ namespace osu.Game.Screens.Edit.Timing isSwinging = false; - clunkDelegate?.Cancel(); - clunkDelegate = null; + tickPlaybackDelegate?.Cancel(); + tickPlaybackDelegate = null; // instantly latch if pendulum arm is close enough to center (to prevent awkward delayed playback of latch sound) if (Precision.AlmostEquals(swing.Rotation, 0, 1)) { swing.RotateTo(0); - latch?.Play(); + sampleLatch?.Play(); return; } @@ -286,7 +286,7 @@ namespace osu.Game.Screens.Edit.Timing stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); using (BeginDelayedSequence(380)) - latchDelegate = Schedule(() => latch?.Play()); + latchDelegate = Schedule(() => sampleLatch?.Play()); } } } @@ -316,12 +316,12 @@ namespace osu.Game.Screens.Edit.Timing { stick.FlashColour(overlayColourProvider.Content1, beatLength, Easing.OutQuint); - clunkDelegate = Schedule(() => + tickPlaybackDelegate = Schedule(() => { if (!EnableClicking) return; - var channel = beatIndex % timingPoint.TimeSignature.Numerator == 0 ? tickDownbeat?.GetChannel() : tick?.GetChannel(); + var channel = beatIndex % timingPoint.TimeSignature.Numerator == 0 ? sampleTickDownbeat?.GetChannel() : sampleTick?.GetChannel(); if (channel == null) return; From a40ad6f784aa5b16722017176e2c221b226b4908 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 15:07:35 +0900 Subject: [PATCH 125/803] Add slight transform when resetting arm from almost-zero --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 22a15a291e..37fc38404f 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -275,7 +275,7 @@ namespace osu.Game.Screens.Edit.Timing // instantly latch if pendulum arm is close enough to center (to prevent awkward delayed playback of latch sound) if (Precision.AlmostEquals(swing.Rotation, 0, 1)) { - swing.RotateTo(0); + swing.RotateTo(0, 60, Easing.OutQuint); sampleLatch?.Play(); return; } From 0f6f000188680ec7a375b3b244d7abe8557376ef Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 21 Jun 2022 09:13:44 +0100 Subject: [PATCH 126/803] Remove difficulty spike nerf --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 14d13ec785..71dbc026f2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double skillMultiplier => 0.05; private double strainDecayBase => 0.15; protected override double DecayWeight => 1.0; + protected override int ReducedSectionCount => -1; private double currentStrain; From f74b4ac2771edbeee3eebae2148db8a0f8a18508 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 17:15:25 +0900 Subject: [PATCH 127/803] Fix blocking overhead when calling `WriteAsync` --- osu.Game/Database/RealmAccess.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 865a8b5021..d02f8fe6b2 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -385,11 +385,22 @@ namespace osu.Game.Database /// Write changes to realm asynchronously, guaranteeing order of execution. /// /// The work to run. - public async Task WriteAsync(Action action) + public Task WriteAsync(Action action) { - total_writes_async.Value++; - using (var realm = getRealmInstance()) - await realm.WriteAsync(() => action(realm)); + // Regardless of calling Realm.GetInstance of Realm.GetInstanceAsync, there is a blocking overhead on retrieval. + // Adding a forced Task.Run resolves this. + + return Task.Run(async () => + { + total_writes_async.Value++; + + // Not attempting to use Realm.GetInstanceAsync as there's seemingly no benefit to us (for now) and it adds complexity due to locking + // concerns in getRealmInstance(). On a quick check, it looks to be more suited to cases where realm is connecting to an online sync + // server, which we don't use. May want to report upstream or revisit in the future. + using (var realm = getRealmInstance()) + // ReSharper disable once AccessToDisposedClosure (WriteAsync should be marked as [InstantHandle]). + await realm.WriteAsync(() => action(realm)); + }); } /// From 630bd244d5af115226bac1a25d3e245c193ea571 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 21 Jun 2022 10:01:11 +0100 Subject: [PATCH 128/803] Inherit StrainSkill instead --- .../Difficulty/Skills/Flashlight.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 71dbc026f2..f64c218c65 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -6,6 +6,7 @@ using System; using System.Linq; using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Evaluators; using osu.Game.Rulesets.Osu.Mods; @@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// /// Represents the skill required to memorise and hit every object in a map with the Flashlight mod enabled. /// - public class Flashlight : OsuStrainSkill + public class Flashlight : StrainSkill { private readonly bool hasHiddenMod; @@ -27,8 +28,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double skillMultiplier => 0.05; private double strainDecayBase => 0.15; - protected override double DecayWeight => 1.0; - protected override int ReducedSectionCount => -1; private double currentStrain; @@ -43,5 +42,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return currentStrain; } + + public override double DifficultyValue() + { + double difficulty = GetCurrentStrainPeaks().Sum(); + + // 1.06 comes from the default DifficultyMultiplier field in OsuStrainSkill, + // and it's added here to keep values the same after Flashlight was converted from OsuStrainSkill. + return difficulty * 1.06; + } } } From 043aa82a3195a9724256332e82b90c6d139493e3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 18:09:00 +0900 Subject: [PATCH 129/803] Add test --- .../TestSceneSliderFollowCircleInput.cs | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs new file mode 100644 index 0000000000..7a6e19575e --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs @@ -0,0 +1,120 @@ +// 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.Screens; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Replays; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [HeadlessTest] + public class TestSceneSliderFollowCircleInput : RateAdjustedBeatmapTestScene + { + private List? judgementResults; + private ScoreAccessibleReplayPlayer? currentPlayer; + + [Test] + public void TestMaximumDistanceTrackingWithoutMovement( + [Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)] + float circleSize, + [Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)] + double velocity) + { + const double time_slider_start = 1000; + + float circleRadius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (circleSize - 5) / 5) / 2; + float followCircleRadius = circleRadius * 1.2f; + + performTest(new Beatmap + { + HitObjects = + { + new Slider + { + StartTime = time_slider_start, + Position = new Vector2(0, 0), + DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = velocity }, + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(followCircleRadius, 0), + }, followCircleRadius), + }, + }, + BeatmapInfo = + { + Difficulty = new BeatmapDifficulty + { + CircleSize = circleSize, + SliderTickRate = 1 + }, + Ruleset = new OsuRuleset().RulesetInfo + }, + }, new List + { + new OsuReplayFrame { Position = new Vector2(-circleRadius + 1, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start }, + }); + + AddAssert("Tracking kept", assertMaxJudge); + } + + private bool assertMaxJudge() => judgementResults?.Any() == true && judgementResults.All(t => t.Type == t.Judgement.MaxResult); + + private void performTest(Beatmap beatmap, List frames) + { + AddStep("load player", () => + { + Beatmap.Value = CreateWorkingBeatmap(beatmap); + + var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); + + p.OnLoadComplete += _ => + { + p.ScoreProcessor.NewJudgement += result => + { + if (currentPlayer == p) judgementResults?.Add(result); + }; + }; + + LoadScreen(currentPlayer = p); + judgementResults = new List(); + }); + + AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); + AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); + AddUntilStep("Wait for completion", () => currentPlayer?.ScoreProcessor.HasCompleted.Value == true); + } + + private class ScoreAccessibleReplayPlayer : ReplayPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + protected override bool PauseOnFocusLost => false; + + public ScoreAccessibleReplayPlayer(Score score) + : base(score, new PlayerConfiguration + { + AllowPause = false, + ShowResults = false, + }) + { + } + } + } +} From ede5ca31a1a40bda61922301f625997ca687ae48 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 18:09:23 +0900 Subject: [PATCH 130/803] Always track final follow circle size --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 7 ------ .../Skinning/Default/SliderBall.cs | 25 ++++++++----------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 8a15d730cd..00009f4c3d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -30,9 +30,6 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Apply classic note lock", "Applies note lock to the full hit window.")] public Bindable ClassicNoteLock { get; } = new BindableBool(true); - [SettingSource("Use fixed slider follow circle hit area", "Makes the slider follow circle track its final size at all times.")] - public Bindable FixedFollowCircleHitArea { get; } = new BindableBool(true); - [SettingSource("Always play a slider's tail sample", "Always plays a slider's tail sample regardless of whether it was hit or not.")] public Bindable AlwaysPlayTailSample { get; } = new BindableBool(true); @@ -62,10 +59,6 @@ namespace osu.Game.Rulesets.Osu.Mods { switch (obj) { - case DrawableSlider slider: - slider.Ball.InputTracksVisualSize = !FixedFollowCircleHitArea.Value; - break; - case DrawableSliderHead head: head.TrackFollowCircle = !NoSliderHeadMovement.Value; break; diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index d2ea8f1660..389e9343e7 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -34,13 +34,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default set => ball.Colour = value; } - /// - /// Whether to track accurately to the visual size of this . - /// If false, tracking will be performed at the final scale at all times. - /// - public bool InputTracksVisualSize = true; - private readonly Drawable followCircle; + private readonly Drawable fullSizeFollowCircle; private readonly DrawableSlider drawableSlider; private readonly Drawable ball; @@ -62,6 +57,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default Alpha = 0, Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()), }, + fullSizeFollowCircle = new CircularContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true + }, ball = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBall), _ => new DefaultSliderBall()) { Anchor = Anchor.Centre, @@ -104,14 +106,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default tracking = value; - if (InputTracksVisualSize) - followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint); - else - { - // We need to always be tracking the final size, at both endpoints. For now, this is achieved by removing the scale duration. - followCircle.ScaleTo(tracking ? 2.4f : 1f); - } + fullSizeFollowCircle.Scale = new Vector2(tracking ? 2.4f : 1f); + followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint); followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint); } } @@ -170,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default // in valid time range Time.Current >= drawableSlider.HitObject.StartTime && Time.Current < drawableSlider.HitObject.EndTime && // in valid position range - lastScreenSpaceMousePosition.HasValue && followCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && + lastScreenSpaceMousePosition.HasValue && fullSizeFollowCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && // valid action (actions?.Any(isValidTrackingAction) ?? false); From 2e3d8d7e5d347a45e6cf97476beb7444f82dd1fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 18:13:14 +0900 Subject: [PATCH 131/803] 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 c82365d750..caea787e22 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 d820be4bdc..652dc2740f 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 a8c59d676d..0c3c38f9f0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From a7de43ade6459fe63d85f75d40cb7ea08b7d7e81 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 19:31:44 +0900 Subject: [PATCH 132/803] Add attribute ID --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 72c9608697..b24ff54b8a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -101,6 +101,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty yield return (ATTRIB_ID_FLASHLIGHT, FlashlightDifficulty); yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor); + yield return (ATTRIB_ID_SPEED_NOTE_COUNT, SpeedNoteCount); } public override void FromDatabaseAttributes(IReadOnlyDictionary values) @@ -115,6 +116,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty StarRating = values[ATTRIB_ID_DIFFICULTY]; FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT); SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR]; + SpeedNoteCount = values[ATTRIB_ID_SPEED_NOTE_COUNT]; } #region Newtonsoft.Json implicit ShouldSerialize() methods diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index e0844a5c8a..90c8f94ed0 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -26,6 +26,7 @@ namespace osu.Game.Rulesets.Difficulty protected const int ATTRIB_ID_SCORE_MULTIPLIER = 15; protected const int ATTRIB_ID_FLASHLIGHT = 17; protected const int ATTRIB_ID_SLIDER_FACTOR = 19; + protected const int ATTRIB_ID_SPEED_NOTE_COUNT = 21; /// /// The mods which were applied to the beatmap. From 73124d2b1fb09523ac8b16ea1d86c6cd1ef3cb1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 12:49:01 +0200 Subject: [PATCH 133/803] Encapsulate mod hotkey selection logic in strategy pattern --- .../UserInterface/TestSceneModColumn.cs | 2 +- .../Overlays/Mods/Input/IModHotkeyHandler.cs | 22 +++++++ .../Overlays/Mods/Input/ModHotkeyHandler.cs | 30 ++++++++++ .../Mods/Input/NoopModHotkeyHandler.cs | 17 ++++++ .../Mods/Input/SequentialModHotkeyHandler.cs | 58 +++++++++++++++++++ osu.Game/Overlays/Mods/ModColumn.cs | 21 +++---- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 13 ++--- .../Overlays/Mods/UserModSelectOverlay.cs | 10 +--- .../OnlinePlay/FreeModSelectOverlay.cs | 3 +- 9 files changed, 145 insertions(+), 31 deletions(-) create mode 100644 osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs create mode 100644 osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs create mode 100644 osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs create mode 100644 osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index 50817bf804..f98f39d445 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -139,7 +139,7 @@ namespace osu.Game.Tests.Visual.UserInterface { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(30), - Child = column = new ModColumn(ModType.DifficultyReduction, true, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }) + Child = column = new ModColumn(ModType.DifficultyReduction, true) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs new file mode 100644 index 0000000000..aec16ff764 --- /dev/null +++ b/osu.Game/Overlays/Mods/Input/IModHotkeyHandler.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 System.Collections.Generic; +using osuTK.Input; + +namespace osu.Game.Overlays.Mods.Input +{ + /// + /// Encapsulates strategies of handling mod hotkeys on the . + /// + public interface IModHotkeyHandler + { + /// + /// Attempt to handle a press of the supplied as a selection of one of the mods in . + /// + /// The key that was pressed by the user. + /// The list of currently available mods. + /// Whether the was handled as a mod selection/deselection. + bool HandleHotkeyPressed(Key hotkey, IEnumerable availableMods); + } +} diff --git a/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs new file mode 100644 index 0000000000..22de557979 --- /dev/null +++ b/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs @@ -0,0 +1,30 @@ +// 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.Mods; + +namespace osu.Game.Overlays.Mods.Input +{ + /// + /// Static factory class for s. + /// + public static class ModHotkeyHandler + { + /// + /// Creates an appropriate for the given . + /// + public static IModHotkeyHandler Create(ModType modType) + { + switch (modType) + { + case ModType.DifficultyReduction: + case ModType.DifficultyIncrease: + case ModType.Automation: + return SequentialModHotkeyHandler.Create(modType); + + default: + return new NoopModHotkeyHandler(); + } + } + } +} diff --git a/osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs new file mode 100644 index 0000000000..81152226da --- /dev/null +++ b/osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.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 System.Collections.Generic; +using osuTK.Input; + +namespace osu.Game.Overlays.Mods.Input +{ + /// + /// A no-op implementation of . + /// Used when a column is not handling any hotkeys at all. + /// + public class NoopModHotkeyHandler : IModHotkeyHandler + { + public bool HandleHotkeyPressed(Key hotkey, IEnumerable availableMods) => false; + } +} diff --git a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs new file mode 100644 index 0000000000..45cfa60fff --- /dev/null +++ b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Rulesets.Mods; +using osuTK.Input; + +namespace osu.Game.Overlays.Mods.Input +{ + /// + /// This implementation of receives a sequence of s, + /// and maps the sequence of keys onto the items it is provided in . + /// In this case, particular mods are not bound to particular keys, the hotkeys are a byproduct of mod ordering. + /// + public class SequentialModHotkeyHandler : IModHotkeyHandler + { + public static SequentialModHotkeyHandler Create(ModType modType) + { + switch (modType) + { + case ModType.DifficultyReduction: + return new SequentialModHotkeyHandler(new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }); + + case ModType.DifficultyIncrease: + return new SequentialModHotkeyHandler(new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }); + + case ModType.Automation: + return new SequentialModHotkeyHandler(new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }); + + default: + throw new ArgumentOutOfRangeException(nameof(modType), modType, $"Cannot create {nameof(SequentialModHotkeyHandler)} for provided mod type"); + } + } + + private readonly Key[] toggleKeys; + + private SequentialModHotkeyHandler(Key[] keys) + { + toggleKeys = keys; + } + + public bool HandleHotkeyPressed(Key hotkey, IEnumerable availableMods) + { + int index = Array.IndexOf(toggleKeys, hotkey); + if (index < 0) + return false; + + var modState = availableMods.ElementAtOrDefault(index); + if (modState == null || modState.Filtered.Value) + return false; + + modState.Active.Toggle(); + return true; + } + } +} diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 563e9a8d55..998437a0a0 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -21,10 +21,10 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; +using osu.Game.Overlays.Mods.Input; using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; -using osuTK.Input; namespace osu.Game.Overlays.Mods { @@ -70,7 +70,7 @@ namespace osu.Game.Overlays.Mods protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod); - private readonly Key[]? toggleKeys; + private readonly IModHotkeyHandler hotkeyHandler; private readonly TextFlowContainer headerText; private readonly Box headerBackground; @@ -86,10 +86,10 @@ namespace osu.Game.Overlays.Mods private const float header_height = 42; - public ModColumn(ModType modType, bool allowBulkSelection, Key[]? toggleKeys = null) + public ModColumn(ModType modType, bool allowBulkSelection) { ModType = modType; - this.toggleKeys = toggleKeys; + hotkeyHandler = ModHotkeyHandler.Create(modType); Width = 320; RelativeSizeAxes = Axes.Y; @@ -425,17 +425,10 @@ namespace osu.Game.Overlays.Mods protected override bool OnKeyDown(KeyDownEvent e) { - if (e.ControlPressed || e.AltPressed || e.SuperPressed) return false; - if (toggleKeys == null) return false; + if (e.ControlPressed || e.AltPressed || e.SuperPressed || e.Repeat) + return false; - int index = Array.IndexOf(toggleKeys, e.Key); - if (index < 0) return false; - - var modState = availableMods.ElementAtOrDefault(index); - if (modState == null || modState.Filtered.Value) return false; - - modState.Active.Toggle(); - return true; + return hotkeyHandler.HandleHotkeyPressed(e.Key, availableMods); } #endregion diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ad02f079a5..811db393d6 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -21,7 +21,6 @@ using osu.Game.Localisation; using osu.Game.Rulesets.Mods; using osu.Game.Utils; using osuTK; -using osuTK.Input; namespace osu.Game.Overlays.Mods { @@ -68,7 +67,7 @@ namespace osu.Game.Overlays.Mods /// protected virtual bool AllowCustomisation => true; - protected virtual ModColumn CreateModColumn(ModType modType, Key[]? toggleKeys = null) => new ModColumn(modType, false, toggleKeys); + protected virtual ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, false); protected virtual IReadOnlyList ComputeNewModsFromSelection(IReadOnlyList oldSelection, IReadOnlyList newSelection) => newSelection; @@ -160,9 +159,9 @@ namespace osu.Game.Overlays.Mods Padding = new MarginPadding { Bottom = 10 }, Children = new[] { - createModColumnContent(ModType.DifficultyReduction, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }), - createModColumnContent(ModType.DifficultyIncrease, new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }), - createModColumnContent(ModType.Automation, new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }), + createModColumnContent(ModType.DifficultyReduction), + createModColumnContent(ModType.DifficultyIncrease), + createModColumnContent(ModType.Automation), createModColumnContent(ModType.Conversion), createModColumnContent(ModType.Fun) } @@ -264,9 +263,9 @@ namespace osu.Game.Overlays.Mods column.DeselectAll(); } - private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null) + private ColumnDimContainer createModColumnContent(ModType modType) { - var column = CreateModColumn(modType, toggleKeys).With(column => + var column = CreateModColumn(modType).With(column => { // spacing applied here rather than via `columnFlow.Spacing` to avoid uneven gaps when some of the columns are hidden. column.Margin = new MarginPadding { Right = 10 }; diff --git a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs index b8f4b8a196..a292a50b72 100644 --- a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs @@ -1,14 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Game.Rulesets.Mods; using osu.Game.Utils; -using osuTK.Input; namespace osu.Game.Overlays.Mods { @@ -19,7 +15,7 @@ namespace osu.Game.Overlays.Mods { } - protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new UserModColumn(modType, false, toggleKeys); + protected override ModColumn CreateModColumn(ModType modType) => new UserModColumn(modType, false); protected override IReadOnlyList ComputeNewModsFromSelection(IReadOnlyList oldSelection, IReadOnlyList newSelection) { @@ -44,8 +40,8 @@ namespace osu.Game.Overlays.Mods private class UserModColumn : ModColumn { - public UserModColumn(ModType modType, bool allowBulkSelection, [CanBeNull] Key[] toggleKeys = null) - : base(modType, allowBulkSelection, toggleKeys) + public UserModColumn(ModType modType, bool allowBulkSelection) + : base(modType, allowBulkSelection) { } diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index c27b78642a..0f02692eda 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; -using osuTK.Input; namespace osu.Game.Screens.OnlinePlay { @@ -33,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay IsValidMod = _ => true; } - protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new ModColumn(modType, true, toggleKeys); + protected override ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, true); protected override IEnumerable CreateFooterButtons() => base.CreateFooterButtons().Prepend( new SelectAllModsButton(this) From 816fd338cb8cd4143242053d415783ccee08e4fd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 19:57:27 +0900 Subject: [PATCH 134/803] Fix typo --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index d02f8fe6b2..cf9109eb8b 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -387,7 +387,7 @@ namespace osu.Game.Database /// The work to run. public Task WriteAsync(Action action) { - // Regardless of calling Realm.GetInstance of Realm.GetInstanceAsync, there is a blocking overhead on retrieval. + // Regardless of calling Realm.GetInstance or Realm.GetInstanceAsync, there is a blocking overhead on retrieval. // Adding a forced Task.Run resolves this. return Task.Run(async () => From f2eb7e055166bcf08c5cf2cbeeeb280498a2275a Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Tue, 21 Jun 2022 19:06:38 +0800 Subject: [PATCH 135/803] Use better design and fix some problem Let saveReplay async but still void Make failed score's rank = F --- osu.Game/Screens/Play/FailOverlay.cs | 42 ++++++++++++++++- osu.Game/Screens/Play/Player.cs | 13 +++--- .../Screens/Play/SaveFailedScoreButton.cs | 45 +++++++++++++++++++ 3 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Screens/Play/SaveFailedScoreButton.cs diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index 1b9d81d33b..881791271b 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -5,8 +5,14 @@ using System; using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; namespace osu.Game.Screens.Play { @@ -21,7 +27,41 @@ namespace osu.Game.Screens.Play { AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); - AddButton("Save replay and Quit", colours.Blue, () => SaveReplay?.Invoke()); + // from #10339 maybe this is a better visual effect + Add(new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = TwoLayerButton.SIZE_EXTENDED.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#333") + }, + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Spacing = new Vector2(5), + Padding = new MarginPadding(10), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new SaveFailedScoreButton() + { + OnSave = SaveReplay, + RelativeSizeAxes = Axes.Y, + Width = 300 + }, + } + } + } + }); } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d38c10c071..8223ed1e3d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -266,7 +266,7 @@ namespace osu.Game.Screens.Play }, FailOverlay = new FailOverlay { - SaveReplay = saveReplay, + SaveReplay = saveFailedReplay, OnRetry = Restart, OnQuit = () => PerformExit(true), }, @@ -1024,8 +1024,7 @@ namespace osu.Game.Screens.Play if (prepareScoreForDisplayTask == null) { Score.ScoreInfo.Passed = false; - // potentially should be ScoreRank.F instead? this is the best alternative for now. - Score.ScoreInfo.Rank = ScoreRank.D; + Score.ScoreInfo.Rank = ScoreRank.F; } // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. @@ -1044,20 +1043,20 @@ namespace osu.Game.Screens.Play return base.OnExiting(e); } - private void saveReplay() + private async void saveFailedReplay() { + Score.ScoreInfo.Passed = false; + Score.ScoreInfo.Rank = ScoreRank.F; var scoreCopy = Score.DeepClone(); try { - ImportScore(scoreCopy).ConfigureAwait(false); + await ImportScore(scoreCopy).ConfigureAwait(false); } catch (Exception ex) { Logger.Error(ex, @"Score import failed!"); } - - PerformExit(true); } /// diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs new file mode 100644 index 0000000000..1c198421ab --- /dev/null +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online; + +namespace osu.Game.Screens.Play +{ + public class SaveFailedScoreButton : DownloadButton + { + public Action? OnSave; + + [BackgroundDependencyLoader] + private void load() + { + State.BindValueChanged(updateTooltip, true); + Action = saveScore; + } + + private void saveScore() + { + if (State.Value != DownloadState.LocallyAvailable) + OnSave?.Invoke(); + + State.Value = DownloadState.LocallyAvailable; + } + + private void updateTooltip(ValueChangedEvent state) + { + switch (state.NewValue) + { + case DownloadState.LocallyAvailable: + TooltipText = @"Score saved"; + break; + + default: + TooltipText = @"Save score"; + break; + } + } + } +} From 5abd8a07d2ba2dcea0ab6e0c80b8cca8cc714a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 13:03:46 +0200 Subject: [PATCH 136/803] Add setting for changing mod select hotkey style --- osu.Game/Configuration/OsuConfigManager.cs | 3 +++ osu.Game/Localisation/UserInterfaceStrings.cs | 5 ++++ .../Mods/Input/ModSelectHotkeyStyle.cs | 27 +++++++++++++++++++ .../UserInterface/SongSelectSettings.cs | 7 +++++ 4 files changed, 42 insertions(+) create mode 100644 osu.Game/Overlays/Mods/Input/ModSelectHotkeyStyle.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a0e1d9ddc4..713166a9a0 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -20,6 +20,7 @@ using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Overlays; +using osu.Game.Overlays.Mods.Input; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; @@ -47,6 +48,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.SongSelectSortingMode, SortMode.Title); SetDefault(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation); + SetDefault(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Sequential); SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f); @@ -324,6 +326,7 @@ namespace osu.Game.Configuration SongSelectGroupingMode, SongSelectSortingMode, RandomSelectAlgorithm, + ModSelectHotkeyStyle, ShowFpsDisplay, ChatDisplayHeight, BeatmapListingCardSize, diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index 4e7af99ce9..a007f760d8 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -106,6 +106,11 @@ namespace osu.Game.Localisation /// public static LocalisableString RandomSelectionAlgorithm => new TranslatableString(getKey(@"random_selection_algorithm"), @"Random selection algorithm"); + /// + /// "Mod select hotkey style" + /// + public static LocalisableString ModSelectHotkeyStyle => new TranslatableString(getKey(@"mod_select_hotkey_style"), @"Mod select hotkey style"); + /// /// "no limit" /// diff --git a/osu.Game/Overlays/Mods/Input/ModSelectHotkeyStyle.cs b/osu.Game/Overlays/Mods/Input/ModSelectHotkeyStyle.cs new file mode 100644 index 0000000000..6375b37f8b --- /dev/null +++ b/osu.Game/Overlays/Mods/Input/ModSelectHotkeyStyle.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.Rulesets.Mods; + +namespace osu.Game.Overlays.Mods.Input +{ + /// + /// The style of hotkey handling to use on the mod select screen. + /// + public enum ModSelectHotkeyStyle + { + /// + /// Each letter row on the keyboard controls one of the three first s. + /// Individual letters in a row trigger the mods in a sequential fashion. + /// Uses . + /// + Sequential, + + /// + /// Matches keybindings from stable 1:1. + /// One keybinding can toggle between what used to be s on stable, + /// and some mods in a column may not have any hotkeys at all. + /// + Classic + } +} diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index 8cec7bbb30..507e116723 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -11,6 +11,7 @@ using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; +using osu.Game.Overlays.Mods.Input; namespace osu.Game.Overlays.Settings.Sections.UserInterface { @@ -61,6 +62,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface { LabelText = UserInterfaceStrings.RandomSelectionAlgorithm, Current = config.GetBindable(OsuSetting.RandomSelectAlgorithm), + }, + new SettingsEnumDropdown + { + LabelText = UserInterfaceStrings.ModSelectHotkeyStyle, + Current = config.GetBindable(OsuSetting.ModSelectHotkeyStyle), + ClassicDefault = ModSelectHotkeyStyle.Classic } }; } From 658f5341c7b6b8717ac82fb5dea1a17129b6ebad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 13:10:22 +0200 Subject: [PATCH 137/803] Set up flow for switching between hotkey styles --- .../Visual/UserInterface/TestSceneModColumn.cs | 9 ++++++++- .../Mods/Input/ClassicModHotkeyHandler.cs | 16 ++++++++++++++++ osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs | 6 ++++-- osu.Game/Overlays/Mods/ModColumn.cs | 10 +++++++--- 4 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index f98f39d445..d992f1aa05 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -9,9 +9,11 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Overlays.Mods.Input; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Utils; @@ -25,6 +27,9 @@ namespace osu.Game.Tests.Visual.UserInterface [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + [Resolved] + private OsuConfigManager configManager { get; set; } = null!; + [TestCase(ModType.DifficultyReduction)] [TestCase(ModType.DifficultyIncrease)] [TestCase(ModType.Conversion)] @@ -132,8 +137,10 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestKeyboardSelection() + public void TestSequentialKeyboardSelection() { + AddStep("set sequential hotkey mode", () => configManager.SetValue(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Sequential)); + ModColumn column = null!; AddStep("create content", () => Child = new Container { diff --git a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs new file mode 100644 index 0000000000..1dd732b79c --- /dev/null +++ b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osuTK.Input; + +namespace osu.Game.Overlays.Mods.Input +{ + /// + /// Uses bindings from stable 1:1. + /// + public class ClassicModHotkeyHandler : IModHotkeyHandler + { + public bool HandleHotkeyPressed(Key hotkey, IEnumerable availableMods) => false; // TODO + } +} diff --git a/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs index 22de557979..5c46b2065f 100644 --- a/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs @@ -13,14 +13,16 @@ namespace osu.Game.Overlays.Mods.Input /// /// Creates an appropriate for the given . /// - public static IModHotkeyHandler Create(ModType modType) + public static IModHotkeyHandler Create(ModType modType, ModSelectHotkeyStyle style) { switch (modType) { case ModType.DifficultyReduction: case ModType.DifficultyIncrease: case ModType.Automation: - return SequentialModHotkeyHandler.Create(modType); + return style == ModSelectHotkeyStyle.Sequential + ? (IModHotkeyHandler)SequentialModHotkeyHandler.Create(modType) + : new ClassicModHotkeyHandler(); default: return new NoopModHotkeyHandler(); diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 998437a0a0..cf3d354d47 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -70,7 +71,8 @@ namespace osu.Game.Overlays.Mods protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod); - private readonly IModHotkeyHandler hotkeyHandler; + private Bindable hotkeyStyle = null!; + private IModHotkeyHandler hotkeyHandler = null!; private readonly TextFlowContainer headerText; private readonly Box headerBackground; @@ -89,7 +91,6 @@ namespace osu.Game.Overlays.Mods public ModColumn(ModType modType, bool allowBulkSelection) { ModType = modType; - hotkeyHandler = ModHotkeyHandler.Create(modType); Width = 320; RelativeSizeAxes = Axes.Y; @@ -231,7 +232,7 @@ namespace osu.Game.Overlays.Mods } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuColour colours) + private void load(OverlayColourProvider colourProvider, OsuColour colours, OsuConfigManager configManager) { headerBackground.Colour = accentColour = colours.ForModType(ModType); @@ -243,6 +244,8 @@ namespace osu.Game.Overlays.Mods contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Background4, colourProvider.Background3); contentBackground.Colour = colourProvider.Background4; + + hotkeyStyle = configManager.GetBindable(OsuSetting.ModSelectHotkeyStyle); } protected override void LoadComplete() @@ -250,6 +253,7 @@ namespace osu.Game.Overlays.Mods base.LoadComplete(); toggleAllCheckbox?.Current.BindValueChanged(_ => updateToggleAllText(), true); + hotkeyStyle.BindValueChanged(val => hotkeyHandler = ModHotkeyHandler.Create(ModType, val.NewValue), true); asyncLoadPanels(); } From 143c8e8da679b156868c40d7b72ffca2b011977b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 13:33:33 +0200 Subject: [PATCH 138/803] Add test scene for desired classic selection behaviour --- .../UserInterface/TestSceneModColumn.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index d992f1aa05..25e99ac24b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -178,6 +178,56 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("clear filter", () => setFilter(null)); } + [Test] + public void TestClassicKeyboardSelection() + { + AddStep("set classic hotkey mode", () => configManager.SetValue(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Classic)); + + ModColumn column = null!; + AddStep("create content", () => Child = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(30), + Child = column = new ModColumn(ModType.DifficultyIncrease, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AvailableMods = getExampleModsFor(ModType.DifficultyIncrease) + } + }); + + AddUntilStep("wait for panel load", () => column.IsLoaded && column.ItemsLoaded); + + AddStep("press A", () => InputManager.Key(Key.A)); + AddAssert("HR panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "HR").Active.Value); + + AddStep("press A again", () => InputManager.Key(Key.A)); + AddAssert("HR panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "HR").Active.Value); + + AddStep("press D", () => InputManager.Key(Key.D)); + AddAssert("DT panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value); + + AddStep("press D again", () => InputManager.Key(Key.D)); + AddAssert("DT panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value); + AddAssert("NC panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value); + + AddStep("press D again", () => InputManager.Key(Key.D)); + AddAssert("DT panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value); + AddAssert("NC panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value); + + AddStep("press Shift-D", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.D); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + AddAssert("DT panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value); + AddAssert("NC panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value); + + AddStep("press J", () => InputManager.Key(Key.J)); + AddAssert("no change", () => this.ChildrenOfType().Single(panel => panel.Active.Value).Mod.Acronym == "NC"); + } + private void setFilter(Func? filter) { foreach (var modState in this.ChildrenOfType().Single().AvailableMods) From 234120ff43ec16c09f394067b8e7c5d67330135b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 13:37:17 +0200 Subject: [PATCH 139/803] Forward entire event to `IModHotkeyHandler` Required for shift handling in the classic implementation. --- .../Overlays/Mods/Input/ClassicModHotkeyHandler.cs | 4 ++-- osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs | 10 +++++----- osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs | 4 ++-- .../Overlays/Mods/Input/SequentialModHotkeyHandler.cs | 5 +++-- osu.Game/Overlays/Mods/ModColumn.cs | 2 +- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs index 1dd732b79c..e22ad13451 100644 --- a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osuTK.Input; +using osu.Framework.Input.Events; namespace osu.Game.Overlays.Mods.Input { @@ -11,6 +11,6 @@ namespace osu.Game.Overlays.Mods.Input /// public class ClassicModHotkeyHandler : IModHotkeyHandler { - public bool HandleHotkeyPressed(Key hotkey, IEnumerable availableMods) => false; // TODO + public bool HandleHotkeyPressed(KeyDownEvent e, IEnumerable availableMods) => false; // TODO } } diff --git a/osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs index aec16ff764..d2cc0e84d2 100644 --- a/osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osuTK.Input; +using osu.Framework.Input.Events; namespace osu.Game.Overlays.Mods.Input { @@ -12,11 +12,11 @@ namespace osu.Game.Overlays.Mods.Input public interface IModHotkeyHandler { /// - /// Attempt to handle a press of the supplied as a selection of one of the mods in . + /// Attempt to handle the supplied as a selection of one of the mods in . /// - /// The key that was pressed by the user. + /// The event representing the user's keypress. /// The list of currently available mods. - /// Whether the was handled as a mod selection/deselection. - bool HandleHotkeyPressed(Key hotkey, IEnumerable availableMods); + /// Whether the supplied event was handled as a mod selection/deselection. + bool HandleHotkeyPressed(KeyDownEvent e, IEnumerable availableMods); } } diff --git a/osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs index 81152226da..3f7a6298a1 100644 --- a/osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osuTK.Input; +using osu.Framework.Input.Events; namespace osu.Game.Overlays.Mods.Input { @@ -12,6 +12,6 @@ namespace osu.Game.Overlays.Mods.Input /// public class NoopModHotkeyHandler : IModHotkeyHandler { - public bool HandleHotkeyPressed(Key hotkey, IEnumerable availableMods) => false; + public bool HandleHotkeyPressed(KeyDownEvent e, IEnumerable availableMods) => false; } } diff --git a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs index 45cfa60fff..e6717053e1 100644 --- a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Mods; using osuTK.Input; @@ -41,9 +42,9 @@ namespace osu.Game.Overlays.Mods.Input toggleKeys = keys; } - public bool HandleHotkeyPressed(Key hotkey, IEnumerable availableMods) + public bool HandleHotkeyPressed(KeyDownEvent e, IEnumerable availableMods) { - int index = Array.IndexOf(toggleKeys, hotkey); + int index = Array.IndexOf(toggleKeys, e.Key); if (index < 0) return false; diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index cf3d354d47..1fef7257b5 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -432,7 +432,7 @@ namespace osu.Game.Overlays.Mods if (e.ControlPressed || e.AltPressed || e.SuperPressed || e.Repeat) return false; - return hotkeyHandler.HandleHotkeyPressed(e.Key, availableMods); + return hotkeyHandler.HandleHotkeyPressed(e, availableMods); } #endregion From 7b7b8c1892e36a9d2dda89c95e69d1f6d09ed4af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 14:24:30 +0200 Subject: [PATCH 140/803] Implement behaviour for classic selection style --- .../Mods/Input/ClassicModHotkeyHandler.cs | 66 ++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs index e22ad13451..0faf509136 100644 --- a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs @@ -1,8 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Mods; +using osuTK.Input; namespace osu.Game.Overlays.Mods.Input { @@ -11,6 +16,65 @@ namespace osu.Game.Overlays.Mods.Input /// public class ClassicModHotkeyHandler : IModHotkeyHandler { - public bool HandleHotkeyPressed(KeyDownEvent e, IEnumerable availableMods) => false; // TODO + private static readonly Dictionary mod_type_lookup = new Dictionary + { + [Key.Q] = new[] { typeof(ModEasy) }, + [Key.W] = new[] { typeof(ModNoFail) }, + [Key.E] = new[] { typeof(ModHalfTime) }, + [Key.A] = new[] { typeof(ModHardRock) }, + [Key.S] = new[] { typeof(ModSuddenDeath), typeof(ModPerfect) }, + [Key.D] = new[] { typeof(ModDoubleTime), typeof(ModNightcore) }, + [Key.F] = new[] { typeof(ModHidden) }, + [Key.G] = new[] { typeof(ModFlashlight) }, + [Key.Z] = new[] { typeof(ModRelax) }, + [Key.V] = new[] { typeof(ModAutoplay), typeof(ModCinema) } + }; + + public bool HandleHotkeyPressed(KeyDownEvent e, IEnumerable availableMods) + { + if (!mod_type_lookup.TryGetValue(e.Key, out var typesToMatch)) + return false; + + var matchingMods = availableMods.Where(modState => matches(modState, typesToMatch)).ToArray(); + + if (matchingMods.Length == 0) + return false; + + if (matchingMods.Length == 1) + { + matchingMods.Single().Active.Toggle(); + return true; + } + + // we're assuming that only one mod from the group can be active at a time. + // this is mostly ensured by `IncompatibleMods` definitions, but let's make sure just in case. + Debug.Assert(matchingMods.Count(modState => modState.Active.Value) <= 1); + int currentSelectedIndex = Array.FindIndex(matchingMods, modState => modState.Active.Value); + + // `FindIndex` will return -1 if it doesn't find the item. + // this is convenient in the forward direction, since if we add 1 then we'll end up at the first item, + // but less so in the backwards direction. + // for convenience, detect this situation and set the index to one index past the last item. + // this makes it so that if we subtract 1 then we'll end up at the last item again. + if (currentSelectedIndex < 0 && e.ShiftPressed) + currentSelectedIndex = matchingMods.Length; + + int indexToSelect = e.ShiftPressed ? currentSelectedIndex - 1 : currentSelectedIndex + 1; + + // `currentSelectedIndex` and `indexToSelect` can both be equal to -1 or `matchingMods.Length`. + // if the former is beyond array range, it means nothing was previously selected and so there's nothing to deselect. + // if the latter is beyond array range, it means that either the previous selection was first and we're going backwards, + // or it was last and we're going forwards. + // in either case there is nothing to select. + if (currentSelectedIndex >= 0 && currentSelectedIndex <= matchingMods.Length - 1) + matchingMods[currentSelectedIndex].Active.Value = false; + if (indexToSelect >= 0 && indexToSelect <= matchingMods.Length - 1) + matchingMods[indexToSelect].Active.Value = true; + + return true; + } + + private static bool matches(ModState modState, Type[] typesToMatch) + => typesToMatch.Any(typeToMatch => typeToMatch.IsInstanceOfType(modState.Mod)); } } From da1814e7c3bcc8beff26ec0de471a0ca19106a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 14:48:41 +0200 Subject: [PATCH 141/803] Restructure everything to fix free mod overlay issue --- .../UserInterface/TestSceneModColumn.cs | 55 +++++++++++++++++-- .../Mods/Input/ClassicModHotkeyHandler.cs | 22 +++++++- .../Overlays/Mods/Input/ModHotkeyHandler.cs | 32 ----------- osu.Game/Overlays/Mods/ModColumn.cs | 33 +++++++++-- .../Overlays/Mods/UserModSelectOverlay.cs | 4 +- 5 files changed, 101 insertions(+), 45 deletions(-) delete mode 100644 osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index 25e99ac24b..ec3c5b6ac6 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -179,7 +179,7 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestClassicKeyboardSelection() + public void TestClassicKeyboardExclusiveSelection() { AddStep("set classic hotkey mode", () => configManager.SetValue(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Classic)); @@ -188,7 +188,7 @@ namespace osu.Game.Tests.Visual.UserInterface { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(30), - Child = column = new ModColumn(ModType.DifficultyIncrease, true) + Child = column = new ModColumn(ModType.DifficultyIncrease, false) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -228,6 +228,53 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("no change", () => this.ChildrenOfType().Single(panel => panel.Active.Value).Mod.Acronym == "NC"); } + [Test] + public void TestClassicKeyboardIncompatibleSelection() + { + AddStep("set classic hotkey mode", () => configManager.SetValue(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Classic)); + + ModColumn column = null!; + AddStep("create content", () => Child = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(30), + Child = column = new ModColumn(ModType.DifficultyIncrease, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AvailableMods = getExampleModsFor(ModType.DifficultyIncrease) + } + }); + + AddUntilStep("wait for panel load", () => column.IsLoaded && column.ItemsLoaded); + + AddStep("press A", () => InputManager.Key(Key.A)); + AddAssert("HR panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "HR").Active.Value); + + AddStep("press A again", () => InputManager.Key(Key.A)); + AddAssert("HR panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "HR").Active.Value); + + AddStep("press D", () => InputManager.Key(Key.D)); + AddAssert("DT panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value); + AddAssert("NC panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value); + + AddStep("press D again", () => InputManager.Key(Key.D)); + AddAssert("DT panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value); + AddAssert("NC panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value); + + AddStep("press Shift-D", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.D); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + AddAssert("DT panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value); + AddAssert("NC panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value); + + AddStep("press J", () => InputManager.Key(Key.J)); + AddAssert("no change", () => this.ChildrenOfType().Count(panel => panel.Active.Value) == 2); + } + private void setFilter(Func? filter) { foreach (var modState in this.ChildrenOfType().Single().AvailableMods) @@ -238,8 +285,8 @@ namespace osu.Game.Tests.Visual.UserInterface { public new bool SelectionAnimationRunning => base.SelectionAnimationRunning; - public TestModColumn(ModType modType, bool allowBulkSelection) - : base(modType, allowBulkSelection) + public TestModColumn(ModType modType, bool allowIncompatibleSelection) + : base(modType, allowIncompatibleSelection) { } } diff --git a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs index 0faf509136..33d86cbdaa 100644 --- a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs @@ -30,6 +30,13 @@ namespace osu.Game.Overlays.Mods.Input [Key.V] = new[] { typeof(ModAutoplay), typeof(ModCinema) } }; + private readonly bool allowIncompatibleSelection; + + public ClassicModHotkeyHandler(bool allowIncompatibleSelection) + { + this.allowIncompatibleSelection = allowIncompatibleSelection; + } + public bool HandleHotkeyPressed(KeyDownEvent e, IEnumerable availableMods) { if (!mod_type_lookup.TryGetValue(e.Key, out var typesToMatch)) @@ -46,8 +53,19 @@ namespace osu.Game.Overlays.Mods.Input return true; } - // we're assuming that only one mod from the group can be active at a time. - // this is mostly ensured by `IncompatibleMods` definitions, but let's make sure just in case. + if (allowIncompatibleSelection) + { + // easier path - multiple incompatible mods can be active at a time. + // this is used in the free mod select overlay. + // in this case, just toggle everything. + bool anyActive = matchingMods.Any(mod => mod.Active.Value); + foreach (var mod in matchingMods) + mod.Active.Value = !anyActive; + return true; + } + + // we now know there are multiple possible mods to handle, and only one of them can be active at a time. + // let's make sure of this just in case. Debug.Assert(matchingMods.Count(modState => modState.Active.Value) <= 1); int currentSelectedIndex = Array.FindIndex(matchingMods, modState => modState.Active.Value); diff --git a/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs deleted file mode 100644 index 5c46b2065f..0000000000 --- a/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs +++ /dev/null @@ -1,32 +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.Mods; - -namespace osu.Game.Overlays.Mods.Input -{ - /// - /// Static factory class for s. - /// - public static class ModHotkeyHandler - { - /// - /// Creates an appropriate for the given . - /// - public static IModHotkeyHandler Create(ModType modType, ModSelectHotkeyStyle style) - { - switch (modType) - { - case ModType.DifficultyReduction: - case ModType.DifficultyIncrease: - case ModType.Automation: - return style == ModSelectHotkeyStyle.Sequential - ? (IModHotkeyHandler)SequentialModHotkeyHandler.Create(modType) - : new ClassicModHotkeyHandler(); - - default: - return new NoopModHotkeyHandler(); - } - } - } -} diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 1fef7257b5..f8f6b814ca 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -71,8 +71,7 @@ namespace osu.Game.Overlays.Mods protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod); - private Bindable hotkeyStyle = null!; - private IModHotkeyHandler hotkeyHandler = null!; + private readonly bool allowIncompatibleSelection; private readonly TextFlowContainer headerText; private readonly Box headerBackground; @@ -83,14 +82,18 @@ namespace osu.Game.Overlays.Mods private Colour4 accentColour; + private Bindable hotkeyStyle = null!; + private IModHotkeyHandler hotkeyHandler = null!; + private Task? latestLoadTask; internal bool ItemsLoaded => latestLoadTask == null; private const float header_height = 42; - public ModColumn(ModType modType, bool allowBulkSelection) + public ModColumn(ModType modType, bool allowIncompatibleSelection) { ModType = modType; + this.allowIncompatibleSelection = allowIncompatibleSelection; Width = 320; RelativeSizeAxes = Axes.Y; @@ -198,7 +201,7 @@ namespace osu.Game.Overlays.Mods createHeaderText(); - if (allowBulkSelection) + if (allowIncompatibleSelection) { controlContainer.Height = 35; controlContainer.Add(toggleAllCheckbox = new ToggleAllCheckbox(this) @@ -253,7 +256,7 @@ namespace osu.Game.Overlays.Mods base.LoadComplete(); toggleAllCheckbox?.Current.BindValueChanged(_ => updateToggleAllText(), true); - hotkeyStyle.BindValueChanged(val => hotkeyHandler = ModHotkeyHandler.Create(ModType, val.NewValue), true); + hotkeyStyle.BindValueChanged(val => hotkeyHandler = CreateHotkeyHandler(val.NewValue), true); asyncLoadPanels(); } @@ -427,6 +430,26 @@ namespace osu.Game.Overlays.Mods #region Keyboard selection support + /// + /// Creates an appropriate for this column's and + /// the supplied . + /// + protected virtual IModHotkeyHandler CreateHotkeyHandler(ModSelectHotkeyStyle hotkeyStyle) + { + switch (ModType) + { + case ModType.DifficultyReduction: + case ModType.DifficultyIncrease: + case ModType.Automation: + return hotkeyStyle == ModSelectHotkeyStyle.Sequential + ? (IModHotkeyHandler)SequentialModHotkeyHandler.Create(ModType) + : new ClassicModHotkeyHandler(allowIncompatibleSelection); + + default: + return new NoopModHotkeyHandler(); + } + } + protected override bool OnKeyDown(KeyDownEvent e) { if (e.ControlPressed || e.AltPressed || e.SuperPressed || e.Repeat) diff --git a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs index a292a50b72..1090306c5b 100644 --- a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs @@ -40,8 +40,8 @@ namespace osu.Game.Overlays.Mods private class UserModColumn : ModColumn { - public UserModColumn(ModType modType, bool allowBulkSelection) - : base(modType, allowBulkSelection) + public UserModColumn(ModType modType, bool allowIncompatibleSelection) + : base(modType, allowIncompatibleSelection) { } From a996325e19bf5496994802e0c8d88c081b164e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 15:00:39 +0200 Subject: [PATCH 142/803] Add test coverage for filter handling in classic style --- .../Visual/UserInterface/TestSceneModColumn.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index ec3c5b6ac6..3765bc2ca0 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -226,6 +226,11 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("press J", () => InputManager.Key(Key.J)); AddAssert("no change", () => this.ChildrenOfType().Single(panel => panel.Active.Value).Mod.Acronym == "NC"); + + AddStep("filter everything but NC", () => setFilter(mod => mod.Acronym == "NC")); + + AddStep("press A", () => InputManager.Key(Key.A)); + AddAssert("no change", () => this.ChildrenOfType().Single(panel => panel.Active.Value).Mod.Acronym == "NC"); } [Test] @@ -273,6 +278,11 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("press J", () => InputManager.Key(Key.J)); AddAssert("no change", () => this.ChildrenOfType().Count(panel => panel.Active.Value) == 2); + + AddStep("filter everything but NC", () => setFilter(mod => mod.Acronym == "NC")); + + AddStep("press A", () => InputManager.Key(Key.A)); + AddAssert("no change", () => this.ChildrenOfType().Count(panel => panel.Active.Value) == 2); } private void setFilter(Func? filter) From 9e5cc89edbcef90f2804e6a0eb163445b08f07a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 15:01:33 +0200 Subject: [PATCH 143/803] Fix classic hotkeys toggling filtered mods --- osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs index 33d86cbdaa..4f3c18fc43 100644 --- a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Mods.Input if (!mod_type_lookup.TryGetValue(e.Key, out var typesToMatch)) return false; - var matchingMods = availableMods.Where(modState => matches(modState, typesToMatch)).ToArray(); + var matchingMods = availableMods.Where(modState => matches(modState, typesToMatch) && !modState.Filtered.Value).ToArray(); if (matchingMods.Length == 0) return false; From a9f6eb0293fd8fcc018f4ff2d29dba10f24bc117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 15:18:37 +0200 Subject: [PATCH 144/803] Add test coverage for new intended behaviour of sequential hotkey style --- osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index 3765bc2ca0..72cddc0ad2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -165,9 +165,12 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("set filter to NF", () => setFilter(mod => mod.Acronym == "NF")); AddStep("press W", () => InputManager.Key(Key.W)); + AddAssert("NF panel not selected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value); + + AddStep("press Q", () => InputManager.Key(Key.Q)); AddAssert("NF panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value); - AddStep("press W again", () => InputManager.Key(Key.W)); + AddStep("press Q again", () => InputManager.Key(Key.Q)); AddAssert("NF panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value); AddStep("filter out everything", () => setFilter(_ => false)); From f564ed589fdac206748c43000d98b2c918f1ae78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 15:19:41 +0200 Subject: [PATCH 145/803] Alter sequential hotkey style to always use visible index Previous behaviour was once mentioned off-hand as unintuitive. --- osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs index e6717053e1..dedb556304 100644 --- a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs @@ -48,8 +48,8 @@ namespace osu.Game.Overlays.Mods.Input if (index < 0) return false; - var modState = availableMods.ElementAtOrDefault(index); - if (modState == null || modState.Filtered.Value) + var modState = availableMods.Where(modState => !modState.Filtered.Value).ElementAtOrDefault(index); + if (modState == null) return false; modState.Active.Toggle(); From 7013909322687424052ffe6562a0b26d4ca62a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 16:43:04 +0200 Subject: [PATCH 146/803] Remove unnecessary `protected virtual` --- osu.Game/Overlays/Mods/ModColumn.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index f8f6b814ca..c51e6baa0d 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -256,7 +256,7 @@ namespace osu.Game.Overlays.Mods base.LoadComplete(); toggleAllCheckbox?.Current.BindValueChanged(_ => updateToggleAllText(), true); - hotkeyStyle.BindValueChanged(val => hotkeyHandler = CreateHotkeyHandler(val.NewValue), true); + hotkeyStyle.BindValueChanged(val => hotkeyHandler = createHotkeyHandler(val.NewValue), true); asyncLoadPanels(); } @@ -434,7 +434,7 @@ namespace osu.Game.Overlays.Mods /// Creates an appropriate for this column's and /// the supplied . /// - protected virtual IModHotkeyHandler CreateHotkeyHandler(ModSelectHotkeyStyle hotkeyStyle) + private IModHotkeyHandler createHotkeyHandler(ModSelectHotkeyStyle hotkeyStyle) { switch (ModType) { From bff35cb34895848daa8af880483e08ff9c59b8ed Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Wed, 22 Jun 2022 01:19:20 +0900 Subject: [PATCH 147/803] Shake button when replay already save --- .../Screens/Play/SaveFailedScoreButton.cs | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index 1c198421ab..3efd8fa4a3 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -4,20 +4,54 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online; +using osuTK; namespace osu.Game.Screens.Play { - public class SaveFailedScoreButton : DownloadButton + public class SaveFailedScoreButton : CompositeDrawable { public Action? OnSave; + protected readonly Bindable State = new Bindable(); + + private DownloadButton button; + private ShakeContainer shakeContainer; + + public SaveFailedScoreButton() + { + InternalChild = shakeContainer = new ShakeContainer + { + RelativeSizeAxes = Axes.Both, + Child = button = new DownloadButton + { + RelativeSizeAxes = Axes.Both, + } + }; + Size = new Vector2(50, 30); + } + [BackgroundDependencyLoader] private void load() { + button.Action = () => + { + switch (State.Value) + { + case DownloadState.LocallyAvailable: + shakeContainer.Shake(); + break; + + default: + saveScore(); + break; + } + }; State.BindValueChanged(updateTooltip, true); - Action = saveScore; } private void saveScore() @@ -26,6 +60,7 @@ namespace osu.Game.Screens.Play OnSave?.Invoke(); State.Value = DownloadState.LocallyAvailable; + button.State.Value = DownloadState.LocallyAvailable; } private void updateTooltip(ValueChangedEvent state) @@ -33,11 +68,11 @@ namespace osu.Game.Screens.Play switch (state.NewValue) { case DownloadState.LocallyAvailable: - TooltipText = @"Score saved"; + button.TooltipText = @"Score saved"; break; default: - TooltipText = @"Save score"; + button.TooltipText = @"Save score"; break; } } From e4931f977cadd7d90a4f965d4215013eaa207fe6 Mon Sep 17 00:00:00 2001 From: Supersonicboss1 Date: Tue, 21 Jun 2022 23:00:03 +0100 Subject: [PATCH 148/803] improved UI of maintenance section in settings --- ...{GeneralSettings.cs => BeatmapSettings.cs} | 88 +++---------------- .../Maintenance/CollectionsSettings.cs | 49 +++++++++++ .../Sections/Maintenance/ScoreSettings.cs | 52 +++++++++++ .../Sections/Maintenance/SkinSettings.cs | 52 +++++++++++ .../Settings/Sections/MaintenanceSection.cs | 5 +- 5 files changed, 170 insertions(+), 76 deletions(-) rename osu.Game/Overlays/Settings/Sections/Maintenance/{GeneralSettings.cs => BeatmapSettings.cs} (62%) create mode 100644 osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs create mode 100644 osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs create mode 100644 osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs similarity index 62% rename from osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs rename to osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs index 916c607c1c..474f23e4e7 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs @@ -9,31 +9,25 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Beatmaps; -using osu.Game.Collections; using osu.Game.Database; using osu.Game.Localisation; using osu.Game.Scoring; -using osu.Game.Skinning; namespace osu.Game.Overlays.Settings.Sections.Maintenance { - public class GeneralSettings : SettingsSubsection + public class BeatmapSettings : SettingsSubsection { - protected override LocalisableString Header => "General"; - + protected override LocalisableString Header => "Beatmaps"; private SettingsButton importBeatmapsButton; private SettingsButton importScoresButton; - private SettingsButton importSkinsButton; - private SettingsButton importCollectionsButton; private SettingsButton deleteBeatmapsButton; + private SettingsButton deleteBeatmapVideosButton; private SettingsButton deleteScoresButton; - private SettingsButton deleteSkinsButton; private SettingsButton restoreButton; private SettingsButton undeleteButton; - private SettingsButton deleteBeatmapVideosButton; [BackgroundDependencyLoader(permitNulls: true)] - private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + private void load(BeatmapManager beatmaps, ScoreManager scores, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) { if (legacyImportManager?.SupportsImportFromStable == true) { @@ -46,6 +40,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance legacyImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true)); } }); + Add(importScoresButton = new SettingsButton + { + Text = MaintenanceSettingsStrings.ImportScoresFromStable, + Action = () => + { + importScoresButton.Enabled.Value = false; + legacyImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true)); + } + }); } Add(deleteBeatmapsButton = new DangerousSettingsButton @@ -74,19 +77,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); - if (legacyImportManager?.SupportsImportFromStable == true) - { - Add(importScoresButton = new SettingsButton - { - Text = MaintenanceSettingsStrings.ImportScoresFromStable, - Action = () => - { - importScoresButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true)); - } - }); - } - Add(deleteScoresButton = new DangerousSettingsButton { Text = MaintenanceSettingsStrings.DeleteAllScores, @@ -99,58 +89,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance })); } }); - - if (legacyImportManager?.SupportsImportFromStable == true) - { - Add(importSkinsButton = new SettingsButton - { - Text = MaintenanceSettingsStrings.ImportSkinsFromStable, - Action = () => - { - importSkinsButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Skins).ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true)); - } - }); - } - - Add(deleteSkinsButton = new DangerousSettingsButton - { - Text = MaintenanceSettingsStrings.DeleteAllSkins, - Action = () => - { - dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => - { - deleteSkinsButton.Enabled.Value = false; - Task.Run(() => skins.Delete()).ContinueWith(t => Schedule(() => deleteSkinsButton.Enabled.Value = true)); - })); - } - }); - - if (collectionManager != null) - { - if (legacyImportManager?.SupportsImportFromStable == true) - { - Add(importCollectionsButton = new SettingsButton - { - Text = MaintenanceSettingsStrings.ImportCollectionsFromStable, - Action = () => - { - importCollectionsButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); - } - }); - } - - Add(new DangerousSettingsButton - { - Text = MaintenanceSettingsStrings.DeleteAllCollections, - Action = () => - { - dialogOverlay?.Push(new MassDeleteConfirmationDialog(collectionManager.DeleteAll)); - } - }); - } - AddRange(new Drawable[] { restoreButton = new SettingsButton diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs new file mode 100644 index 0000000000..98a9a271ab --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.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. + +#nullable disable + +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Localisation; +using osu.Game.Collections; +using osu.Game.Database; +using osu.Game.Localisation; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public class CollectionsSettings : SettingsSubsection + { + protected override LocalisableString Header => "Collections"; + private SettingsButton importCollectionsButton; + + [BackgroundDependencyLoader(permitNulls: true)] + private void load([CanBeNull] CollectionManager collectionManager, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + { + if (collectionManager != null) + { + if (legacyImportManager?.SupportsImportFromStable == true) + { + Add(importCollectionsButton = new SettingsButton + { + Text = MaintenanceSettingsStrings.ImportCollectionsFromStable, + Action = () => + { + importCollectionsButton.Enabled.Value = false; + legacyImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); + } + }); + } + + Add(new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.DeleteAllCollections, + Action = () => + { + dialogOverlay?.Push(new MassDeleteConfirmationDialog(collectionManager.DeleteAll)); + } + }); + } + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs new file mode 100644 index 0000000000..ddeb48725c --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System.Threading.Tasks; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Localisation; +using osu.Game.Database; +using osu.Game.Localisation; +using osu.Game.Scoring; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public class ScoreSettings : SettingsSubsection + { + protected override LocalisableString Header => "Scores"; + private SettingsButton importScoresButton; + private SettingsButton deleteScoresButton; + + [BackgroundDependencyLoader(permitNulls: true)] + private void load(ScoreManager scores, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + { + if (legacyImportManager?.SupportsImportFromStable == true) + { + Add(importScoresButton = new SettingsButton + { + Text = MaintenanceSettingsStrings.ImportScoresFromStable, + Action = () => + { + importScoresButton.Enabled.Value = false; + legacyImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true)); + } + }); + } + + Add(deleteScoresButton = new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.DeleteAllScores, + Action = () => + { + dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => + { + deleteScoresButton.Enabled.Value = false; + Task.Run(() => scores.Delete()).ContinueWith(t => Schedule(() => deleteScoresButton.Enabled.Value = true)); + })); + } + }); + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs new file mode 100644 index 0000000000..6019023c50 --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System.Threading.Tasks; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Localisation; +using osu.Game.Database; +using osu.Game.Localisation; +using osu.Game.Skinning; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public class SkinSettings : SettingsSubsection + { + protected override LocalisableString Header => "Skins"; + private SettingsButton importSkinsButton; + private SettingsButton deleteSkinsButton; + + [BackgroundDependencyLoader(permitNulls: true)] + private void load(SkinManager skins, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + { + if (legacyImportManager?.SupportsImportFromStable == true) + { + Add(importSkinsButton = new SettingsButton + { + Text = MaintenanceSettingsStrings.ImportSkinsFromStable, + Action = () => + { + importSkinsButton.Enabled.Value = false; + legacyImportManager.ImportFromStableAsync(StableContent.Skins).ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true)); + } + }); + } + + Add(deleteSkinsButton = new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.DeleteAllSkins, + Action = () => + { + dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => + { + deleteSkinsButton.Enabled.Value = false; + Task.Run(() => skins.Delete()).ContinueWith(t => Schedule(() => deleteSkinsButton.Enabled.Value = true)); + })); + } + }); + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs index cfe1961bc1..19d02baa4a 100644 --- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs @@ -24,7 +24,10 @@ namespace osu.Game.Overlays.Settings.Sections { Children = new Drawable[] { - new GeneralSettings() + new BeatmapSettings(), + new SkinSettings(), + new CollectionsSettings(), + new ScoreSettings() }; } } From 4aa86b0a8001fa1e0f2f6895f7160d226b80cb6d Mon Sep 17 00:00:00 2001 From: Supersonicboss1 Date: Tue, 21 Jun 2022 23:41:25 +0100 Subject: [PATCH 149/803] fixed duplicates and cleaned up code --- .../Sections/Maintenance/BeatmapSettings.cs | 113 +++++++----------- .../Maintenance/CollectionsSettings.cs | 36 +++--- .../Sections/Maintenance/ScoreSettings.cs | 2 +- .../Sections/Maintenance/SkinSettings.cs | 2 +- 4 files changed, 64 insertions(+), 89 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs index 474f23e4e7..57de259670 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd .Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable @@ -11,7 +11,6 @@ using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Localisation; -using osu.Game.Scoring; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -19,15 +18,13 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { protected override LocalisableString Header => "Beatmaps"; private SettingsButton importBeatmapsButton; - private SettingsButton importScoresButton; private SettingsButton deleteBeatmapsButton; private SettingsButton deleteBeatmapVideosButton; - private SettingsButton deleteScoresButton; private SettingsButton restoreButton; private SettingsButton undeleteButton; [BackgroundDependencyLoader(permitNulls: true)] - private void load(BeatmapManager beatmaps, ScoreManager scores, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + private void load(BeatmapManager beatmaps, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) { if (legacyImportManager?.SupportsImportFromStable == true) { @@ -40,76 +37,54 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance legacyImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true)); } }); - Add(importScoresButton = new SettingsButton + + Add(deleteBeatmapsButton = new DangerousSettingsButton { - Text = MaintenanceSettingsStrings.ImportScoresFromStable, + Text = MaintenanceSettingsStrings.DeleteAllBeatmaps, Action = () => { - importScoresButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true)); + dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => + { + deleteBeatmapsButton.Enabled.Value = false; + Task.Run(() => beatmaps.Delete()).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); + })); + } + }); + + Add(deleteBeatmapVideosButton = new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.DeleteAllBeatmapVideos, + Action = () => + { + dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() => + { + deleteBeatmapVideosButton.Enabled.Value = false; + Task.Run(beatmaps.DeleteAllVideos).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); + })); + } + }); + AddRange(new Drawable[] + { + restoreButton = new SettingsButton + { + Text = MaintenanceSettingsStrings.RestoreAllHiddenDifficulties, + Action = () => + { + restoreButton.Enabled.Value = false; + Task.Run(beatmaps.RestoreAll).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); + } + }, + undeleteButton = new SettingsButton + { + Text = MaintenanceSettingsStrings.RestoreAllRecentlyDeletedBeatmaps, + Action = () => + { + undeleteButton.Enabled.Value = false; + Task.Run(beatmaps.UndeleteAll).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); + } } }); } - - Add(deleteBeatmapsButton = new DangerousSettingsButton - { - Text = MaintenanceSettingsStrings.DeleteAllBeatmaps, - Action = () => - { - dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => - { - deleteBeatmapsButton.Enabled.Value = false; - Task.Run(() => beatmaps.Delete()).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); - })); - } - }); - - Add(deleteBeatmapVideosButton = new DangerousSettingsButton - { - Text = MaintenanceSettingsStrings.DeleteAllBeatmapVideos, - Action = () => - { - dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() => - { - deleteBeatmapVideosButton.Enabled.Value = false; - Task.Run(beatmaps.DeleteAllVideos).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); - })); - } - }); - - Add(deleteScoresButton = new DangerousSettingsButton - { - Text = MaintenanceSettingsStrings.DeleteAllScores, - Action = () => - { - dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => - { - deleteScoresButton.Enabled.Value = false; - Task.Run(() => scores.Delete()).ContinueWith(t => Schedule(() => deleteScoresButton.Enabled.Value = true)); - })); - } - }); - AddRange(new Drawable[] - { - restoreButton = new SettingsButton - { - Text = MaintenanceSettingsStrings.RestoreAllHiddenDifficulties, - Action = () => - { - restoreButton.Enabled.Value = false; - Task.Run(beatmaps.RestoreAll).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); - } - }, - undeleteButton = new SettingsButton - { - Text = MaintenanceSettingsStrings.RestoreAllRecentlyDeletedBeatmaps, - Action = () => - { - undeleteButton.Enabled.Value = false; - Task.Run(beatmaps.UndeleteAll).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); - } - }, - }); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 98a9a271ab..31b889ce0e 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd .Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable @@ -20,30 +20,30 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance [BackgroundDependencyLoader(permitNulls: true)] private void load([CanBeNull] CollectionManager collectionManager, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) { - if (collectionManager != null) - { - if (legacyImportManager?.SupportsImportFromStable == true) - { - Add(importCollectionsButton = new SettingsButton - { - Text = MaintenanceSettingsStrings.ImportCollectionsFromStable, - Action = () => - { - importCollectionsButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); - } - }); - } + if (collectionManager == null) return; - Add(new DangerousSettingsButton + if (legacyImportManager?.SupportsImportFromStable == true) + + { + Add(importCollectionsButton = new SettingsButton { - Text = MaintenanceSettingsStrings.DeleteAllCollections, + Text = MaintenanceSettingsStrings.ImportCollectionsFromStable, Action = () => { - dialogOverlay?.Push(new MassDeleteConfirmationDialog(collectionManager.DeleteAll)); + importCollectionsButton.Enabled.Value = false; + legacyImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); } }); } + + Add(new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.DeleteAllCollections, + Action = () => + { + dialogOverlay?.Push(new MassDeleteConfirmationDialog(collectionManager.DeleteAll)); + } + }); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs index ddeb48725c..26d9d2aa51 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd .Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs index 6019023c50..ad680b363e 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd .Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable From 50728f3fbc29a4e8fd9a39817b924468b50349ad Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 21 Jun 2022 18:31:09 -0700 Subject: [PATCH 150/803] Fix overlay header tab item not working with localisable string --- osu.Game/Overlays/TabControlOverlayHeader.cs | 28 +++++++++++++------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs index 4e32afb86f..2b47e6bc46 100644 --- a/osu.Game/Overlays/TabControlOverlayHeader.cs +++ b/osu.Game/Overlays/TabControlOverlayHeader.cs @@ -8,10 +8,12 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -108,17 +110,25 @@ namespace osu.Game.Overlays public OverlayHeaderTabItem(T value) : base(value) { - if (!(Value is Enum enumValue)) - Text.Text = Value.ToString().ToLower(); - else + switch (Value) { - var localisableDescription = enumValue.GetLocalisableDescription(); - string nonLocalisableDescription = enumValue.GetDescription(); + case LocalisableString localisableString: + Text.Text = localisableString.ToLower(); + break; - // If localisable == non-localisable, then we must have a basic string, so .ToLower() is used. - Text.Text = localisableDescription.Equals(nonLocalisableDescription) - ? nonLocalisableDescription.ToLower() - : localisableDescription; + case Enum enumValue: + var localisableDescription = enumValue.GetLocalisableDescription(); + string nonLocalisableDescription = enumValue.GetDescription(); + + // If localisable == non-localisable, then we must have a basic string, so .ToLower() is used. + Text.Text = localisableDescription.Equals(nonLocalisableDescription) + ? nonLocalisableDescription.ToLower() + : localisableDescription; + break; + + default: + Text.Text = Value.ToString().ToLower(); + break; } Text.Font = OsuFont.GetFont(size: 14); From 07e0cd53a4c81d1256c21864ec71bc849ebf37d9 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 21 Jun 2022 19:06:08 -0700 Subject: [PATCH 151/803] Add back online view container to chat overlay --- osu.Game/Overlays/ChatOverlay.cs | 74 +++++++++++++++++++------------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 81a408598b..c491c0c695 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -18,6 +18,7 @@ using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; +using osu.Game.Online; using osu.Game.Online.Chat; using osu.Game.Overlays.Chat; using osu.Game.Overlays.Chat.ChannelList; @@ -100,23 +101,10 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.X, Height = top_bar_height, }, - channelList = new ChannelList - { - RelativeSizeAxes = Axes.Y, - Width = side_bar_width, - Padding = new MarginPadding { Top = top_bar_height }, - }, new Container { RelativeSizeAxes = Axes.Both, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Padding = new MarginPadding - { - Top = top_bar_height, - Left = side_bar_width, - Bottom = chat_bar_height, - }, + Padding = new MarginPadding { Top = top_bar_height }, Children = new Drawable[] { new Box @@ -124,24 +112,50 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background4, }, - currentChannelContainer = new Container + new OnlineViewContainer("Sign in to chat") { RelativeSizeAxes = Axes.Both, - }, - loading = new LoadingLayer(true), - channelListing = new ChannelListing - { - RelativeSizeAxes = Axes.Both, - }, - }, - }, - textBar = new ChatTextBar - { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Padding = new MarginPadding { Left = side_bar_width }, - }, + Children = new Drawable[] + { + channelList = new ChannelList + { + RelativeSizeAxes = Axes.Y, + Width = side_bar_width, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Padding = new MarginPadding + { + Left = side_bar_width, + Bottom = chat_bar_height, + }, + Children = new Drawable[] + { + currentChannelContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + loading = new LoadingLayer(true), + channelListing = new ChannelListing + { + RelativeSizeAxes = Axes.Both, + }, + }, + }, + textBar = new ChatTextBar + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Padding = new MarginPadding { Left = side_bar_width }, + }, + } + } + } + } }; } From 23254d2ff206146b29fcf36c6643a404b1b33bbd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 22 Jun 2022 13:02:33 +0900 Subject: [PATCH 152/803] Fix broken files --- .../Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs | 2 +- .../Settings/Sections/Maintenance/CollectionsSettings.cs | 2 +- .../Overlays/Settings/Sections/Maintenance/ScoreSettings.cs | 2 +- osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs index 57de259670..7d5c8a9958 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd .Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 31b889ce0e..96583a1e4b 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd .Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs index 26d9d2aa51..ddeb48725c 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd .Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs index ad680b363e..6019023c50 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd .Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable From 1018c9852d0ce5e7b6bf3b0f4e056ea92e5d8664 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 22 Jun 2022 13:07:44 +0900 Subject: [PATCH 153/803] Cleanup nullability --- .../Sections/Maintenance/BeatmapSettings.cs | 18 ++++++++---------- .../Maintenance/CollectionsSettings.cs | 10 ++++------ .../Sections/Maintenance/ScoreSettings.cs | 12 +++++------- .../Sections/Maintenance/SkinSettings.cs | 12 +++++------- 4 files changed, 22 insertions(+), 30 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs index 7d5c8a9958..8039ee7cd9 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Threading.Tasks; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Localisation; @@ -17,14 +14,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public class BeatmapSettings : SettingsSubsection { protected override LocalisableString Header => "Beatmaps"; - private SettingsButton importBeatmapsButton; - private SettingsButton deleteBeatmapsButton; - private SettingsButton deleteBeatmapVideosButton; - private SettingsButton restoreButton; - private SettingsButton undeleteButton; - [BackgroundDependencyLoader(permitNulls: true)] - private void load(BeatmapManager beatmaps, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + private SettingsButton importBeatmapsButton = null!; + private SettingsButton deleteBeatmapsButton = null!; + private SettingsButton deleteBeatmapVideosButton = null!; + private SettingsButton restoreButton = null!; + private SettingsButton undeleteButton = null!; + + [BackgroundDependencyLoader] + private void load(BeatmapManager beatmaps, LegacyImportManager? legacyImportManager, IDialogOverlay? dialogOverlay) { if (legacyImportManager?.SupportsImportFromStable == true) { diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 96583a1e4b..8387582fcb 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -1,9 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Localisation; using osu.Game.Collections; @@ -15,10 +12,11 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public class CollectionsSettings : SettingsSubsection { protected override LocalisableString Header => "Collections"; - private SettingsButton importCollectionsButton; - [BackgroundDependencyLoader(permitNulls: true)] - private void load([CanBeNull] CollectionManager collectionManager, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + private SettingsButton importCollectionsButton = null!; + + [BackgroundDependencyLoader] + private void load(CollectionManager? collectionManager, LegacyImportManager? legacyImportManager, IDialogOverlay? dialogOverlay) { if (collectionManager == null) return; diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs index ddeb48725c..9c1281f371 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Threading.Tasks; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Localisation; using osu.Game.Database; @@ -16,11 +13,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public class ScoreSettings : SettingsSubsection { protected override LocalisableString Header => "Scores"; - private SettingsButton importScoresButton; - private SettingsButton deleteScoresButton; - [BackgroundDependencyLoader(permitNulls: true)] - private void load(ScoreManager scores, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + private SettingsButton importScoresButton = null!; + private SettingsButton deleteScoresButton = null!; + + [BackgroundDependencyLoader] + private void load(ScoreManager scores, LegacyImportManager? legacyImportManager, IDialogOverlay? dialogOverlay) { if (legacyImportManager?.SupportsImportFromStable == true) { diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs index 6019023c50..0fff9fef26 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Threading.Tasks; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Localisation; using osu.Game.Database; @@ -16,11 +13,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public class SkinSettings : SettingsSubsection { protected override LocalisableString Header => "Skins"; - private SettingsButton importSkinsButton; - private SettingsButton deleteSkinsButton; - [BackgroundDependencyLoader(permitNulls: true)] - private void load(SkinManager skins, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + private SettingsButton importSkinsButton = null!; + private SettingsButton deleteSkinsButton = null!; + + [BackgroundDependencyLoader] + private void load(SkinManager skins, LegacyImportManager? legacyImportManager, IDialogOverlay? dialogOverlay) { if (legacyImportManager?.SupportsImportFromStable == true) { From 321920bc857b07f1359ac0407a080c56f2815a6c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 22 Jun 2022 13:08:14 +0900 Subject: [PATCH 154/803] Remove one more nullable disable --- osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs index 19d02baa4a..841a7d0abe 100644 --- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; From ff440fc1a4517d1da519f2ace93aa75a0698c366 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 22 Jun 2022 13:13:04 +0900 Subject: [PATCH 155/803] Fix too many buttons inside condition --- .../Sections/Maintenance/BeatmapSettings.cs | 94 +++++++++---------- .../Maintenance/CollectionsSettings.cs | 1 - 2 files changed, 47 insertions(+), 48 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs index 8039ee7cd9..8c8c2c96ff 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs @@ -35,54 +35,54 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance legacyImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true)); } }); - - Add(deleteBeatmapsButton = new DangerousSettingsButton - { - Text = MaintenanceSettingsStrings.DeleteAllBeatmaps, - Action = () => - { - dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => - { - deleteBeatmapsButton.Enabled.Value = false; - Task.Run(() => beatmaps.Delete()).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); - })); - } - }); - - Add(deleteBeatmapVideosButton = new DangerousSettingsButton - { - Text = MaintenanceSettingsStrings.DeleteAllBeatmapVideos, - Action = () => - { - dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() => - { - deleteBeatmapVideosButton.Enabled.Value = false; - Task.Run(beatmaps.DeleteAllVideos).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); - })); - } - }); - AddRange(new Drawable[] - { - restoreButton = new SettingsButton - { - Text = MaintenanceSettingsStrings.RestoreAllHiddenDifficulties, - Action = () => - { - restoreButton.Enabled.Value = false; - Task.Run(beatmaps.RestoreAll).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); - } - }, - undeleteButton = new SettingsButton - { - Text = MaintenanceSettingsStrings.RestoreAllRecentlyDeletedBeatmaps, - Action = () => - { - undeleteButton.Enabled.Value = false; - Task.Run(beatmaps.UndeleteAll).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); - } - } - }); } + + Add(deleteBeatmapsButton = new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.DeleteAllBeatmaps, + Action = () => + { + dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => + { + deleteBeatmapsButton.Enabled.Value = false; + Task.Run(() => beatmaps.Delete()).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); + })); + } + }); + + Add(deleteBeatmapVideosButton = new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.DeleteAllBeatmapVideos, + Action = () => + { + dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() => + { + deleteBeatmapVideosButton.Enabled.Value = false; + Task.Run(beatmaps.DeleteAllVideos).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); + })); + } + }); + AddRange(new Drawable[] + { + restoreButton = new SettingsButton + { + Text = MaintenanceSettingsStrings.RestoreAllHiddenDifficulties, + Action = () => + { + restoreButton.Enabled.Value = false; + Task.Run(beatmaps.RestoreAll).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); + } + }, + undeleteButton = new SettingsButton + { + Text = MaintenanceSettingsStrings.RestoreAllRecentlyDeletedBeatmaps, + Action = () => + { + undeleteButton.Enabled.Value = false; + Task.Run(beatmaps.UndeleteAll).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); + } + } + }); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 8387582fcb..74e9ebc084 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -21,7 +21,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance if (collectionManager == null) return; if (legacyImportManager?.SupportsImportFromStable == true) - { Add(importCollectionsButton = new SettingsButton { From f561d5b4ad5a96d88690c14126efe41ee3e946eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jun 2022 14:32:42 +0900 Subject: [PATCH 156/803] Change always-discarded-parameter inspection to hint See https://github.com/ppy/osu/blob/f8830c6850128456266c82de83273204f8b74ac0/osu.Game/Rulesets/Scoring/ScoreProcessor.cs#L518 Coming up as a suggestion (the only one in the solution). --- osu.sln.DotSettings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 286a1eb29f..e732b25951 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -126,6 +126,8 @@ HINT HINT WARNING + HINT + HINT HINT HINT HINT From 950551f4fda91db3ab8a078b29d2f4977fd3d370 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 22 Jun 2022 15:33:42 +0900 Subject: [PATCH 157/803] Fix metronome arm being stuck white when paused 'close enough' to center --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 37fc38404f..3895959982 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -276,6 +276,7 @@ namespace osu.Game.Screens.Edit.Timing if (Precision.AlmostEquals(swing.Rotation, 0, 1)) { swing.RotateTo(0, 60, Easing.OutQuint); + stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); sampleLatch?.Play(); return; } From 948c28f41532bd3b8406ce371cb453bdf1dbaca1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jun 2022 20:34:05 +0900 Subject: [PATCH 158/803] Fix collection modified during `BlockAllOperations` if any subscriptions have been established --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index cf9109eb8b..68f9dcb541 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -822,7 +822,7 @@ namespace osu.Game.Database // Before disposing the update context, clean up all subscriptions. // Note that in the case of realm notification subscriptions, this is not really required (they will be cleaned up by disposal). // In the case of custom subscriptions, we want them to fire before the update realm is disposed in case they do any follow-up work. - foreach (var action in customSubscriptionsResetMap) + foreach (var action in customSubscriptionsResetMap.ToArray()) { action.Value?.Dispose(); customSubscriptionsResetMap[action.Key] = null; From bf5a7e3f2aa077fcbddaea82e510b74975bb2973 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 22 Jun 2022 17:01:28 -0400 Subject: [PATCH 159/803] add locked room filter --- .../Lounge/Components/FilterCriteria.cs | 1 + .../Lounge/Components/RoomsContainer.cs | 2 ++ .../OnlinePlay/Lounge/LoungeSubScreen.cs | 19 +++++++++++++++---- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs index 67e805986a..45c1b9b394 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs @@ -13,5 +13,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components public RoomStatusFilter Status; public string Category; public RulesetInfo Ruleset; + public bool Locked = true; } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 2c3a2997cc..908505e386 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -87,6 +87,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components matchingFilter &= r.FilterTerms.Any(term => term.ToString().Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); } + matchingFilter &= criteria.Locked || !r.Room.HasPassword.Value; + r.MatchingFilter = matchingFilter; } }); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index f7f3c27ede..a726ff632f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -85,6 +85,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private RoomsContainer roomsContainer; private SearchTextBox searchTextBox; private Dropdown statusDropdown; + private Checkbox lockedCheckbox; [BackgroundDependencyLoader(true)] private void load([CanBeNull] IdleTracker idleTracker) @@ -224,9 +225,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { SearchString = searchTextBox.Current.Value, Ruleset = ruleset.Value, - Status = statusDropdown.Current.Value + Status = statusDropdown.Current.Value, + Locked = lockedCheckbox.Current.Value, }; + #endregion + protected virtual IEnumerable CreateFilterControls() { statusDropdown = new SlimEnumDropdown @@ -237,10 +241,17 @@ namespace osu.Game.Screens.OnlinePlay.Lounge statusDropdown.Current.BindValueChanged(_ => UpdateFilter()); - yield return statusDropdown; - } + lockedCheckbox = new OsuTabControlCheckbox + { + Current = new Bindable(true), + RelativeSizeAxes = Axes.None, + Text = @"Show Locked", + }; - #endregion + lockedCheckbox.Current.BindValueChanged(_ => UpdateFilter()); + + return new Drawable[] { lockedCheckbox, statusDropdown, }; + } public override void OnEntering(ScreenTransitionEvent e) { From a03cfbc2d4ec60c07c3ea2156dad084a7e123247 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 22 Jun 2022 17:01:42 -0400 Subject: [PATCH 160/803] add test coverage --- .../TestSceneLoungeRoomsContainer.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 171f8eea52..363a2b4a4d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -157,6 +157,26 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3); } + [Test] + public void TestLockedFiltering() + { + AddStep("add rooms", () => + { + RoomManager.AddRooms(1, withPassword: true); + RoomManager.AddRooms(1, withPassword: false); + }); + + AddUntilStep("both rooms shown", () => container.Rooms.Count(r => r.IsPresent) == 2); + + AddStep("filter locked rooms", () => container.Filter.Value = new FilterCriteria { Locked = false }); + + AddUntilStep("locked room hidden", () => container.Rooms.All(r => !r.Room.HasPassword.Value)); + + AddStep("unfilter locked rooms", () => container.Filter.SetDefault()); + + AddUntilStep("both rooms shown", () => container.Rooms.Count(r => r.IsPresent) == 2); + } + [Test] public void TestPasswordProtectedRooms() { From bc557bacf05199aecd51adec69536eb1cdb10a9a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 14:10:17 +0900 Subject: [PATCH 161/803] Remove room null assertion in `TestMultiplayerClient.ChangeState` to avoid assert This method is run from async contexts, but `get_Room` asserts update thread which is not the case. Was causing silent test failures (showing as pass but failing `dotnet-test` via return code): TestSceneMultiplayer.TestGameplayFlow ``` [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: Score preparation failed! [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: NUnit.Framework.AssertionException: : [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: at osu.Framework.Logging.ThrowingTraceListener.Fail(String message1, String message2) [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: at System.Diagnostics.TraceInternal.Fail(String message, String detailMessage) [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: at System.Diagnostics.TraceInternal.TraceProvider.Fail(String message, String detailMessage) [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: at System.Diagnostics.Debug.Fail(String message, String detailMessage) [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: at osu.Game.Online.Multiplayer.MultiplayerClient.get_Room() in /opt/buildagent/work/ecd860037212ac52/osu.Game/Online/Multiplayer/MultiplayerClient.cs:line 98 [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: at osu.Game.Tests.Visual.Multiplayer.TestMultiplayerClient.ChangeState(MultiplayerUserState newState) in /opt/buildagent/work/ecd860037212ac52/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs:line 275 [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: at osu.Game.Screens.OnlinePlay.Multiplayer.MultiplayerPlayer.PrepareScoreForResultsAsync(Score score) in /opt/buildagent/work/ecd860037212ac52/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs:line 223 [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: at osu.Game.Screens.Play.Player.prepareScoreForResults() in /opt/buildagent/work/ecd860037212ac52/osu.Game/Screens/Play/Player.cs:line 747 ``` --- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index e5a1236abf..f2024a3107 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -272,8 +272,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task ChangeState(MultiplayerUserState newState) { - Debug.Assert(Room != null); - if (newState == MultiplayerUserState.Idle && LocalUser?.State == MultiplayerUserState.WaitingForLoad) return Task.CompletedTask; From ed9c55a7765d8e9771d6dd3b6ceca350c5781a3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 14:33:07 +0900 Subject: [PATCH 162/803] Ban usage of `ManualResetEventSlim.Wait()` without a timeout value --- CodeAnalysis/BannedSymbols.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index b72df0a306..77197c4c51 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -16,3 +16,4 @@ M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList,NotificationCallbackDelegate) instead. M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks. P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks. +M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever. From 7ef8b7df5f788427d2d0c0309923babb073b23bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 14:33:20 +0900 Subject: [PATCH 163/803] Add timeout for all `ManualResetEventSlim.Wait` invocations Timeout values were taken as best-guesses of upper values we'd expect from sane execution. --- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 4 ++-- osu.Game.Tests/Database/GeneralUsageTests.cs | 4 ++-- .../Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs | 2 +- osu.Game/Database/DatabaseContextFactory.cs | 2 +- osu.Game/Graphics/ScreenshotManager.cs | 2 +- osu.Game/OsuGameBase.cs | 3 ++- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index bdc24315bf..b26fc3fd59 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -76,7 +76,7 @@ namespace osu.Game.Benchmarks } }); - done.Wait(); + done.Wait(60000); } [Benchmark] @@ -115,7 +115,7 @@ namespace osu.Game.Benchmarks } }); - done.Wait(); + done.Wait(60000); } [Benchmark] diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index d10ab2ad2b..c28a0c63af 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -79,11 +79,11 @@ namespace osu.Game.Tests.Database { hasThreadedUsage.Set(); - stopThreadedUsage.Wait(); + stopThreadedUsage.Wait(60000); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler); - hasThreadedUsage.Wait(); + hasThreadedUsage.Wait(60000); Assert.Throws(() => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs index fc3079cba0..5b4e0a88aa 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Task.Run(() => { - allowResponseCallback.Wait(); + allowResponseCallback.Wait(10000); allowResponseCallback.Reset(); Schedule(() => d?.Invoke("Incorrect password")); }); diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 8af649fbfa..075383c6e1 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -209,6 +209,6 @@ namespace osu.Game.Database public void SetMigrationCompletion() => migrationComplete.Set(); - public void WaitForMigrationCompletion() => migrationComplete.Wait(); + public void WaitForMigrationCompletion() => migrationComplete.Wait(300000); } } diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index 7147f89dd0..fe9865dad6 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -103,7 +103,7 @@ namespace osu.Game.Graphics framesWaitedEvent.Set(); }, 10, true); - framesWaitedEvent.Wait(); + framesWaitedEvent.Wait(1000); waitDelegate.Cancel(); } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 58929ce4a4..7bbad3bb72 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -451,7 +451,8 @@ namespace osu.Game readyToRun.Set(); }, false); - readyToRun.Wait(); + if (!readyToRun.Wait(30000)) + throw new TimeoutException("Attempting to block for migration took too long."); bool? cleanupSucceded = (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); From 51268d0cc8333bb7f74e74e6fc776b698ea47d63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 15:28:20 +0900 Subject: [PATCH 164/803] Throw on `Wait` failure in a few remaining cases --- osu.Game/Database/DatabaseContextFactory.cs | 6 +++++- osu.Game/Graphics/ScreenshotManager.cs | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 075383c6e1..af91fb4971 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -209,6 +209,10 @@ namespace osu.Game.Database public void SetMigrationCompletion() => migrationComplete.Set(); - public void WaitForMigrationCompletion() => migrationComplete.Wait(300000); + public void WaitForMigrationCompletion() + { + if (!migrationComplete.Wait(300000)) + throw new TimeoutException("Migration took too long (likely stuck)."); + } } } diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index fe9865dad6..2cc9e63c87 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -103,7 +103,9 @@ namespace osu.Game.Graphics framesWaitedEvent.Set(); }, 10, true); - framesWaitedEvent.Wait(1000); + if (!framesWaitedEvent.Wait(1000)) + throw new TimeoutException("Screenshot data did not arrive in a timely fashion"); + waitDelegate.Cancel(); } } From 9be13f88fd3711bddce59ec63522e0aff22b499b Mon Sep 17 00:00:00 2001 From: 63411 <62799417+molneya@users.noreply.github.com> Date: Thu, 23 Jun 2022 15:51:28 +0800 Subject: [PATCH 165/803] revert customAccuracy changes --- .../Difficulty/ManiaPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 216dfe6c47..a925e7c0ac 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -73,6 +73,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty /// /// Accuracy used to weight judgements independently from the score's actual accuracy. /// - private double customAccuracy => Math.Max((countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50 - countMiss * 100) / (totalHits * 320), 0); + private double customAccuracy => (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320); } } From 158f0a1f233d14234ddc999dfe6b5d763f41e733 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 17:38:34 +0900 Subject: [PATCH 166/803] Remove remaining `Room!=null` checks in `TestMultiplayerClient` where possible --- .../Visual/Multiplayer/TestMultiplayerClient.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index f2024a3107..6b7495762a 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -87,8 +87,6 @@ namespace osu.Game.Tests.Visual.Multiplayer switch (Room?.MatchState) { case TeamVersusRoomState teamVersus: - Debug.Assert(Room != null); - // simulate the server's automatic assignment of users to teams on join. // the "best" team is the one with the least users on it. int bestTeam = teamVersus.Teams @@ -103,6 +101,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void RemoveUser(APIUser user) { Debug.Assert(Room != null); + ((IMultiplayerClient)this).UserLeft(new MultiplayerRoomUser(user.Id)); Schedule(() => @@ -114,25 +113,23 @@ namespace osu.Game.Tests.Visual.Multiplayer public void ChangeRoomState(MultiplayerRoomState newState) { - Debug.Assert(Room != null); ((IMultiplayerClient)this).RoomStateChanged(newState); } public void ChangeUserState(int userId, MultiplayerUserState newState) { - Debug.Assert(Room != null); - ((IMultiplayerClient)this).UserStateChanged(userId, newState); updateRoomStateIfRequired(); } private void updateRoomStateIfRequired() { - Debug.Assert(Room != null); Debug.Assert(APIRoom != null); Schedule(() => { + Debug.Assert(Room != null); + switch (Room.State) { case MultiplayerRoomState.Open: @@ -179,8 +176,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public void ChangeUserBeatmapAvailability(int userId, BeatmapAvailability newBeatmapAvailability) { - Debug.Assert(Room != null); - ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(userId, newBeatmapAvailability); } @@ -290,7 +285,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public void ChangeUserMods(int userId, IEnumerable newMods) { - Debug.Assert(Room != null); ((IMultiplayerClient)this).UserModsChanged(userId, newMods.ToList()); } @@ -338,7 +332,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task AbortGameplay() { - Debug.Assert(Room != null); Debug.Assert(LocalUser != null); ChangeUserState(LocalUser.UserID, MultiplayerUserState.Idle); From 4a316fad2fedf334db9761764a46d59fb9e18e3a Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 23 Jun 2022 17:18:37 +0900 Subject: [PATCH 167/803] Add audio feedback for rearranging list items --- .../OsuRearrangeableListContainer.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs index 84d0d1eb32..a314992287 100644 --- a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs +++ b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs @@ -3,9 +3,14 @@ #nullable disable +using System.Collections.Specialized; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; namespace osu.Game.Graphics.Containers { @@ -18,11 +23,44 @@ namespace osu.Game.Graphics.Containers protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); + private Sample sampleSwap; + private double sampleLastPlaybackTime; + protected sealed override RearrangeableListItem CreateDrawable(TModel item) => CreateOsuDrawable(item).With(d => { d.DragActive.BindTo(DragActive); }); protected abstract OsuRearrangeableListItem CreateOsuDrawable(TModel item); + + protected OsuRearrangeableListContainer() + { + Items.CollectionChanged += (_, args) => + { + if (args.Action == NotifyCollectionChangedAction.Move) + playSwapSample(); + }; + } + + private void playSwapSample() + { + if (Time.Current - sampleLastPlaybackTime <= 35) + return; + + var channel = sampleSwap?.GetChannel(); + if (channel == null) + return; + + channel.Frequency.Value = 0.96 + RNG.NextDouble(0.08); + channel.Play(); + sampleLastPlaybackTime = Time.Current; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleSwap = audio.Samples.Get(@"UI/item-swap"); + sampleLastPlaybackTime = Time.Current; + } } } From d6b073ebad526db327ad0fcb43d4c9f9dbaf2fbb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 16:39:33 +0900 Subject: [PATCH 168/803] Move `DifficultyRetrieve` to own class and split both pathways into separate constructors --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 63 ++++---------- .../Beatmaps/Drawables/DifficultyRetriever.cs | 85 +++++++++++++++++++ 2 files changed, 100 insertions(+), 48 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index a1b0f04aae..a5b13d10e6 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.Threading; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -63,7 +62,8 @@ namespace osu.Game.Beatmaps.Drawables /// The mods to show the difficulty with. /// Whether to display a tooltip when hovered. /// Whether to perform difficulty lookup (including calculation if necessary). - public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true) + public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true, + bool performBackgroundDifficultyLookup = true) : this(beatmapInfo, shouldShowTooltip, performBackgroundDifficultyLookup) { this.ruleset = ruleset ?? beatmapInfo.Ruleset; @@ -125,13 +125,21 @@ namespace osu.Game.Beatmaps.Drawables }; if (performBackgroundDifficultyLookup) - iconContainer.Add(new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmapInfo, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0)); + iconContainer.Add(new DelayedLoadUnloadWrapper(createDifficultyRetriever, 0)); else difficultyBindable.Value = new StarDifficulty(beatmapInfo.StarRating, 0); difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars)); } + private Drawable createDifficultyRetriever() + { + if (ruleset != null && mods != null) + return new DifficultyRetriever(beatmapInfo, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }; + + return new DifficultyRetriever(beatmapInfo) { StarDifficulty = { BindTarget = difficultyBindable } }; + } + private Drawable getRulesetIcon() { int? onlineID = (ruleset ?? beatmapInfo.Ruleset).OnlineID; @@ -142,51 +150,10 @@ namespace osu.Game.Beatmaps.Drawables return new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }; } - ITooltip IHasCustomTooltip.GetCustomTooltip() => new DifficultyIconTooltip(); + ITooltip IHasCustomTooltip. + GetCustomTooltip() => new DifficultyIconTooltip(); - DifficultyIconTooltipContent IHasCustomTooltip.TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmapInfo, difficultyBindable) : null; - - private class DifficultyRetriever : Component - { - public readonly Bindable StarDifficulty = new Bindable(); - - private readonly IBeatmapInfo beatmapInfo; - private readonly IRulesetInfo ruleset; - private readonly IReadOnlyList mods; - - private CancellationTokenSource difficultyCancellation; - - [Resolved] - private BeatmapDifficultyCache difficultyCache { get; set; } - - public DifficultyRetriever(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, IReadOnlyList mods) - { - this.beatmapInfo = beatmapInfo; - this.ruleset = ruleset; - this.mods = mods; - } - - private IBindable localStarDifficulty; - - [BackgroundDependencyLoader] - private void load() - { - difficultyCancellation = new CancellationTokenSource(); - localStarDifficulty = ruleset != null - ? difficultyCache.GetBindableDifficulty(beatmapInfo, ruleset, mods, difficultyCancellation.Token) - : difficultyCache.GetBindableDifficulty(beatmapInfo, difficultyCancellation.Token); - localStarDifficulty.BindValueChanged(d => - { - if (d.NewValue is StarDifficulty diff) - StarDifficulty.Value = diff; - }); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - difficultyCancellation?.Cancel(); - } - } + DifficultyIconTooltipContent IHasCustomTooltip. + TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmapInfo, difficultyBindable) : null; } } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs b/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs new file mode 100644 index 0000000000..a993f0657b --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs @@ -0,0 +1,85 @@ +// 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.Collections.Generic; +using System.Threading; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Beatmaps.Drawables +{ + /// + /// A component solely responsible for calculating difficulty in the background. + /// Intended for use with to only run processing when usage is on-screen. + /// + public class DifficultyRetriever : Component + { + /// + /// The bindable star difficulty. + /// + public IBindable StarDifficulty => starDifficulty; + + private readonly Bindable starDifficulty = new Bindable(); + + private readonly IBeatmapInfo beatmapInfo; + + private readonly IRulesetInfo? ruleset; + private readonly IReadOnlyList? mods; + + private readonly CancellationTokenSource difficultyCancellation = new CancellationTokenSource(); + + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } = null!; + + /// + /// Construct a difficulty retriever that tracks the current ruleset / mod selection. + /// + /// The beatmap to use for calculation. + public DifficultyRetriever(IBeatmapInfo beatmapInfo) + { + this.beatmapInfo = beatmapInfo; + } + + /// + /// Construct a difficulty retriever that is calculated only once for the specified ruleset / mod combination. + /// This will not track global ruleset and mod changes. + /// + /// The beatmap to use for calculation. + /// The ruleset to use for calculation. + /// The mods to use for calculation. + public DifficultyRetriever(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, IReadOnlyList mods) + { + this.beatmapInfo = beatmapInfo; + this.ruleset = ruleset; + this.mods = mods; + } + + private IBindable localStarDifficulty = null!; + + [BackgroundDependencyLoader] + private void load() + { + localStarDifficulty = ruleset != null + ? difficultyCache.GetBindableDifficulty(beatmapInfo, ruleset, mods, difficultyCancellation.Token) + : difficultyCache.GetBindableDifficulty(beatmapInfo, difficultyCancellation.Token); + + localStarDifficulty.BindValueChanged(d => + { + if (d.NewValue is StarDifficulty diff) + starDifficulty.Value = diff; + }); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + difficultyCancellation.Cancel(); + } + } +} From 3a83e5684c64226005ddddc349af36f1defba66a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 16:55:22 +0900 Subject: [PATCH 169/803] Tidy up `DifficultyIcon` --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index a5b13d10e6..48ee1e1fc8 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -26,8 +26,6 @@ namespace osu.Game.Beatmaps.Drawables { public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip { - private readonly Container iconContainer; - /// /// Size of this difficulty icon. /// @@ -46,14 +44,19 @@ namespace osu.Game.Beatmaps.Drawables [CanBeNull] private readonly IReadOnlyList mods; + [Resolved] + private IRulesetStore rulesets { get; set; } + private readonly bool shouldShowTooltip; private readonly bool performBackgroundDifficultyLookup; - private readonly Bindable difficultyBindable = new Bindable(); - private Drawable background; + private readonly Container iconContainer; + + private readonly Bindable difficultyBindable = new Bindable(); + /// /// Creates a new with a given and combination. /// @@ -87,9 +90,6 @@ namespace osu.Game.Beatmaps.Drawables InternalChild = iconContainer = new Container { Size = new Vector2(20f) }; } - [Resolved] - private IRulesetStore rulesets { get; set; } - [BackgroundDependencyLoader] private void load(OsuColour colours) { From 4a2ca4394b7ccd79fe32232ce3a84fa93a3f5234 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 18:37:36 +0900 Subject: [PATCH 170/803] Remove unused `ModeTypeInfo` class --- .../OnlinePlay/Components/ModeTypeInfo.cs | 78 ------------------- 1 file changed, 78 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs diff --git a/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs b/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs deleted file mode 100644 index 641776a9e8..0000000000 --- a/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps.Drawables; -using osu.Game.Rulesets; -using osuTK; - -namespace osu.Game.Screens.OnlinePlay.Components -{ - public class ModeTypeInfo : OnlinePlayComposite - { - private const float height = 28; - private const float transition_duration = 100; - - [Resolved] - private RulesetStore rulesets { get; set; } - - private Container drawableRuleset; - - public ModeTypeInfo() - { - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load() - { - Container gameTypeContainer; - - InternalChild = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5f, 0f), - LayoutDuration = 100, - Children = new[] - { - drawableRuleset = new Container - { - AutoSizeAxes = Axes.Both, - }, - gameTypeContainer = new Container - { - AutoSizeAxes = Axes.Both, - }, - }, - }; - - Type.BindValueChanged(type => gameTypeContainer.Child = new DrawableGameType(type.NewValue) { Size = new Vector2(height) }, true); - - Playlist.CollectionChanged += (_, __) => updateBeatmap(); - - updateBeatmap(); - } - - private void updateBeatmap() - { - var item = Playlist.FirstOrDefault(); - var ruleset = item == null ? null : rulesets.GetRuleset(item.RulesetID)?.CreateInstance(); - - if (item?.Beatmap != null && ruleset != null) - { - var mods = item.RequiredMods.Select(m => m.ToMod(ruleset)).ToArray(); - - drawableRuleset.FadeIn(transition_duration); - drawableRuleset.Child = new DifficultyIcon(item.Beatmap, ruleset.RulesetInfo, mods) { Size = new Vector2(height) }; - } - else - drawableRuleset.FadeOut(transition_duration); - } - } -} From 7dec530ca5b1a3cdf18c56f76863d9eaf3a2c633 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 18:37:53 +0900 Subject: [PATCH 171/803] Split out simple `DifficultyIcon` with no calculation overhead and update usages --- .../Drawables/CalculatingDifficultyIcon.cs | 80 +++++++++++++++++ osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 89 ++++++------------- .../Drawables/GroupedDifficultyIcon.cs | 2 +- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 3 +- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- .../Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Carousel/FilterableDifficultyIcon.cs | 4 +- .../Select/Carousel/SetPanelContent.cs | 8 +- 8 files changed, 120 insertions(+), 70 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs diff --git a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs new file mode 100644 index 0000000000..5f914f446c --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs @@ -0,0 +1,80 @@ +#nullable enable +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osuTK; + +namespace osu.Game.Beatmaps.Drawables +{ + /// + /// A difficulty icon which automatically calculates difficulty in the background. + /// + public class CalculatingDifficultyIcon : CompositeDrawable + { + /// + /// Size of this difficulty icon. + /// + public new Vector2 Size + { + get => difficultyIcon.Size; + set => difficultyIcon.Size = value; + } + + private readonly IRulesetInfo? ruleset; + + private readonly IReadOnlyList? mods; + + private readonly IBeatmapInfo beatmapInfo; + + private readonly DifficultyIcon difficultyIcon; + + /// + /// Creates a new with a given and combination. + /// + /// The beatmap to show the difficulty of. + /// The ruleset to show the difficulty with. + /// The mods to show the difficulty with. + /// Whether to display a tooltip when hovered. + /// Whether to perform difficulty lookup (including calculation if necessary). + public CalculatingDifficultyIcon(IBeatmapInfo beatmapInfo, IRulesetInfo? ruleset, IReadOnlyList? mods, bool shouldShowTooltip = true, + bool performBackgroundDifficultyLookup = true) + : this(beatmapInfo, shouldShowTooltip, performBackgroundDifficultyLookup) + { + this.ruleset = ruleset ?? beatmapInfo.Ruleset; + this.mods = mods ?? Array.Empty(); + } + + /// + /// Creates a new that follows the currently-selected ruleset and mods. + /// + /// The beatmap to show the difficulty of. + /// Whether to display a tooltip when hovered. + /// Whether to perform difficulty lookup (including calculation if necessary). + public CalculatingDifficultyIcon(IBeatmapInfo beatmapInfo, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true) + { + this.beatmapInfo = beatmapInfo ?? throw new ArgumentNullException(nameof(beatmapInfo)); + + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + difficultyIcon = new DifficultyIcon(beatmapInfo, beatmapInfo.Ruleset), + new DelayedLoadUnloadWrapper(createDifficultyRetriever, 0) + }; + } + + private Drawable createDifficultyRetriever() + { + if (ruleset != null && mods != null) + return new DifficultyRetriever(beatmapInfo, ruleset, mods) { StarDifficulty = { BindTarget = difficultyIcon.Current } }; + + return new DifficultyRetriever(beatmapInfo) { StarDifficulty = { BindTarget = difficultyIcon.Current } }; + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 48ee1e1fc8..d1b15cba76 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -1,11 +1,6 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using System; -using System.Collections.Generic; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -15,16 +10,16 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables { - public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip + public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip, IHasCurrentValue { /// /// Size of this difficulty icon. @@ -35,58 +30,46 @@ namespace osu.Game.Beatmaps.Drawables set => iconContainer.Size = value; } - [NotNull] - private readonly IBeatmapInfo beatmapInfo; + private readonly IBeatmapInfo? beatmap; - [CanBeNull] private readonly IRulesetInfo ruleset; - [CanBeNull] - private readonly IReadOnlyList mods; - - [Resolved] - private IRulesetStore rulesets { get; set; } - - private readonly bool shouldShowTooltip; - - private readonly bool performBackgroundDifficultyLookup; - - private Drawable background; + private Drawable background = null!; private readonly Container iconContainer; - private readonly Bindable difficultyBindable = new Bindable(); + private readonly BindableWithCurrent difficulty = new BindableWithCurrent(); + + public virtual Bindable Current + { + get => difficulty.Current; + set => difficulty.Current = value; + } + + [Resolved] + private IRulesetStore rulesets { get; set; } = null!; /// - /// Creates a new with a given and combination. + /// Creates a new with a tooltip. Will use provided beatmap's for initial value. /// - /// The beatmap to show the difficulty of. - /// The ruleset to show the difficulty with. - /// The mods to show the difficulty with. - /// Whether to display a tooltip when hovered. - /// Whether to perform difficulty lookup (including calculation if necessary). - public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true, - bool performBackgroundDifficultyLookup = true) - : this(beatmapInfo, shouldShowTooltip, performBackgroundDifficultyLookup) + /// The beatmap to be displayed in the tooltip, and to be used for the initial star rating value. + /// An optional ruleset to be used for the icon display, in place of the beatmap's ruleset. + public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset) + : this(ruleset ?? beatmap.Ruleset) { - this.ruleset = ruleset ?? beatmapInfo.Ruleset; - this.mods = mods ?? Array.Empty(); + this.beatmap = beatmap; + Current.Value = new StarDifficulty(beatmap.StarRating, 0); } /// - /// Creates a new that follows the currently-selected ruleset and mods. + /// Creates a new with no tooltip. /// - /// The beatmap to show the difficulty of. - /// Whether to display a tooltip when hovered. - /// Whether to perform difficulty lookup (including calculation if necessary). - public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true) + /// The ruleset to be used for the icon display. + public DifficultyIcon(IRulesetInfo ruleset) { - this.beatmapInfo = beatmapInfo ?? throw new ArgumentNullException(nameof(beatmapInfo)); - this.shouldShowTooltip = shouldShowTooltip; - this.performBackgroundDifficultyLookup = performBackgroundDifficultyLookup; + this.ruleset = ruleset; AutoSizeAxes = Axes.Both; - InternalChild = iconContainer = new Container { Size = new Vector2(20f) }; } @@ -111,7 +94,6 @@ namespace osu.Game.Beatmaps.Drawables Child = background = new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.ForStarDifficulty(beatmapInfo.StarRating) // Default value that will be re-populated once difficulty calculation completes }, }, new ConstrainedIconContainer @@ -124,25 +106,12 @@ namespace osu.Game.Beatmaps.Drawables }, }; - if (performBackgroundDifficultyLookup) - iconContainer.Add(new DelayedLoadUnloadWrapper(createDifficultyRetriever, 0)); - else - difficultyBindable.Value = new StarDifficulty(beatmapInfo.StarRating, 0); - - difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars)); - } - - private Drawable createDifficultyRetriever() - { - if (ruleset != null && mods != null) - return new DifficultyRetriever(beatmapInfo, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }; - - return new DifficultyRetriever(beatmapInfo) { StarDifficulty = { BindTarget = difficultyBindable } }; + Current.BindValueChanged(difficulty => background.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars), true); } private Drawable getRulesetIcon() { - int? onlineID = (ruleset ?? beatmapInfo.Ruleset).OnlineID; + int? onlineID = ruleset.OnlineID; if (onlineID >= 0 && rulesets.GetRuleset(onlineID.Value)?.CreateInstance() is Ruleset rulesetInstance) return rulesetInstance.CreateIcon(); @@ -154,6 +123,6 @@ namespace osu.Game.Beatmaps.Drawables GetCustomTooltip() => new DifficultyIconTooltip(); DifficultyIconTooltipContent IHasCustomTooltip. - TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmapInfo, difficultyBindable) : null; + TooltipContent => (beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current) : null)!; } } diff --git a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs index 15ca4c60d4..a713592246 100644 --- a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs @@ -19,7 +19,7 @@ namespace osu.Game.Beatmaps.Drawables /// /// Used in cases when there are too many difficulty icons to show. /// - public class GroupedDifficultyIcon : DifficultyIcon + public class GroupedDifficultyIcon : CalculatingDifficultyIcon { public GroupedDifficultyIcon(IEnumerable beatmaps, IRulesetInfo ruleset, Color4 counterColour) : base(beatmaps.OrderBy(b => b.StarRating).Last(), ruleset, null, false) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 8d89bb3cc7..2efdb71204 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -274,8 +274,9 @@ namespace osu.Game.Overlays.BeatmapSet Alpha = 0.5f } }, - icon = new DifficultyIcon(beatmapInfo, shouldShowTooltip: false) + icon = new DifficultyIcon(beatmapInfo.Ruleset) { + Current = { Value = new StarDifficulty(beatmapInfo.StarRating, 0) }, Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(size - tile_icon_padding * 2), diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 53519b8b00..a3a176477e 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -266,7 +266,7 @@ namespace osu.Game.Screens.OnlinePlay } if (beatmap != null) - difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(icon_height) }; + difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset) { Size = new Vector2(icon_height) }; else difficultyIconContainer.Clear(); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 1bea689e7c..79eee63172 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select.Carousel Origin = Anchor.CentreLeft, Children = new Drawable[] { - new DifficultyIcon(beatmapInfo, shouldShowTooltip: false) + new CalculatingDifficultyIcon(beatmapInfo, shouldShowTooltip: false) { Scale = new Vector2(1.8f), }, diff --git a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs index b0841caa47..3f7d5c13ad 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs @@ -10,7 +10,7 @@ using osu.Game.Beatmaps.Drawables; namespace osu.Game.Screens.Select.Carousel { - public class FilterableDifficultyIcon : DifficultyIcon + public class FilterableDifficultyIcon : CalculatingDifficultyIcon { private readonly BindableBool filtered = new BindableBool(); @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Select.Carousel public readonly CarouselBeatmap Item; public FilterableDifficultyIcon(CarouselBeatmap item) - : base(item.BeatmapInfo, performBackgroundDifficultyLookup: false) + : base(item.BeatmapInfo) { filtered.BindTo(item.Filtered); filtered.ValueChanged += isFiltered => Schedule(() => this.FadeTo(isFiltered.NewValue ? 0.1f : 1, 100)); diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 73a835d67f..c5e5a70d59 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Select.Carousel TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, Status = beatmapSet.Status }, - new FillFlowContainer + new FillFlowContainer { AutoSizeAxes = Axes.Both, Spacing = new Vector2(3), @@ -87,13 +87,13 @@ namespace osu.Game.Screens.Select.Carousel private const int maximum_difficulty_icons = 18; - private IEnumerable getDifficultyIcons() + private IEnumerable getDifficultyIcons() { var beatmaps = carouselSet.Beatmaps.ToList(); return beatmaps.Count > maximum_difficulty_icons - ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset) - .Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset)) + ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset) + .Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset)) : beatmaps.Select(b => new FilterableDifficultyIcon(b)); } } From 01da6f20b380923a196885e4a789fd29d21e668e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 18:53:21 +0900 Subject: [PATCH 172/803] Tidy up all remaining usages --- .../SongSelect/TestScenePlaySongSelect.cs | 4 +- .../Drawables/CalculatingDifficultyIcon.cs | 40 ++++--------------- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 9 ++++- .../Beatmaps/Drawables/DifficultyRetriever.cs | 2 - .../Drawables/GroupedDifficultyIcon.cs | 39 ------------------ .../Carousel/DrawableCarouselBeatmap.cs | 3 +- .../Carousel/FilterableDifficultyIcon.cs | 2 +- ...icultyIcon.cs => GroupedDifficultyIcon.cs} | 25 ++++++++++-- .../Select/Carousel/SetPanelContent.cs | 8 ++-- 9 files changed, 46 insertions(+), 86 deletions(-) delete mode 100644 osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs rename osu.Game/Screens/Select/Carousel/{FilterableGroupedDifficultyIcon.cs => GroupedDifficultyIcon.cs} (53%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 9fcd470d17..6d881555da 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -872,10 +872,10 @@ namespace osu.Game.Tests.Visual.SongSelect return set != null; }); - FilterableGroupedDifficultyIcon groupIcon = null; + GroupedDifficultyIcon groupIcon = null; AddUntilStep("Find group icon for different ruleset", () => { - return (groupIcon = set.ChildrenOfType() + return (groupIcon = set.ChildrenOfType() .FirstOrDefault(icon => icon.Items.First().BeatmapInfo.Ruleset.OnlineID == 3)) != null; }); diff --git a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs index 5f914f446c..f8304226e5 100644 --- a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs @@ -1,13 +1,9 @@ -#nullable enable -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osuTK; namespace osu.Game.Beatmaps.Drawables @@ -26,30 +22,16 @@ namespace osu.Game.Beatmaps.Drawables set => difficultyIcon.Size = value; } - private readonly IRulesetInfo? ruleset; - - private readonly IReadOnlyList? mods; + public bool ShowTooltip + { + get => difficultyIcon.ShowTooltip; + set => difficultyIcon.ShowTooltip = value; + } private readonly IBeatmapInfo beatmapInfo; private readonly DifficultyIcon difficultyIcon; - /// - /// Creates a new with a given and combination. - /// - /// The beatmap to show the difficulty of. - /// The ruleset to show the difficulty with. - /// The mods to show the difficulty with. - /// Whether to display a tooltip when hovered. - /// Whether to perform difficulty lookup (including calculation if necessary). - public CalculatingDifficultyIcon(IBeatmapInfo beatmapInfo, IRulesetInfo? ruleset, IReadOnlyList? mods, bool shouldShowTooltip = true, - bool performBackgroundDifficultyLookup = true) - : this(beatmapInfo, shouldShowTooltip, performBackgroundDifficultyLookup) - { - this.ruleset = ruleset ?? beatmapInfo.Ruleset; - this.mods = mods ?? Array.Empty(); - } - /// /// Creates a new that follows the currently-selected ruleset and mods. /// @@ -64,17 +46,11 @@ namespace osu.Game.Beatmaps.Drawables InternalChildren = new Drawable[] { - difficultyIcon = new DifficultyIcon(beatmapInfo, beatmapInfo.Ruleset), + difficultyIcon = new DifficultyIcon(beatmapInfo), new DelayedLoadUnloadWrapper(createDifficultyRetriever, 0) }; } - private Drawable createDifficultyRetriever() - { - if (ruleset != null && mods != null) - return new DifficultyRetriever(beatmapInfo, ruleset, mods) { StarDifficulty = { BindTarget = difficultyIcon.Current } }; - - return new DifficultyRetriever(beatmapInfo) { StarDifficulty = { BindTarget = difficultyIcon.Current } }; - } + private Drawable createDifficultyRetriever() => new DifficultyRetriever(beatmapInfo) { StarDifficulty = { BindTarget = difficultyIcon.Current } }; } } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index d1b15cba76..03ff0b29b0 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -30,6 +30,11 @@ namespace osu.Game.Beatmaps.Drawables set => iconContainer.Size = value; } + /// + /// Whether to display a tooltip on hover. Only works if a beatmap was provided at construction time. + /// + public bool ShowTooltip { get; set; } = true; + private readonly IBeatmapInfo? beatmap; private readonly IRulesetInfo ruleset; @@ -54,7 +59,7 @@ namespace osu.Game.Beatmaps.Drawables /// /// The beatmap to be displayed in the tooltip, and to be used for the initial star rating value. /// An optional ruleset to be used for the icon display, in place of the beatmap's ruleset. - public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset) + public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null) : this(ruleset ?? beatmap.Ruleset) { this.beatmap = beatmap; @@ -123,6 +128,6 @@ namespace osu.Game.Beatmaps.Drawables GetCustomTooltip() => new DifficultyIconTooltip(); DifficultyIconTooltipContent IHasCustomTooltip. - TooltipContent => (beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current) : null)!; + TooltipContent => (ShowTooltip && beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current) : null)!; } } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs b/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs index a993f0657b..90ef89ee36 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable enable - using System.Collections.Generic; using System.Threading; using osu.Framework.Allocation; diff --git a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs deleted file mode 100644 index a713592246..0000000000 --- a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets; -using osuTK.Graphics; - -namespace osu.Game.Beatmaps.Drawables -{ - /// - /// A difficulty icon that contains a counter on the right-side of it. - /// - /// - /// Used in cases when there are too many difficulty icons to show. - /// - public class GroupedDifficultyIcon : CalculatingDifficultyIcon - { - public GroupedDifficultyIcon(IEnumerable beatmaps, IRulesetInfo ruleset, Color4 counterColour) - : base(beatmaps.OrderBy(b => b.StarRating).Last(), ruleset, null, false) - { - AddInternal(new OsuSpriteText - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Padding = new MarginPadding { Left = Size.X }, - Margin = new MarginPadding { Left = 2, Right = 5 }, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), - Text = beatmaps.Count().ToString(), - Colour = counterColour, - }); - } - } -} diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 79eee63172..e5fe03f929 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -113,8 +113,9 @@ namespace osu.Game.Screens.Select.Carousel Origin = Anchor.CentreLeft, Children = new Drawable[] { - new CalculatingDifficultyIcon(beatmapInfo, shouldShowTooltip: false) + new CalculatingDifficultyIcon(beatmapInfo) { + ShowTooltip = false, Scale = new Vector2(1.8f), }, new FillFlowContainer diff --git a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs index 3f7d5c13ad..cc904fc1da 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs @@ -10,7 +10,7 @@ using osu.Game.Beatmaps.Drawables; namespace osu.Game.Screens.Select.Carousel { - public class FilterableDifficultyIcon : CalculatingDifficultyIcon + public class FilterableDifficultyIcon : DifficultyIcon { private readonly BindableBool filtered = new BindableBool(); diff --git a/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/GroupedDifficultyIcon.cs similarity index 53% rename from osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs rename to osu.Game/Screens/Select/Carousel/GroupedDifficultyIcon.cs index f883740fd7..8b4140df56 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/GroupedDifficultyIcon.cs @@ -8,23 +8,42 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using osuTK.Graphics; namespace osu.Game.Screens.Select.Carousel { - public class FilterableGroupedDifficultyIcon : GroupedDifficultyIcon + /// + /// A difficulty icon that contains a counter on the right-side of it. + /// + /// + /// Used in cases when there are too many difficulty icons to show. + /// + public class GroupedDifficultyIcon : DifficultyIcon { public readonly List Items; - public FilterableGroupedDifficultyIcon(List items, RulesetInfo ruleset) - : base(items.Select(i => i.BeatmapInfo).ToList(), ruleset, Color4.White) + public GroupedDifficultyIcon(List items, RulesetInfo ruleset) + : base(items.OrderBy(b => b.BeatmapInfo.StarRating).Last().BeatmapInfo, ruleset) { Items = items; foreach (var item in items) item.Filtered.BindValueChanged(_ => Scheduler.AddOnce(updateFilteredDisplay)); + AddInternal(new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Padding = new MarginPadding { Left = Size.X }, + Margin = new MarginPadding { Left = 2, Right = 5 }, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), + Text = items.Count.ToString(), + Colour = Color4.White, + }); + updateFilteredDisplay(); } diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index c5e5a70d59..cc3d722852 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Select.Carousel TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, Status = beatmapSet.Status }, - new FillFlowContainer + new FillFlowContainer { AutoSizeAxes = Axes.Both, Spacing = new Vector2(3), @@ -87,13 +87,13 @@ namespace osu.Game.Screens.Select.Carousel private const int maximum_difficulty_icons = 18; - private IEnumerable getDifficultyIcons() + private IEnumerable getDifficultyIcons() { var beatmaps = carouselSet.Beatmaps.ToList(); return beatmaps.Count > maximum_difficulty_icons - ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset) - .Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset)) + ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset) + .Select(group => new GroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset)) : beatmaps.Select(b => new FilterableDifficultyIcon(b)); } } From 32652ace75b2a3954e359f4af915385b77262b8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 19:51:44 +0900 Subject: [PATCH 173/803] 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 caea787e22..f2790d2520 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 652dc2740f..0802a98858 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 0c3c38f9f0..a272df20d4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From b068df21497cba26067b98ed8d7356634421fb5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 19:44:38 +0900 Subject: [PATCH 174/803] Enable NRT on `BeatmapDiffiultyCache` --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 45 +++++++++------------ 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 69488277f1..b6314f5841 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -1,15 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -50,19 +47,19 @@ namespace osu.Game.Beatmaps /// private readonly object bindableUpdateLock = new object(); - private CancellationTokenSource trackedUpdateCancellationSource; + private CancellationTokenSource trackedUpdateCancellationSource = new CancellationTokenSource(); [Resolved] - private BeatmapManager beatmapManager { get; set; } + private BeatmapManager beatmapManager { get; set; } = null!; [Resolved] - private Bindable currentRuleset { get; set; } + private Bindable currentRuleset { get; set; } = null!; [Resolved] - private Bindable> currentMods { get; set; } + private Bindable> currentMods { get; set; } = null!; - private ModSettingChangeTracker modSettingChangeTracker; - private ScheduledDelegate debouncedModSettingsChange; + private ModSettingChangeTracker? modSettingChangeTracker; + private ScheduledDelegate? debouncedModSettingsChange; protected override void LoadComplete() { @@ -91,7 +88,7 @@ namespace osu.Game.Beatmaps /// The to get the difficulty of. /// An optional which stops updating the star difficulty for the given . /// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state (but not during updates to ruleset and mods if a stale value is already propagated). - public IBindable GetBindableDifficulty([NotNull] IBeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) + public IBindable GetBindableDifficulty(IBeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) { var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken); @@ -112,7 +109,7 @@ namespace osu.Game.Beatmaps /// The s to get the difficulty with. If null, no mods will be assumed. /// An optional which stops updating the star difficulty for the given . /// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state. - public IBindable GetBindableDifficulty([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, + public IBindable GetBindableDifficulty(IBeatmapInfo beatmapInfo, IRulesetInfo? rulesetInfo, IEnumerable? mods, CancellationToken cancellationToken = default) => createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken); @@ -128,8 +125,8 @@ namespace osu.Game.Beatmaps /// A return value indicates that the difficulty process failed or was interrupted early, /// and as such there is no usable star difficulty value to be returned. /// - public virtual Task GetDifficultyAsync([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo = null, - [CanBeNull] IEnumerable mods = null, CancellationToken cancellationToken = default) + public virtual Task GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo? rulesetInfo = null, + IEnumerable? mods = null, CancellationToken cancellationToken = default) { // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. rulesetInfo ??= beatmapInfo.Ruleset; @@ -204,7 +201,6 @@ namespace osu.Game.Beatmaps lock (bindableUpdateLock) { cancelTrackedBindableUpdate(); - trackedUpdateCancellationSource = new CancellationTokenSource(); foreach (var b in trackedBindables) { @@ -223,16 +219,13 @@ namespace osu.Game.Beatmaps { lock (bindableUpdateLock) { - trackedUpdateCancellationSource?.Cancel(); - trackedUpdateCancellationSource = null; + trackedUpdateCancellationSource.Cancel(); + trackedUpdateCancellationSource = new CancellationTokenSource(); - if (linkedCancellationSources != null) - { - foreach (var c in linkedCancellationSources) - c.Dispose(); + foreach (var c in linkedCancellationSources) + c.Dispose(); - linkedCancellationSources.Clear(); - } + linkedCancellationSources.Clear(); } } @@ -244,7 +237,7 @@ namespace osu.Game.Beatmaps /// The initial s to get the difficulty with. /// An optional which stops updating the star difficulty for the given . /// The . - private BindableStarDifficulty createBindable([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo initialRulesetInfo, [CanBeNull] IEnumerable initialMods, + private BindableStarDifficulty createBindable(IBeatmapInfo beatmapInfo, IRulesetInfo? initialRulesetInfo, IEnumerable? initialMods, CancellationToken cancellationToken) { var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken); @@ -259,7 +252,7 @@ namespace osu.Game.Beatmaps /// The to update with. /// The s to update with. /// A token that may be used to cancel this update. - private void updateBindable([NotNull] BindableStarDifficulty bindable, [CanBeNull] IRulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, CancellationToken cancellationToken = default) + private void updateBindable(BindableStarDifficulty bindable, IRulesetInfo? rulesetInfo, IEnumerable? mods, CancellationToken cancellationToken = default) { // GetDifficultyAsync will fall back to existing data from IBeatmapInfo if not locally available // (contrary to GetAsync) @@ -329,7 +322,7 @@ namespace osu.Game.Beatmaps modSettingChangeTracker?.Dispose(); cancelTrackedBindableUpdate(); - updateScheduler?.Dispose(); + updateScheduler.Dispose(); } public readonly struct DifficultyCacheLookup : IEquatable @@ -339,7 +332,7 @@ namespace osu.Game.Beatmaps public readonly Mod[] OrderedMods; - public DifficultyCacheLookup([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo ruleset, IEnumerable mods) + public DifficultyCacheLookup(BeatmapInfo beatmapInfo, RulesetInfo? ruleset, IEnumerable? mods) { BeatmapInfo = beatmapInfo; // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. From ef258122d23b4c2fe1a24756f38e266a7597b2e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 19:46:59 +0900 Subject: [PATCH 175/803] Move `GetDifficultyRating` helper method to `StarDifficulty` --- .../TestSceneBeatmapDifficultyCache.cs | 2 +- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 29 ----------------- osu.Game/Beatmaps/EFBeatmapInfo.cs | 2 +- osu.Game/Beatmaps/StarDifficulty.cs | 31 ++++++++++++++++++- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 2 +- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs index f87e5711a6..6eb103316f 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs @@ -157,7 +157,7 @@ namespace osu.Game.Tests.Beatmaps [TestCase(8.3, DifficultyRating.ExpertPlus)] public void TestDifficultyRatingMapping(double starRating, DifficultyRating expectedBracket) { - var actualBracket = BeatmapDifficultyCache.GetDifficultyRating(starRating); + var actualBracket = StarDifficulty.GetDifficultyRating(starRating); Assert.AreEqual(expectedBracket, actualBracket); } diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index b6314f5841..5e468e975a 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -13,7 +13,6 @@ using osu.Framework.Extensions; using osu.Framework.Lists; using osu.Framework.Logging; using osu.Framework.Threading; -using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Rulesets; @@ -165,34 +164,6 @@ namespace osu.Game.Beatmaps updateScheduler); } - /// - /// Retrieves the that describes a star rating. - /// - /// - /// For more information, see: https://osu.ppy.sh/help/wiki/Difficulties - /// - /// The star rating. - /// The that best describes . - public static DifficultyRating GetDifficultyRating(double starRating) - { - if (Precision.AlmostBigger(starRating, 6.5, 0.005)) - return DifficultyRating.ExpertPlus; - - if (Precision.AlmostBigger(starRating, 5.3, 0.005)) - return DifficultyRating.Expert; - - if (Precision.AlmostBigger(starRating, 4.0, 0.005)) - return DifficultyRating.Insane; - - if (Precision.AlmostBigger(starRating, 2.7, 0.005)) - return DifficultyRating.Hard; - - if (Precision.AlmostBigger(starRating, 2.0, 0.005)) - return DifficultyRating.Normal; - - return DifficultyRating.Easy; - } - /// /// Updates all tracked using the current ruleset and mods. /// diff --git a/osu.Game/Beatmaps/EFBeatmapInfo.cs b/osu.Game/Beatmaps/EFBeatmapInfo.cs index 34311448eb..20abdc686a 100644 --- a/osu.Game/Beatmaps/EFBeatmapInfo.cs +++ b/osu.Game/Beatmaps/EFBeatmapInfo.cs @@ -128,7 +128,7 @@ namespace osu.Game.Beatmaps public List Scores { get; set; } [JsonIgnore] - public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(StarRating); + public DifficultyRating DifficultyRating => StarDifficulty.GetDifficultyRating(StarRating); public override string ToString() => this.GetDisplayTitle(); diff --git a/osu.Game/Beatmaps/StarDifficulty.cs b/osu.Game/Beatmaps/StarDifficulty.cs index 91bc3aacf6..e042f1c698 100644 --- a/osu.Game/Beatmaps/StarDifficulty.cs +++ b/osu.Game/Beatmaps/StarDifficulty.cs @@ -4,6 +4,7 @@ #nullable disable using JetBrains.Annotations; +using osu.Framework.Utils; using osu.Game.Rulesets.Difficulty; namespace osu.Game.Beatmaps @@ -50,6 +51,34 @@ namespace osu.Game.Beatmaps Attributes = null; } - public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(Stars); + public DifficultyRating DifficultyRating => GetDifficultyRating(Stars); + + /// + /// Retrieves the that describes a star rating. + /// + /// + /// For more information, see: https://osu.ppy.sh/help/wiki/Difficulties + /// + /// The star rating. + /// The that best describes . + public static DifficultyRating GetDifficultyRating(double starRating) + { + if (Precision.AlmostBigger(starRating, 6.5, 0.005)) + return DifficultyRating.ExpertPlus; + + if (Precision.AlmostBigger(starRating, 5.3, 0.005)) + return DifficultyRating.Expert; + + if (Precision.AlmostBigger(starRating, 4.0, 0.005)) + return DifficultyRating.Insane; + + if (Precision.AlmostBigger(starRating, 2.7, 0.005)) + return DifficultyRating.Hard; + + if (Precision.AlmostBigger(starRating, 2.0, 0.005)) + return DifficultyRating.Normal; + + return DifficultyRating.Easy; + } } } diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index d5b32760c9..3030018138 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Edit.Verify [BackgroundDependencyLoader] private void load() { - InterpretedDifficulty.Default = BeatmapDifficultyCache.GetDifficultyRating(EditorBeatmap.BeatmapInfo.StarRating); + InterpretedDifficulty.Default = StarDifficulty.GetDifficultyRating(EditorBeatmap.BeatmapInfo.StarRating); InterpretedDifficulty.SetDefault(); Child = new Container From 9c46592e0e45279e4a92d4f9b415b61b17510787 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 23 Jun 2022 20:04:54 +0900 Subject: [PATCH 176/803] Move collection change event binding to LoadComplete --- osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs index a314992287..4cf2c3778a 100644 --- a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs +++ b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs @@ -33,8 +33,10 @@ namespace osu.Game.Graphics.Containers protected abstract OsuRearrangeableListItem CreateOsuDrawable(TModel item); - protected OsuRearrangeableListContainer() + protected override void LoadComplete() { + base.LoadComplete(); + Items.CollectionChanged += (_, args) => { if (args.Action == NotifyCollectionChangedAction.Move) From aaf619b35c8d6b8642c6d89d83cdb74a4758df00 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 23 Jun 2022 20:13:01 +0900 Subject: [PATCH 177/803] Don't play sample if there is no item being actively dragged --- osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs index 4cf2c3778a..b604ae73eb 100644 --- a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs +++ b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs @@ -46,6 +46,9 @@ namespace osu.Game.Graphics.Containers private void playSwapSample() { + if (!DragActive.Value) + return; + if (Time.Current - sampleLastPlaybackTime <= 35) return; From 2def6d809bf4c48108a95324101c5a1c107b3bfa Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 23 Jun 2022 11:02:27 -0400 Subject: [PATCH 178/803] refactor to use enum dropdown --- .../Multiplayer/TestSceneLoungeRoomsContainer.cs | 12 +++++------- .../OnlinePlay/Lounge/Components/FilterCriteria.cs | 2 +- .../Lounge/Components/RoomPublicityFilter.cs | 13 +++++++++++++ .../OnlinePlay/Lounge/Components/RoomsContainer.cs | 2 +- .../Screens/OnlinePlay/Lounge/LoungeSubScreen.cs | 13 ++++++------- 5 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 363a2b4a4d..4ebd05d46a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -158,7 +158,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - public void TestLockedFiltering() + public void TestPublicityFiltering() { AddStep("add rooms", () => { @@ -166,15 +166,13 @@ namespace osu.Game.Tests.Visual.Multiplayer RoomManager.AddRooms(1, withPassword: false); }); - AddUntilStep("both rooms shown", () => container.Rooms.Count(r => r.IsPresent) == 2); + AddStep("filter public rooms", () => container.Filter.Value = new FilterCriteria { Publicity = RoomPublicityFilter.Public }); - AddStep("filter locked rooms", () => container.Filter.Value = new FilterCriteria { Locked = false }); + AddUntilStep("private room hidden", () => container.Rooms.All(r => !r.Room.HasPassword.Value)); - AddUntilStep("locked room hidden", () => container.Rooms.All(r => !r.Room.HasPassword.Value)); + AddStep("filter private rooms", () => container.Filter.Value = new FilterCriteria { Publicity = RoomPublicityFilter.Private }); - AddStep("unfilter locked rooms", () => container.Filter.SetDefault()); - - AddUntilStep("both rooms shown", () => container.Rooms.Count(r => r.IsPresent) == 2); + AddUntilStep("public room hidden", () => container.Rooms.All(r => r.Room.HasPassword.Value)); } [Test] diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs index 45c1b9b394..3bde1f07b8 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs @@ -13,6 +13,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components public RoomStatusFilter Status; public string Category; public RulesetInfo Ruleset; - public bool Locked = true; + public RoomPublicityFilter Publicity; } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs new file mode 100644 index 0000000000..ffa527cb9c --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.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. + +#nullable disable + +namespace osu.Game.Screens.OnlinePlay.Lounge.Components +{ + public enum RoomPublicityFilter + { + Public, + Private + } +} diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 908505e386..e5d7170f16 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components matchingFilter &= r.FilterTerms.Any(term => term.ToString().Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); } - matchingFilter &= criteria.Locked || !r.Room.HasPassword.Value; + matchingFilter &= (criteria.Publicity == RoomPublicityFilter.Public && !r.Room.HasPassword.Value) || (criteria.Publicity == RoomPublicityFilter.Private && r.Room.HasPassword.Value); r.MatchingFilter = matchingFilter; } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index a726ff632f..7ec2c7d8e7 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -85,7 +85,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private RoomsContainer roomsContainer; private SearchTextBox searchTextBox; private Dropdown statusDropdown; - private Checkbox lockedCheckbox; + private Dropdown publicityDropdown; [BackgroundDependencyLoader(true)] private void load([CanBeNull] IdleTracker idleTracker) @@ -226,7 +226,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge SearchString = searchTextBox.Current.Value, Ruleset = ruleset.Value, Status = statusDropdown.Current.Value, - Locked = lockedCheckbox.Current.Value, + Publicity = publicityDropdown.Current.Value, }; #endregion @@ -241,16 +241,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge statusDropdown.Current.BindValueChanged(_ => UpdateFilter()); - lockedCheckbox = new OsuTabControlCheckbox + publicityDropdown = new SlimEnumDropdown { - Current = new Bindable(true), RelativeSizeAxes = Axes.None, - Text = @"Show Locked", + Width = 160, }; - lockedCheckbox.Current.BindValueChanged(_ => UpdateFilter()); + publicityDropdown.Current.BindValueChanged(_ => UpdateFilter()); - return new Drawable[] { lockedCheckbox, statusDropdown, }; + return new Drawable[] { publicityDropdown, statusDropdown, }; } public override void OnEntering(ScreenTransitionEvent e) From 9a15adbfffb4b4db274b71db5cc00b20694ccf32 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 23 Jun 2022 11:24:28 -0400 Subject: [PATCH 179/803] add an all option to publicity filter --- .../Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs | 4 ++++ .../OnlinePlay/Lounge/Components/RoomPublicityFilter.cs | 1 + .../Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 4ebd05d46a..e16e71ca70 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -166,6 +166,10 @@ namespace osu.Game.Tests.Visual.Multiplayer RoomManager.AddRooms(1, withPassword: false); }); + AddStep("apply default filter", () => container.Filter.SetDefault()); + + AddUntilStep("both rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2); + AddStep("filter public rooms", () => container.Filter.Value = new FilterCriteria { Publicity = RoomPublicityFilter.Public }); AddUntilStep("private room hidden", () => container.Rooms.All(r => !r.Room.HasPassword.Value)); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs index ffa527cb9c..53f85ba7c4 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs @@ -7,6 +7,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public enum RoomPublicityFilter { + All, Public, Private } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index e5d7170f16..0e8918fa18 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components matchingFilter &= r.FilterTerms.Any(term => term.ToString().Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); } - matchingFilter &= (criteria.Publicity == RoomPublicityFilter.Public && !r.Room.HasPassword.Value) || (criteria.Publicity == RoomPublicityFilter.Private && r.Room.HasPassword.Value); + matchingFilter &= criteria.Publicity == RoomPublicityFilter.All || (criteria.Publicity == RoomPublicityFilter.Public && !r.Room.HasPassword.Value) || (criteria.Publicity == RoomPublicityFilter.Private && r.Room.HasPassword.Value); r.MatchingFilter = matchingFilter; } From d67c482c48b9cf3175672edccc84a91ec5d17c84 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 23 Jun 2022 11:36:08 -0400 Subject: [PATCH 180/803] move publicity filter to multi exclusively --- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 18 ++++------------- .../Multiplayer/MultiplayerLoungeSubScreen.cs | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index 7ec2c7d8e7..f7f3c27ede 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -85,7 +85,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private RoomsContainer roomsContainer; private SearchTextBox searchTextBox; private Dropdown statusDropdown; - private Dropdown publicityDropdown; [BackgroundDependencyLoader(true)] private void load([CanBeNull] IdleTracker idleTracker) @@ -225,12 +224,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { SearchString = searchTextBox.Current.Value, Ruleset = ruleset.Value, - Status = statusDropdown.Current.Value, - Publicity = publicityDropdown.Current.Value, + Status = statusDropdown.Current.Value }; - #endregion - protected virtual IEnumerable CreateFilterControls() { statusDropdown = new SlimEnumDropdown @@ -241,17 +237,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge statusDropdown.Current.BindValueChanged(_ => UpdateFilter()); - publicityDropdown = new SlimEnumDropdown - { - RelativeSizeAxes = Axes.None, - Width = 160, - }; - - publicityDropdown.Current.BindValueChanged(_ => UpdateFilter()); - - return new Drawable[] { publicityDropdown, statusDropdown, }; + yield return statusDropdown; } + #endregion + public override void OnEntering(ScreenTransitionEvent e) { base.OnEntering(e); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index ae65e1d969..2c0f0f43fe 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -3,11 +3,15 @@ #nullable disable +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Framework.Screens; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; @@ -27,6 +31,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private MultiplayerClient client { get; set; } + private Dropdown publicityDropdown; + public override void OnResuming(ScreenTransitionEvent e) { base.OnResuming(e); @@ -40,10 +46,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } } + protected override IEnumerable CreateFilterControls() + { + publicityDropdown = new SlimEnumDropdown + { + RelativeSizeAxes = Axes.None, + Width = 160, + }; + + publicityDropdown.Current.BindValueChanged(_ => UpdateFilter()); + + return base.CreateFilterControls().Prepend(publicityDropdown); + } + protected override FilterCriteria CreateFilterCriteria() { var criteria = base.CreateFilterCriteria(); criteria.Category = @"realtime"; + criteria.Publicity = publicityDropdown.Current.Value; return criteria; } From 454eff43c542fdad3a37a37ea66bad6c594aa5ba Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 23 Jun 2022 11:40:25 -0400 Subject: [PATCH 181/803] publicity -> accesstype --- .../Multiplayer/TestSceneLoungeRoomsContainer.cs | 4 ++-- .../OnlinePlay/Lounge/Components/FilterCriteria.cs | 2 +- .../{RoomPublicityFilter.cs => RoomAccessType.cs} | 2 +- .../OnlinePlay/Lounge/Components/RoomsContainer.cs | 2 +- .../Multiplayer/MultiplayerLoungeSubScreen.cs | 10 +++++----- 5 files changed, 10 insertions(+), 10 deletions(-) rename osu.Game/Screens/OnlinePlay/Lounge/Components/{RoomPublicityFilter.cs => RoomAccessType.cs} (88%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index e16e71ca70..89be9cc188 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -170,11 +170,11 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("both rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2); - AddStep("filter public rooms", () => container.Filter.Value = new FilterCriteria { Publicity = RoomPublicityFilter.Public }); + AddStep("filter public rooms", () => container.Filter.Value = new FilterCriteria { AccessType = RoomAccessType.Public }); AddUntilStep("private room hidden", () => container.Rooms.All(r => !r.Room.HasPassword.Value)); - AddStep("filter private rooms", () => container.Filter.Value = new FilterCriteria { Publicity = RoomPublicityFilter.Private }); + AddStep("filter private rooms", () => container.Filter.Value = new FilterCriteria { AccessType = RoomAccessType.Private }); AddUntilStep("public room hidden", () => container.Rooms.All(r => r.Room.HasPassword.Value)); } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs index 3bde1f07b8..864634209b 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs @@ -13,6 +13,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components public RoomStatusFilter Status; public string Category; public RulesetInfo Ruleset; - public RoomPublicityFilter Publicity; + public RoomAccessType AccessType; } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs similarity index 88% rename from osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs rename to osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs index 53f85ba7c4..bf8da65881 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs @@ -5,7 +5,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public enum RoomPublicityFilter + public enum RoomAccessType { All, Public, diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 0e8918fa18..55fe8c129a 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components matchingFilter &= r.FilterTerms.Any(term => term.ToString().Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); } - matchingFilter &= criteria.Publicity == RoomPublicityFilter.All || (criteria.Publicity == RoomPublicityFilter.Public && !r.Room.HasPassword.Value) || (criteria.Publicity == RoomPublicityFilter.Private && r.Room.HasPassword.Value); + matchingFilter &= criteria.AccessType == RoomAccessType.All || (criteria.AccessType == RoomAccessType.Public && !r.Room.HasPassword.Value) || (criteria.AccessType == RoomAccessType.Private && r.Room.HasPassword.Value); r.MatchingFilter = matchingFilter; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index 2c0f0f43fe..63a482ce46 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private MultiplayerClient client { get; set; } - private Dropdown publicityDropdown; + private Dropdown roomAccessTypeDropdown; public override void OnResuming(ScreenTransitionEvent e) { @@ -48,22 +48,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override IEnumerable CreateFilterControls() { - publicityDropdown = new SlimEnumDropdown + roomAccessTypeDropdown = new SlimEnumDropdown { RelativeSizeAxes = Axes.None, Width = 160, }; - publicityDropdown.Current.BindValueChanged(_ => UpdateFilter()); + roomAccessTypeDropdown.Current.BindValueChanged(_ => UpdateFilter()); - return base.CreateFilterControls().Prepend(publicityDropdown); + return base.CreateFilterControls().Prepend(roomAccessTypeDropdown); } protected override FilterCriteria CreateFilterCriteria() { var criteria = base.CreateFilterCriteria(); criteria.Category = @"realtime"; - criteria.Publicity = publicityDropdown.Current.Value; + criteria.AccessType = roomAccessTypeDropdown.Current.Value; return criteria; } From bea70988bc09fe4cbed5723715175248c4eedf3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 Jun 2022 17:57:22 +0200 Subject: [PATCH 182/803] Remove unnecessary `#nullable disable` --- osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs index bf8da65881..1141ed2937 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public enum RoomAccessType From 04d69010b5666e1eb57ac02824c0fc2e2e257001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 Jun 2022 18:03:04 +0200 Subject: [PATCH 183/803] Split overlong conditional to local function --- .../Lounge/Components/RoomsContainer.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 55fe8c129a..24dd27c27b 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -87,11 +87,29 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components matchingFilter &= r.FilterTerms.Any(term => term.ToString().Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); } - matchingFilter &= criteria.AccessType == RoomAccessType.All || (criteria.AccessType == RoomAccessType.Public && !r.Room.HasPassword.Value) || (criteria.AccessType == RoomAccessType.Private && r.Room.HasPassword.Value); + matchingFilter &= matchesAccessType(r, criteria.AccessType); r.MatchingFilter = matchingFilter; } }); + + static bool matchesAccessType(DrawableLoungeRoom room, RoomAccessType accessType) + { + switch (accessType) + { + case RoomAccessType.All: + return true; + + case RoomAccessType.Public: + return !room.Room.HasPassword.Value; + + case RoomAccessType.Private: + return room.Room.HasPassword.Value; + + default: + throw new ArgumentOutOfRangeException(nameof(accessType), accessType, $"Unsupported {nameof(RoomAccessType)} in filter"); + } + } } private void roomsChanged(object sender, NotifyCollectionChangedEventArgs args) From 489ed5d34d2fd8ead9ed49a7eda7e9361e1a8b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 Jun 2022 18:04:38 +0200 Subject: [PATCH 184/803] Rename test method --- .../Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 89be9cc188..d56716a0b5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -158,7 +158,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - public void TestPublicityFiltering() + public void TestAccessTypeFiltering() { AddStep("add rooms", () => { From eaae600a49b08299a96fdae2ec44b57712184758 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 24 Jun 2022 04:57:44 +0300 Subject: [PATCH 185/803] Fix typo in auto-import ignore rule --- osu.sln.DotSettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index e732b25951..8fd750a50d 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -793,7 +793,7 @@ See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> True - True + True True True True From 129c90709252aa6fc4dd11b69b0616fb4915dee8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 13:58:02 +0900 Subject: [PATCH 186/803] Remove unused parameters --- osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs index f8304226e5..2adba3ae99 100644 --- a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs @@ -36,9 +36,7 @@ namespace osu.Game.Beatmaps.Drawables /// Creates a new that follows the currently-selected ruleset and mods. /// /// The beatmap to show the difficulty of. - /// Whether to display a tooltip when hovered. - /// Whether to perform difficulty lookup (including calculation if necessary). - public CalculatingDifficultyIcon(IBeatmapInfo beatmapInfo, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true) + public CalculatingDifficultyIcon(IBeatmapInfo beatmapInfo) { this.beatmapInfo = beatmapInfo ?? throw new ArgumentNullException(nameof(beatmapInfo)); From d9c1a9d71fd16c10601654af54e652533cbb7b0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 13:59:17 +0900 Subject: [PATCH 187/803] Use new property to specify no tooltip, rather than passing only ruleset --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 4 ++-- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 03ff0b29b0..679e9c3665 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -55,7 +55,7 @@ namespace osu.Game.Beatmaps.Drawables private IRulesetStore rulesets { get; set; } = null!; /// - /// Creates a new with a tooltip. Will use provided beatmap's for initial value. + /// Creates a new . Will use provided beatmap's for initial value. /// /// The beatmap to be displayed in the tooltip, and to be used for the initial star rating value. /// An optional ruleset to be used for the icon display, in place of the beatmap's ruleset. @@ -67,7 +67,7 @@ namespace osu.Game.Beatmaps.Drawables } /// - /// Creates a new with no tooltip. + /// Creates a new without an associated beatmap. /// /// The ruleset to be used for the icon display. public DifficultyIcon(IRulesetInfo ruleset) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 2efdb71204..1a57a89957 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -274,8 +274,9 @@ namespace osu.Game.Overlays.BeatmapSet Alpha = 0.5f } }, - icon = new DifficultyIcon(beatmapInfo.Ruleset) + icon = new DifficultyIcon(beatmapInfo) { + ShowTooltip = false, Current = { Value = new StarDifficulty(beatmapInfo.StarRating, 0) }, Anchor = Anchor.Centre, Origin = Anchor.Centre, From 12ea8369ee504caddcaf38488cae09bfedb091b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 14:06:31 +0900 Subject: [PATCH 188/803] Update retriever to be relatively sized --- .../Beatmaps/Drawables/CalculatingDifficultyIcon.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs index 2adba3ae99..df9766c891 100644 --- a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.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.Graphics; using osu.Framework.Graphics.Containers; using osuTK; @@ -28,8 +27,6 @@ namespace osu.Game.Beatmaps.Drawables set => difficultyIcon.ShowTooltip = value; } - private readonly IBeatmapInfo beatmapInfo; - private readonly DifficultyIcon difficultyIcon; /// @@ -38,17 +35,16 @@ namespace osu.Game.Beatmaps.Drawables /// The beatmap to show the difficulty of. public CalculatingDifficultyIcon(IBeatmapInfo beatmapInfo) { - this.beatmapInfo = beatmapInfo ?? throw new ArgumentNullException(nameof(beatmapInfo)); - AutoSizeAxes = Axes.Both; InternalChildren = new Drawable[] { difficultyIcon = new DifficultyIcon(beatmapInfo), - new DelayedLoadUnloadWrapper(createDifficultyRetriever, 0) + new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmapInfo) { StarDifficulty = { BindTarget = difficultyIcon.Current } }, 0) + { + RelativeSizeAxes = Axes.Both, + } }; } - - private Drawable createDifficultyRetriever() => new DifficultyRetriever(beatmapInfo) { StarDifficulty = { BindTarget = difficultyIcon.Current } }; } } From 28837693e5a72ccf692df76a7bc231a473987c4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 14:22:38 +0900 Subject: [PATCH 189/803] Nuke calculating everything The whole component is pointless so I'm just going to nuke for now I guess. Kind of makes the whole refactor effort pointless but oh well? To expand on this, the implementation was actually incorrect as pointed out at https://github.com/ppy/osu/pull/18819#pullrequestreview-1017886035. --- .../Drawables/CalculatingDifficultyIcon.cs | 50 ----------- .../Beatmaps/Drawables/DifficultyRetriever.cs | 83 ------------------- .../Carousel/DrawableCarouselBeatmap.cs | 6 +- 3 files changed, 5 insertions(+), 134 deletions(-) delete mode 100644 osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs delete mode 100644 osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs diff --git a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs deleted file mode 100644 index df9766c891..0000000000 --- a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osuTK; - -namespace osu.Game.Beatmaps.Drawables -{ - /// - /// A difficulty icon which automatically calculates difficulty in the background. - /// - public class CalculatingDifficultyIcon : CompositeDrawable - { - /// - /// Size of this difficulty icon. - /// - public new Vector2 Size - { - get => difficultyIcon.Size; - set => difficultyIcon.Size = value; - } - - public bool ShowTooltip - { - get => difficultyIcon.ShowTooltip; - set => difficultyIcon.ShowTooltip = value; - } - - private readonly DifficultyIcon difficultyIcon; - - /// - /// Creates a new that follows the currently-selected ruleset and mods. - /// - /// The beatmap to show the difficulty of. - public CalculatingDifficultyIcon(IBeatmapInfo beatmapInfo) - { - AutoSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - difficultyIcon = new DifficultyIcon(beatmapInfo), - new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmapInfo) { StarDifficulty = { BindTarget = difficultyIcon.Current } }, 0) - { - RelativeSizeAxes = Axes.Both, - } - }; - } - } -} diff --git a/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs b/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs deleted file mode 100644 index 90ef89ee36..0000000000 --- a/osu.Game/Beatmaps/Drawables/DifficultyRetriever.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.Collections.Generic; -using System.Threading; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Beatmaps.Drawables -{ - /// - /// A component solely responsible for calculating difficulty in the background. - /// Intended for use with to only run processing when usage is on-screen. - /// - public class DifficultyRetriever : Component - { - /// - /// The bindable star difficulty. - /// - public IBindable StarDifficulty => starDifficulty; - - private readonly Bindable starDifficulty = new Bindable(); - - private readonly IBeatmapInfo beatmapInfo; - - private readonly IRulesetInfo? ruleset; - private readonly IReadOnlyList? mods; - - private readonly CancellationTokenSource difficultyCancellation = new CancellationTokenSource(); - - [Resolved] - private BeatmapDifficultyCache difficultyCache { get; set; } = null!; - - /// - /// Construct a difficulty retriever that tracks the current ruleset / mod selection. - /// - /// The beatmap to use for calculation. - public DifficultyRetriever(IBeatmapInfo beatmapInfo) - { - this.beatmapInfo = beatmapInfo; - } - - /// - /// Construct a difficulty retriever that is calculated only once for the specified ruleset / mod combination. - /// This will not track global ruleset and mod changes. - /// - /// The beatmap to use for calculation. - /// The ruleset to use for calculation. - /// The mods to use for calculation. - public DifficultyRetriever(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, IReadOnlyList mods) - { - this.beatmapInfo = beatmapInfo; - this.ruleset = ruleset; - this.mods = mods; - } - - private IBindable localStarDifficulty = null!; - - [BackgroundDependencyLoader] - private void load() - { - localStarDifficulty = ruleset != null - ? difficultyCache.GetBindableDifficulty(beatmapInfo, ruleset, mods, difficultyCancellation.Token) - : difficultyCache.GetBindableDifficulty(beatmapInfo, difficultyCancellation.Token); - - localStarDifficulty.BindValueChanged(d => - { - if (d.NewValue is StarDifficulty diff) - starDifficulty.Value = diff; - }); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - difficultyCancellation.Cancel(); - } - } -} diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index e5fe03f929..1b3cab20e8 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -53,7 +53,9 @@ namespace osu.Game.Screens.Select.Carousel private Action hideRequested; private Triangles triangles; + private StarCounter starCounter; + private DifficultyIcon difficultyIcon; [Resolved(CanBeNull = true)] private BeatmapSetOverlay beatmapOverlay { get; set; } @@ -113,7 +115,7 @@ namespace osu.Game.Screens.Select.Carousel Origin = Anchor.CentreLeft, Children = new Drawable[] { - new CalculatingDifficultyIcon(beatmapInfo) + difficultyIcon = new DifficultyIcon(beatmapInfo) { ShowTooltip = false, Scale = new Vector2(1.8f), @@ -217,6 +219,8 @@ namespace osu.Game.Screens.Select.Carousel starDifficultyBindable.BindValueChanged(d => { starCounter.Current = (float)(d.NewValue?.Stars ?? 0); + if (d.NewValue != null) + difficultyIcon.Current.Value = d.NewValue.Value; }, true); } From f4173a3bff4a170e78e6215a6a82a556798bee31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 14:48:32 +0900 Subject: [PATCH 190/803] Update c# language version to 10 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index dbc84fb88f..1f59f19eaa 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@  - 8.0 + 10.0 true enable From f71f6302fd494ec8d12136f6709f36b705ac70ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 14:48:43 +0900 Subject: [PATCH 191/803] Remove unnecessary `null` casts --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 2 +- osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs | 4 ++-- osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs | 2 +- .../Visual/UserInterface/TestSceneLabelledDrawable.cs | 2 +- .../Visual/UserInterface/TestSceneOsuAnimatedButton.cs | 3 +-- osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs | 3 +-- osu.Game/Graphics/Containers/ScalingContainer.cs | 2 +- osu.Game/Graphics/UserInterface/ShearedToggleButton.cs | 3 +-- osu.Game/Graphics/UserInterface/ToggleMenuItem.cs | 2 +- osu.Game/OsuGameBase.cs | 4 ++-- osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs | 4 ++-- osu.Game/Overlays/Mods/ModColumn.cs | 2 +- osu.Game/Overlays/Mods/ModPanel.cs | 6 +++--- osu.Game/Rulesets/EFRulesetInfo.cs | 2 +- osu.Game/Rulesets/Objects/SliderPath.cs | 2 +- osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs | 2 +- .../Screens/Edit/Components/Menus/DifficultyMenuItem.cs | 2 +- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 2 +- osu.Game/Screens/Select/Carousel/SetPanelContent.cs | 4 ++-- osu.Game/Screens/Utility/LatencyCertifierScreen.cs | 4 ++-- osu.Game/Storyboards/CommandTimelineGroup.cs | 2 +- 23 files changed, 30 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index be2e9c7cb5..5706955fc5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Tests => addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame); 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 Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? getSliderStart : getSliderEnd; private List getSliderCurve() => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve; private Vector2 getSliderStart() => getSliderCurve().First(); diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs index 4a5bb6de46..541ad1e8bb 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs @@ -364,12 +364,12 @@ namespace osu.Game.Tests.NonVisual private void confirmCurrentFrame(int? frame) { - Assert.AreEqual(frame is int x ? replay.Frames[x].Time : (double?)null, handler.CurrentFrame?.Time, "Unexpected current frame"); + Assert.AreEqual(frame is int x ? replay.Frames[x].Time : null, handler.CurrentFrame?.Time, "Unexpected current frame"); } private void confirmNextFrame(int? frame) { - Assert.AreEqual(frame is int x ? replay.Frames[x].Time : (double?)null, handler.NextFrame?.Time, "Unexpected next frame"); + Assert.AreEqual(frame is int x ? replay.Frames[x].Time : null, handler.NextFrame?.Time, "Unexpected next frame"); } private class TestReplayFrame : ReplayFrame diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs index 77ceef6402..1493c10969 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Skins { AddStep($"Set beatmap skin enabled to {allowBeatmapLookups}", () => config.SetValue(OsuSetting.BeatmapSkins, allowBeatmapLookups)); - ISkin expected() => allowBeatmapLookups ? (ISkin)beatmapSource : userSource; + ISkin expected() => allowBeatmapLookups ? beatmapSource : userSource; AddAssert("Check lookup is from correct source", () => requester.FindProvider(s => s.GetDrawableComponent(new TestSkinComponent()) != null) == expected()); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs index f717bb4dee..7ce0fceff9 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Width = 500, AutoSizeAxes = Axes.Y, - Child = component = padded ? (LabelledDrawable)new PaddedLabelledDrawable() : new NonPaddedLabelledDrawable(), + Child = component = padded ? new PaddedLabelledDrawable() : new NonPaddedLabelledDrawable(), }; component.Label = "a sample component"; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs index 28599c740e..bab2121d70 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; @@ -89,7 +88,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddToggleStep("toggle enabled", toggle => { for (int i = 0; i < 6; i++) - button.Action = toggle ? () => { } : (Action)null; + button.Action = toggle ? () => { } : null; }); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs index 0e31a133ac..d4c2bfd422 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; @@ -29,7 +28,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddToggleStep("toggle enabled", toggle => { for (int i = 0; i < 6; i++) - button.Action = toggle ? () => { } : (Action)null; + button.Action = toggle ? () => { } : null; }); } diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 5c8579a144..17c51129a7 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -260,7 +260,7 @@ namespace osu.Game.Graphics.Containers if (host.Window == null) return; bool coversWholeScreen = Size == Vector2.One && safeArea.SafeAreaPadding.Value.Total == Vector2.Zero; - host.Window.CursorConfineRect = coversWholeScreen ? (RectangleF?)null : ToScreenSpace(DrawRectangle).AABBFloat; + host.Window.CursorConfineRect = coversWholeScreen ? null : ToScreenSpace(DrawRectangle).AABBFloat; } } } diff --git a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs index 50682ddfe0..0bbcb2b976 100644 --- a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -47,7 +46,7 @@ namespace osu.Game.Graphics.UserInterface protected override void LoadComplete() { - Active.BindDisabledChanged(disabled => Action = disabled ? (Action?)null : Active.Toggle, true); + Active.BindDisabledChanged(disabled => Action = disabled ? null : Active.Toggle, true); Active.BindValueChanged(_ => { updateActiveState(); diff --git a/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs b/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs index 9d1650c970..6787e17113 100644 --- a/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs @@ -34,6 +34,6 @@ namespace osu.Game.Graphics.UserInterface { } - public override IconUsage? GetIconForState(bool state) => state ? (IconUsage?)FontAwesome.Solid.Check : null; + public override IconUsage? GetIconForState(bool state) => state ? FontAwesome.Solid.Check : null; } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7bbad3bb72..1d9e2042ec 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -89,7 +89,7 @@ namespace osu.Game public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild; internal EndpointConfiguration CreateEndpoints() => - UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); + UseDevelopmentServer ? new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); @@ -583,6 +583,6 @@ namespace osu.Game ControlPointInfo IBeatSyncProvider.ControlPoints => Beatmap.Value.Beatmap.ControlPointInfo; IClock IBeatSyncProvider.Clock => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track : (IClock)null; - ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : (ChannelAmplitudes?)null; + ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : null; } } diff --git a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs index a6f412c00a..8cedd6b374 100644 --- a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs +++ b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs @@ -46,8 +46,8 @@ namespace osu.Game.Overlays.Mods && !ModUtils.CheckCompatibleSet(selectedMods.Value.Append(Mod)); } - protected override Colour4 BackgroundColour => incompatible.Value ? (Colour4)ColourProvider.Background6 : base.BackgroundColour; - protected override Colour4 ForegroundColour => incompatible.Value ? (Colour4)ColourProvider.Background5 : base.ForegroundColour; + protected override Colour4 BackgroundColour => incompatible.Value ? ColourProvider.Background6 : base.BackgroundColour; + protected override Colour4 ForegroundColour => incompatible.Value ? ColourProvider.Background5 : base.ForegroundColour; protected override void UpdateState() { diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index c51e6baa0d..3f788d10e3 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -442,7 +442,7 @@ namespace osu.Game.Overlays.Mods case ModType.DifficultyIncrease: case ModType.Automation: return hotkeyStyle == ModSelectHotkeyStyle.Sequential - ? (IModHotkeyHandler)SequentialModHotkeyHandler.Create(ModType) + ? SequentialModHotkeyHandler.Create(ModType) : new ClassicModHotkeyHandler(allowIncompatibleSelection); default: diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 4d14a31189..02eb395bd9 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -216,9 +216,9 @@ namespace osu.Game.Overlays.Mods base.OnMouseUp(e); } - protected virtual Colour4 BackgroundColour => Active.Value ? activeColour.Darken(0.3f) : (Colour4)ColourProvider.Background3; - protected virtual Colour4 ForegroundColour => Active.Value ? activeColour : (Colour4)ColourProvider.Background2; - protected virtual Colour4 TextColour => Active.Value ? (Colour4)ColourProvider.Background6 : Colour4.White; + protected virtual Colour4 BackgroundColour => Active.Value ? activeColour.Darken(0.3f) : ColourProvider.Background3; + protected virtual Colour4 ForegroundColour => Active.Value ? activeColour : ColourProvider.Background2; + protected virtual Colour4 TextColour => Active.Value ? ColourProvider.Background6 : Colour4.White; protected virtual void UpdateState() { diff --git a/osu.Game/Rulesets/EFRulesetInfo.cs b/osu.Game/Rulesets/EFRulesetInfo.cs index 3c671496de..bd67bdb93c 100644 --- a/osu.Game/Rulesets/EFRulesetInfo.cs +++ b/osu.Game/Rulesets/EFRulesetInfo.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets public int OnlineID { get => ID ?? -1; - set => ID = value >= 0 ? value : (int?)null; + set => ID = value >= 0 ? value : null; } #endregion diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 2285715564..ddc121eb5b 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Objects } public SliderPath(PathType type, Vector2[] controlPoints, double? expectedDistance = null) - : this(controlPoints.Select((c, i) => new PathControlPoint(c, i == 0 ? (PathType?)type : null)).ToArray(), expectedDistance) + : this(controlPoints.Select((c, i) => new PathControlPoint(c, i == 0 ? type : null)).ToArray(), expectedDistance) { } diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 09a090483d..020bae1c42 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Replays CurrentTime = Math.Clamp(time, frameStart, frameEnd); // In an important section, a mid-frame time cannot be used and a null is returned instead. - return inImportantSection && frameStart < time && time < frameEnd ? null : (double?)CurrentTime; + return inImportantSection && frameStart < time && time < frameEnd ? null : CurrentTime; } private double getFrameTime(int index) diff --git a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs index ec1e621491..e14354222b 100644 --- a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs +++ b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs @@ -24,6 +24,6 @@ namespace osu.Game.Screens.Edit.Components.Menus Action.Value = () => difficultyChangeFunc.Invoke(beatmapInfo); } - public override IconUsage? GetIconForState(bool state) => state ? (IconUsage?)FontAwesome.Solid.Check : null; + public override IconUsage? GetIconForState(bool state) => state ? FontAwesome.Solid.Check : null; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 847c96b762..08fc8383c3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } private static string? getCommonBank(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleBank).Distinct().Count() == 1 ? relevantControlPoints.First().SampleBank : null; - private static int? getCommonVolume(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? (int?)relevantControlPoints.First().SampleVolume : null; + private static int? getCommonVolume(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? relevantControlPoints.First().SampleVolume : null; private void updateBankFor(IEnumerable objects, string? newBank) { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7a9dae33b4..20bdf75b7d 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -908,6 +908,6 @@ namespace osu.Game.Screens.Edit ControlPointInfo IBeatSyncProvider.ControlPoints => editorBeatmap.ControlPointInfo; IClock IBeatSyncProvider.Clock => clock; - ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : (ChannelAmplitudes?)null; + ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : null; } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 0994a7d9bb..cd37c541ec 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -255,7 +255,7 @@ namespace osu.Game.Screens.Play ControlPointInfo IBeatSyncProvider.ControlPoints => beatmap.Beatmap.ControlPointInfo; IClock IBeatSyncProvider.Clock => GameplayClock; - ChannelAmplitudes? IBeatSyncProvider.Amplitudes => beatmap.TrackLoaded ? beatmap.Track.CurrentAmplitudes : (ChannelAmplitudes?)null; + ChannelAmplitudes? IBeatSyncProvider.Amplitudes => beatmap.TrackLoaded ? beatmap.Track.CurrentAmplitudes : null; private class HardwareCorrectionOffsetClock : FramedOffsetClock { diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 73a835d67f..0400e2c938 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -92,8 +92,8 @@ namespace osu.Game.Screens.Select.Carousel var beatmaps = carouselSet.Beatmaps.ToList(); return beatmaps.Count > maximum_difficulty_icons - ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset) - .Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset)) + ? beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset) + .Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset)) : beatmaps.Select(b => new FilterableDifficultyIcon(b)); } } diff --git a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs index 6754b65129..c9d4dc7811 100644 --- a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs +++ b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs @@ -448,14 +448,14 @@ namespace osu.Game.Screens.Utility mainArea.AddRange(new[] { - new LatencyArea(Key.Number1, betterSide == 1 ? mapDifficultyToTargetFrameRate(DifficultyLevel) : (int?)null) + new LatencyArea(Key.Number1, betterSide == 1 ? mapDifficultyToTargetFrameRate(DifficultyLevel) : null) { Width = 0.5f, VisualMode = { BindTarget = VisualMode }, IsActiveArea = { Value = true }, ReportUserBest = () => recordResult(betterSide == 0), }, - new LatencyArea(Key.Number2, betterSide == 0 ? mapDifficultyToTargetFrameRate(DifficultyLevel) : (int?)null) + new LatencyArea(Key.Number2, betterSide == 0 ? mapDifficultyToTargetFrameRate(DifficultyLevel) : null) { Width = 0.5f, VisualMode = { BindTarget = VisualMode }, diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index 20232012f6..6fc9f60177 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -56,7 +56,7 @@ namespace osu.Game.Storyboards { var first = Alpha.Commands.FirstOrDefault(); - return first?.StartValue == 0 ? first.StartTime : (double?)null; + return first?.StartValue == 0 ? first.StartTime : null; } } From 224c23df77c0430f78676377b8d155fb5375eb2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 14:58:22 +0900 Subject: [PATCH 192/803] Downgrade to v9 to support iOS toolchain --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 1f59f19eaa..235feea8ce 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@  - 10.0 + 9.0 true enable From 545c04aaf5acfed70065131865821d9ce305dd64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 16:18:51 +0900 Subject: [PATCH 193/803] Fix dangerous `File.Copy` causing intermittent realm migration test failures Resolves an issue I've been able to locally reproduce on windows. Basically, the `File.Copy` would begin while realm was blocking. The "restore" operation is posted to the `SynchronizationContext` to run on next update call, but in the mean time the copy would begin, causing a conflict of interest. Very dangerous. Only really noticeable on windows. --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 7d5f0bcd0c..a803974d30 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -254,7 +254,7 @@ namespace osu.Game.Tests.NonVisual Assert.That(File.Exists(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME))); Directory.CreateDirectory(customPath2); - File.Copy(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME), Path.Combine(customPath2, OsuGameBase.CLIENT_DATABASE_FILENAME)); + File.WriteAllText(Path.Combine(customPath2, OsuGameBase.CLIENT_DATABASE_FILENAME), "I am a text"); // Fails because file already exists. Assert.Throws(() => osu.Migrate(customPath2)); From 453b77e0dcac7c0f6742f5d51f046dd535abaf9a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 16:18:51 +0900 Subject: [PATCH 194/803] Fix dangerous `File.Copy` causing intermittent realm migration test failures Resolves an issue I've been able to locally reproduce on windows. Basically, the `File.Copy` would begin while realm was blocking. The "restore" operation is posted to the `SynchronizationContext` to run on next update call, but in the mean time the copy would begin, causing a conflict of interest. Very dangerous. Only really noticeable on windows. --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 7d5f0bcd0c..a803974d30 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -254,7 +254,7 @@ namespace osu.Game.Tests.NonVisual Assert.That(File.Exists(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME))); Directory.CreateDirectory(customPath2); - File.Copy(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME), Path.Combine(customPath2, OsuGameBase.CLIENT_DATABASE_FILENAME)); + File.WriteAllText(Path.Combine(customPath2, OsuGameBase.CLIENT_DATABASE_FILENAME), "I am a text"); // Fails because file already exists. Assert.Throws(() => osu.Migrate(customPath2)); From 0755430006d582dded0f6a01fcef137a95e666c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 16:53:04 +0900 Subject: [PATCH 195/803] Use `AddOnce` for update calls --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 5e468e975a..7cab868e28 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -64,13 +64,13 @@ namespace osu.Game.Beatmaps { base.LoadComplete(); - currentRuleset.BindValueChanged(_ => updateTrackedBindables()); + currentRuleset.BindValueChanged(_ => Scheduler.AddOnce(updateTrackedBindables)); currentMods.BindValueChanged(mods => { modSettingChangeTracker?.Dispose(); - updateTrackedBindables(); + Scheduler.AddOnce(updateTrackedBindables); modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); modSettingChangeTracker.SettingChanged += _ => From c1791276700bfcb0d368d44321063343d8263d0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 16:53:14 +0900 Subject: [PATCH 196/803] Remove unused bindable retrieval flow --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 35 ++------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 7cab868e28..ef0fa36b16 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -89,7 +89,9 @@ namespace osu.Game.Beatmaps /// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state (but not during updates to ruleset and mods if a stale value is already propagated). public IBindable GetBindableDifficulty(IBeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) { - var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken); + var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken); + + updateBindable(bindable, currentRuleset.Value, currentMods.Value, cancellationToken); lock (bindableUpdateLock) trackedBindables.Add(bindable); @@ -97,21 +99,6 @@ namespace osu.Game.Beatmaps return bindable; } - /// - /// Retrieves a bindable containing the star difficulty of a with a given and combination. - /// - /// - /// The bindable will not update to follow the currently-selected ruleset and mods or its settings. - /// - /// The to get the difficulty of. - /// The to get the difficulty with. If null, the 's ruleset is used. - /// The s to get the difficulty with. If null, no mods will be assumed. - /// An optional which stops updating the star difficulty for the given . - /// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state. - public IBindable GetBindableDifficulty(IBeatmapInfo beatmapInfo, IRulesetInfo? rulesetInfo, IEnumerable? mods, - CancellationToken cancellationToken = default) - => createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken); - /// /// Retrieves the difficulty of a . /// @@ -200,22 +187,6 @@ namespace osu.Game.Beatmaps } } - /// - /// Creates a new and triggers an initial value update. - /// - /// The that star difficulty should correspond to. - /// The initial to get the difficulty with. - /// The initial s to get the difficulty with. - /// An optional which stops updating the star difficulty for the given . - /// The . - private BindableStarDifficulty createBindable(IBeatmapInfo beatmapInfo, IRulesetInfo? initialRulesetInfo, IEnumerable? initialMods, - CancellationToken cancellationToken) - { - var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken); - updateBindable(bindable, initialRulesetInfo, initialMods, cancellationToken); - return bindable; - } - /// /// Updates the value of a with a given ruleset + mods. /// From 26c5b59f6d55976d36a6007aecab4b1e7fb9f39a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Jun 2022 14:39:47 +0200 Subject: [PATCH 197/803] Replace usages of `string.To{Lower,Upper}()` --- CodeAnalysis/BannedSymbols.txt | 2 ++ osu.Game.Rulesets.Catch/CatchSkinComponent.cs | 2 +- osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 2 +- osu.Game.Rulesets.Osu/OsuSkinComponent.cs | 2 +- .../Skinning/TestSceneDrawableTaikoMascot.cs | 6 +++--- osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs | 2 +- osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs | 4 ++-- osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs | 2 +- osu.Game.Tournament/Models/TournamentTeam.cs | 4 ++-- .../Screens/Ladder/Components/DrawableTournamentRound.cs | 4 ++-- osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs | 2 +- .../Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs | 2 +- osu.Game/Database/IModelImporter.cs | 2 +- osu.Game/Database/ModelManager.cs | 2 +- osu.Game/Database/RealmArchiveModelImporter.cs | 2 +- osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs | 2 +- osu.Game/Online/API/Requests/GetUserRequest.cs | 2 +- osu.Game/Online/Leaderboards/UserTopScoreContainer.cs | 2 +- osu.Game/Overlays/Chat/DaySeparator.cs | 3 ++- osu.Game/Overlays/News/NewsCard.cs | 3 ++- osu.Game/Overlays/TabControlOverlayHeader.cs | 6 +++--- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 2 +- osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs | 2 +- .../OnlinePlay/Match/Components/RoomSettingsOverlay.cs | 2 +- osu.Game/Screens/Select/FilterQueryParser.cs | 2 +- 25 files changed, 35 insertions(+), 31 deletions(-) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index 77197c4c51..8b5431e2d6 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -17,3 +17,5 @@ M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Gen M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks. P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks. M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever. +M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString. +M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString. diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponent.cs b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs index 0fcdd34ca3..e79da667da 100644 --- a/osu.Game.Rulesets.Catch/CatchSkinComponent.cs +++ b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Catch protected override string RulesetPrefix => "catch"; // todo: use CatchRuleset.SHORT_NAME; - protected override string ComponentName => Component.ToString().ToLower(); + protected override string ComponentName => Component.ToString().ToLowerInvariant(); } } diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index b709f85523..21b362df00 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME; - protected override string ComponentName => Component.ToString().ToLower(); + protected override string ComponentName => Component.ToString().ToLowerInvariant(); } public enum ManiaSkinComponents diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponent.cs b/osu.Game.Rulesets.Osu/OsuSkinComponent.cs index 82c4005c5e..0abaf2c924 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponent.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponent.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Osu protected override string RulesetPrefix => OsuRuleset.SHORT_NAME; - protected override string ComponentName => Component.ToString().ToLower(); + protected override string ComponentName => Component.ToString().ToLowerInvariant(); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index 1bf6c0560a..ef95358d34 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning createDrawableRuleset(); assertStateAfterResult(new JudgementResult(new Swell(), new TaikoSwellJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Clear); - AddUntilStep($"state reverts to {expectedStateAfterClear.ToString().ToLower()}", () => allMascotsIn(expectedStateAfterClear)); + AddUntilStep($"state reverts to {expectedStateAfterClear.ToString().ToLowerInvariant()}", () => allMascotsIn(expectedStateAfterClear)); } private void setBeatmap(bool kiai = false) @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning { TaikoMascotAnimationState[] mascotStates = null; - AddStep($"{judgementResult.Type.ToString().ToLower()} result for {judgementResult.Judgement.GetType().Name.Humanize(LetterCasing.LowerCase)}", + AddStep($"{judgementResult.Type.ToString().ToLowerInvariant()} result for {judgementResult.Judgement.GetType().Name.Humanize(LetterCasing.LowerCase)}", () => { applyNewResult(judgementResult); @@ -204,7 +204,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Schedule(() => mascotStates = animatedMascots.Select(mascot => mascot.State.Value).ToArray()); }); - AddAssert($"state is {expectedState.ToString().ToLower()}", () => mascotStates.All(state => state == expectedState)); + AddAssert($"state is {expectedState.ToString().ToLowerInvariant()}", () => mascotStates.All(state => state == expectedState)); } private void applyNewResult(JudgementResult judgementResult) diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs index 676a3d4bc3..63314a6822 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Taiko protected override string RulesetPrefix => TaikoRuleset.SHORT_NAME; - protected override string ComponentName => Component.ToString().ToLower(); + protected override string ComponentName => Component.ToString().ToLowerInvariant(); } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs index bacd9b41f8..26a37fc464 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs @@ -139,10 +139,10 @@ namespace osu.Game.Rulesets.Taiko.UI private static Texture getAnimationFrame(ISkin skin, TaikoMascotAnimationState state, int frameIndex) { - var texture = skin.GetTexture($"pippidon{state.ToString().ToLower()}{frameIndex}"); + var texture = skin.GetTexture($"pippidon{state.ToString().ToLowerInvariant()}{frameIndex}"); if (frameIndex == 0 && texture == null) - texture = skin.GetTexture($"pippidon{state.ToString().ToLower()}"); + texture = skin.GetTexture($"pippidon{state.ToString().ToLowerInvariant()}"); return texture; } diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index f9c4985219..c4e8bbfccf 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs @@ -192,7 +192,7 @@ namespace osu.Game.Tests.Gameplay AddStep("apply perfect hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = HitResult.Perfect })); AddAssert("not failed", () => !processor.HasFailed); - AddStep($"apply {resultApplied.ToString().ToLower()} hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = resultApplied })); + AddStep($"apply {resultApplied.ToString().ToLowerInvariant()} hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = resultApplied })); AddAssert("failed", () => processor.HasFailed); } diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs index 9dbe23b4b3..ac57f748da 100644 --- a/osu.Game.Tournament/Models/TournamentTeam.cs +++ b/osu.Game.Tournament/Models/TournamentTeam.cs @@ -66,14 +66,14 @@ namespace osu.Game.Tournament.Models { // use a sane default flag name based on acronym. if (val.OldValue.StartsWith(FlagName.Value, StringComparison.InvariantCultureIgnoreCase)) - FlagName.Value = val.NewValue.Length >= 2 ? val.NewValue?.Substring(0, 2).ToUpper() : string.Empty; + FlagName.Value = val.NewValue.Length >= 2 ? val.NewValue?.Substring(0, 2).ToUpperInvariant() : string.Empty; }; FullName.ValueChanged += val => { // use a sane acronym based on full name. if (val.OldValue.StartsWith(Acronym.Value, StringComparison.InvariantCultureIgnoreCase)) - Acronym.Value = val.NewValue.Length >= 3 ? val.NewValue?.Substring(0, 3).ToUpper() : string.Empty; + Acronym.Value = val.NewValue.Length >= 3 ? val.NewValue?.Substring(0, 3).ToUpperInvariant() : string.Empty; }; } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs index ac196130d6..544725996a 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs @@ -49,10 +49,10 @@ namespace osu.Game.Tournament.Screens.Ladder.Components }; name = round.Name.GetBoundCopy(); - name.BindValueChanged(n => textName.Text = ((losers ? "Losers " : "") + round.Name).ToUpper(), true); + name.BindValueChanged(n => textName.Text = ((losers ? "Losers " : "") + round.Name).ToUpperInvariant(), true); description = round.Description.GetBoundCopy(); - description.BindValueChanged(n => textDescription.Text = round.Description.Value?.ToUpper(), true); + description.BindValueChanged(n => textDescription.Text = round.Description.Value?.ToUpperInvariant(), true); } } } diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index df6e8e816e..925c697346 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -198,7 +198,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro { row.Add(new Sprite { - Texture = textures.Get($"Mods/{mods.ToLower()}"), + Texture = textures.Get($"Mods/{mods.ToLowerInvariant()}"), Scale = new Vector2(0.5f) }); } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs index 78481ac27a..bc0fcb92bb 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs @@ -64,7 +64,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons }; favouriteRequest.Failure += e => { - Logger.Error(e, $"Failed to {actionType.ToString().ToLower()} beatmap: {e.Message}"); + Logger.Error(e, $"Failed to {actionType.ToString().ToLowerInvariant()} beatmap: {e.Message}"); Enabled.Value = true; }; diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index 26342cb5fe..ebb8be39ef 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.cs @@ -26,7 +26,7 @@ namespace osu.Game.Database /// /// A user displayable name for the model type associated with this manager. /// - string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}"; + string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLowerInvariant()}"; /// /// Fired when the user requests to view the resulting import. diff --git a/osu.Game/Database/ModelManager.cs b/osu.Game/Database/ModelManager.cs index e0c3dccf57..9603412178 100644 --- a/osu.Game/Database/ModelManager.cs +++ b/osu.Game/Database/ModelManager.cs @@ -201,6 +201,6 @@ namespace osu.Game.Database public Action? PostNotification { get; set; } - public virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}"; + public virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLowerInvariant()}"; } } diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 27bfaf05be..a3349d9918 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -549,6 +549,6 @@ namespace osu.Game.Database yield return f.Filename; } - public virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}"; + public virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLowerInvariant()}"; } } diff --git a/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs b/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs index 75b0cf8027..2d8a8b3b61 100644 --- a/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs @@ -26,7 +26,7 @@ namespace osu.Game.Online.API.Requests var req = base.CreateWebRequest(); req.AddParameter("spotlight", spotlight.ToString()); - req.AddParameter("filter", sort.ToString().ToLower()); + req.AddParameter("filter", sort.ToString().ToLowerInvariant()); return req; } diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index ab45e8e48f..7dcf75950e 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -45,7 +45,7 @@ namespace osu.Game.Online.API.Requests Ruleset = ruleset; } - protected override string Target => Lookup != null ? $@"users/{Lookup}/{Ruleset?.ShortName}?key={lookupType.ToString().ToLower()}" : $@"me/{Ruleset?.ShortName}"; + protected override string Target => Lookup != null ? $@"users/{Lookup}/{Ruleset?.ShortName}?key={lookupType.ToString().ToLowerInvariant()}" : $@"me/{Ruleset?.ShortName}"; private enum LookupType { diff --git a/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs index df8717fe6d..2d2d82821c 100644 --- a/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs +++ b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs @@ -48,7 +48,7 @@ namespace osu.Game.Online.Leaderboards { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = @"your personal best".ToUpper(), + Text = @"your personal best".ToUpperInvariant(), Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), }, scoreContainer = new Container diff --git a/osu.Game/Overlays/Chat/DaySeparator.cs b/osu.Game/Overlays/Chat/DaySeparator.cs index c7961d1a39..be0b53785c 100644 --- a/osu.Game/Overlays/Chat/DaySeparator.cs +++ b/osu.Game/Overlays/Chat/DaySeparator.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -79,7 +80,7 @@ namespace osu.Game.Overlays.Chat { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Text = time.ToLocalTime().ToString("dd MMMM yyyy").ToUpper(), + Text = time.ToLocalTime().ToLocalisableString(@"dd MMMM yyyy").ToUpper(), Font = OsuFont.Torus.With(size: TextSize, weight: FontWeight.SemiBold), Colour = colourProvider?.Content1 ?? Colour4.White, }, diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs index 69094a0df7..eb76522e11 100644 --- a/osu.Game/Overlays/News/NewsCard.cs +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -146,7 +147,7 @@ namespace osu.Game.Overlays.News }, new OsuSpriteText { - Text = date.ToString("d MMM yyyy").ToUpper(), + Text = date.ToLocalisableString(@"d MMM yyyy").ToUpper(), Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), Margin = new MarginPadding { diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs index 4e32afb86f..84a5bf1144 100644 --- a/osu.Game/Overlays/TabControlOverlayHeader.cs +++ b/osu.Game/Overlays/TabControlOverlayHeader.cs @@ -109,15 +109,15 @@ namespace osu.Game.Overlays : base(value) { if (!(Value is Enum enumValue)) - Text.Text = Value.ToString().ToLower(); + Text.Text = Value.ToString().ToLowerInvariant(); else { var localisableDescription = enumValue.GetLocalisableDescription(); string nonLocalisableDescription = enumValue.GetDescription(); - // If localisable == non-localisable, then we must have a basic string, so .ToLower() is used. + // If localisable == non-localisable, then we must have a basic string, so .ToLowerInvariant() is used. Text.Text = localisableDescription.Equals(nonLocalisableDescription) - ? nonLocalisableDescription.ToLower() + ? nonLocalisableDescription.ToLowerInvariant() : localisableDescription; } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index cb6c4a9928..00b1ca9dc9 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (edgeType == EdgeType.None) yield break; - string postfix = hitObject is IHasDuration ? edgeType.ToString().ToLower() : null; + string postfix = hitObject is IHasDuration ? edgeType.ToString().ToLowerInvariant() : null; if (maxVolume <= muted_threshold) { diff --git a/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs b/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs index a461f63d2d..2a222aece0 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Edit.Checks } } - private bool hasAudioExtension(string filename) => audioExtensions.Any(filename.ToLower().EndsWith); + private bool hasAudioExtension(string filename) => audioExtensions.Any(filename.ToLowerInvariant().EndsWith); private bool probablyHasAudioData(Stream data) => data.Length > min_bytes_threshold; public class IssueTemplateTooShort : IssueTemplate diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs index c295e9f85e..71447b15e2 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs @@ -135,7 +135,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components new OsuSpriteText { Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 12), - Text = title.ToUpper(), + Text = title.ToUpperInvariant(), }, content = new Container { diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 92f707ae3e..03b72bf5e9 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Select { foreach (Match match in query_syntax_regex.Matches(query)) { - string key = match.Groups["key"].Value.ToLower(); + string key = match.Groups["key"].Value.ToLowerInvariant(); var op = parseOperator(match.Groups["op"].Value); string value = match.Groups["value"].Value; From 2ae48d5a87f3a71c67742869973d452106697e3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 17:46:46 +0900 Subject: [PATCH 198/803] Rename incorrect parameter name in `BeatmapManager.GetWorkingBeatmap` --- osu.Game/Beatmaps/BeatmapManager.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 816808638f..73c879226b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -418,22 +418,22 @@ namespace osu.Game.Beatmaps #region Implementation of IWorkingBeatmapCache - public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo? importedBeatmap) + public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo? beatmapInfo) { // Detached sets don't come with files. // If we seem to be missing files, now is a good time to re-fetch. - if (importedBeatmap?.BeatmapSet?.Files.Count == 0) + if (beatmapInfo?.BeatmapSet?.Files.Count == 0) { Realm.Run(r => { - var refetch = r.Find(importedBeatmap.ID)?.Detach(); + var refetch = r.Find(beatmapInfo.ID)?.Detach(); if (refetch != null) - importedBeatmap = refetch; + beatmapInfo = refetch; }); } - return workingBeatmapCache.GetWorkingBeatmap(importedBeatmap); + return workingBeatmapCache.GetWorkingBeatmap(beatmapInfo); } void IWorkingBeatmapCache.Invalidate(BeatmapSetInfo beatmapSetInfo) => workingBeatmapCache.Invalidate(beatmapSetInfo); From 823b3c1c0fcc74b4260c7ab09bdf13468f8984e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 19:04:29 +0900 Subject: [PATCH 199/803] Ensure `WorkingBeatmap` is always using a detached instance --- osu.Game/Beatmaps/BeatmapManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 73c879226b..670dba14ec 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -422,7 +422,7 @@ namespace osu.Game.Beatmaps { // Detached sets don't come with files. // If we seem to be missing files, now is a good time to re-fetch. - if (beatmapInfo?.BeatmapSet?.Files.Count == 0) + if (beatmapInfo?.IsManaged == true || beatmapInfo?.BeatmapSet?.Files.Count == 0) { Realm.Run(r => { @@ -433,6 +433,8 @@ namespace osu.Game.Beatmaps }); } + Debug.Assert(beatmapInfo?.IsManaged != true); + return workingBeatmapCache.GetWorkingBeatmap(beatmapInfo); } From 06d59b717c69ed873ca6a65cd5d02b45777f1fa4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 18:39:53 +0900 Subject: [PATCH 200/803] Move beatmap processing tasks to new `BeatmapUpdater` class --- .../Database/BeatmapImporterTests.cs | 58 +++++++------- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 8 +- osu.Game/Beatmaps/BeatmapImporter.cs | 70 ++--------------- osu.Game/Beatmaps/BeatmapManager.cs | 13 +++- osu.Game/Beatmaps/BeatmapUpdater.cs | 78 +++++++++++++++++++ osu.Game/OsuGameBase.cs | 2 +- 6 files changed, 130 insertions(+), 99 deletions(-) create mode 100644 osu.Game/Beatmaps/BeatmapUpdater.cs diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index be11476c73..f54dd3eb11 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -35,7 +35,8 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using (var importer = new BeatmapImporter(storage, realm)) + var importer = new BeatmapImporter(storage, realm); + using (new RealmRulesetStore(realm, storage)) { var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz")); @@ -76,7 +77,8 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using (var importer = new BeatmapImporter(storage, realm)) + var importer = new BeatmapImporter(storage, realm); + using (new RealmRulesetStore(realm, storage)) { var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz")); @@ -134,7 +136,8 @@ namespace osu.Game.Tests.Database var manager = new ModelManager(storage, realm); - using (var importer = new BeatmapImporter(storage, realm)) + var importer = new BeatmapImporter(storage, realm); + using (new RealmRulesetStore(realm, storage)) { Task.Run(async () => @@ -160,7 +163,8 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using (var importer = new BeatmapImporter(storage, realm)) + var importer = new BeatmapImporter(storage, realm); + using (new RealmRulesetStore(realm, storage)) { var imported = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz")); @@ -187,7 +191,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); await LoadOszIntoStore(importer, realm.Realm); @@ -199,7 +203,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -217,7 +221,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -231,7 +235,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? tempPath = TestResources.GetTestBeatmapForImport(); @@ -261,7 +265,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -281,7 +285,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -317,7 +321,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -366,7 +370,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -417,7 +421,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -465,7 +469,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -513,7 +517,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -548,7 +552,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var progressNotification = new ImportProgressNotification(); @@ -586,7 +590,7 @@ namespace osu.Game.Tests.Database Interlocked.Increment(ref loggedExceptionCount); }; - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -638,7 +642,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm, batchImport: true); @@ -665,7 +669,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(storage, realmFactory); + var importer = new BeatmapImporter(storage, realmFactory); using var store = new RealmRulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Realm); @@ -697,7 +701,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -724,7 +728,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -750,7 +754,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var metadata = new BeatmapMetadata @@ -798,7 +802,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -815,7 +819,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -851,7 +855,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -893,7 +897,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -944,7 +948,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 1d33a895eb..0bf47141e4 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -212,17 +212,17 @@ namespace osu.Game.Tests.Online { } - protected override BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) + protected override BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapUpdater beatmapUpdater) { - return new TestBeatmapImporter(this, storage, realm, onlineLookupQueue); + return new TestBeatmapImporter(this, storage, realm, beatmapUpdater); } internal class TestBeatmapImporter : BeatmapImporter { private readonly TestBeatmapManager testBeatmapManager; - public TestBeatmapImporter(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) - : base(storage, databaseAccess, beatmapOnlineLookupQueue) + public TestBeatmapImporter(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapUpdater beatmapUpdater) + : base(storage, databaseAccess, beatmapUpdater) { this.testBeatmapManager = testBeatmapManager; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 237088036c..0620e5d9b1 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -6,10 +6,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; -using osu.Framework.Audio.Track; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics.Textures; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Testing; @@ -20,8 +18,6 @@ using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Models; using osu.Game.Rulesets; -using osu.Game.Rulesets.Objects; -using osu.Game.Skinning; using Realms; namespace osu.Game.Beatmaps @@ -30,18 +26,18 @@ namespace osu.Game.Beatmaps /// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps. /// [ExcludeFromDynamicCompile] - public class BeatmapImporter : RealmArchiveModelImporter, IDisposable + public class BeatmapImporter : RealmArchiveModelImporter { public override IEnumerable HandledExtensions => new[] { ".osz" }; protected override string[] HashableFileTypes => new[] { ".osu" }; - private readonly BeatmapOnlineLookupQueue? onlineLookupQueue; + private readonly BeatmapUpdater? beatmapUpdater; - public BeatmapImporter(Storage storage, RealmAccess realm, BeatmapOnlineLookupQueue? onlineLookupQueue = null) + public BeatmapImporter(Storage storage, RealmAccess realm, BeatmapUpdater? beatmapUpdater = null) : base(storage, realm) { - this.onlineLookupQueue = onlineLookupQueue; + this.beatmapUpdater = beatmapUpdater; } protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == ".osz"; @@ -65,8 +61,7 @@ namespace osu.Game.Beatmaps bool hadOnlineIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0); - onlineLookupQueue?.Update(beatmapSet); - + // TODO: this may no longer be valid as we aren't doing an online population at this point. // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID. if (hadOnlineIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0)) { @@ -85,6 +80,8 @@ namespace osu.Game.Beatmaps // If this is ever an issue, we can consider marking as pending delete but not resetting the IDs (but care will be required for // beatmaps, which don't have their own `DeletePending` state). + beatmapUpdater?.Process(beatmapSet, realm); + if (beatmapSet.OnlineID > 0) { var existingSetWithSameOnlineID = realm.All().SingleOrDefault(b => b.OnlineID == beatmapSet.OnlineID); @@ -278,64 +275,11 @@ namespace osu.Game.Beatmaps MD5Hash = memoryStream.ComputeMD5Hash(), }; - updateBeatmapStatistics(beatmap, decoded); - beatmaps.Add(beatmap); } } return beatmaps; } - - private void updateBeatmapStatistics(BeatmapInfo beatmap, IBeatmap decoded) - { - var rulesetInstance = ((IRulesetInfo)beatmap.Ruleset).CreateInstance(); - - decoded.BeatmapInfo.Ruleset = rulesetInstance.RulesetInfo; - - // TODO: this should be done in a better place once we actually need to dynamically update it. - beatmap.StarRating = rulesetInstance.CreateDifficultyCalculator(new DummyConversionBeatmap(decoded)).Calculate().StarRating; - beatmap.Length = calculateLength(decoded); - beatmap.BPM = 60000 / decoded.GetMostCommonBeatLength(); - } - - private double calculateLength(IBeatmap b) - { - if (!b.HitObjects.Any()) - return 0; - - var lastObject = b.HitObjects.Last(); - - //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). - double endTime = lastObject.GetEndTime(); - double startTime = b.HitObjects.First().StartTime; - - return endTime - startTime; - } - - public void Dispose() - { - onlineLookupQueue?.Dispose(); - } - - /// - /// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation. - /// - private class DummyConversionBeatmap : WorkingBeatmap - { - private readonly IBeatmap beatmap; - - public DummyConversionBeatmap(IBeatmap beatmap) - : base(beatmap.BeatmapInfo, null) - { - this.beatmap = beatmap; - } - - protected override IBeatmap GetBeatmap() => beatmap; - protected override Texture? GetBackground() => null; - protected override Track? GetBeatmapTrack() => null; - protected internal override ISkin? GetSkin() => null; - public override Stream? GetStream(string storagePath) => null; - } } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 670dba14ec..ee7fcb5b47 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -42,9 +42,10 @@ namespace osu.Game.Beatmaps private readonly WorkingBeatmapCache workingBeatmapCache; private readonly BeatmapOnlineLookupQueue? onlineBeatmapLookupQueue; + private readonly BeatmapUpdater? beatmapUpdater; public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, - WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false) + WorkingBeatmap? defaultBeatmap = null, BeatmapDifficultyCache? difficultyCache = null, bool performOnlineLookups = false) : base(storage, realm) { if (performOnlineLookups) @@ -52,14 +53,18 @@ namespace osu.Game.Beatmaps if (api == null) throw new ArgumentNullException(nameof(api), "API must be provided if online lookups are required."); + if (difficultyCache == null) + throw new ArgumentNullException(nameof(difficultyCache), "Difficulty cache must be provided if online lookups are required."); + onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue(api, storage); + beatmapUpdater = new BeatmapUpdater(this, onlineBeatmapLookupQueue, difficultyCache); } var userResources = new RealmFileStore(realm, storage).Store; BeatmapTrackStore = audioManager.GetTrackStore(userResources); - beatmapImporter = CreateBeatmapImporter(storage, realm, rulesets, onlineBeatmapLookupQueue); + beatmapImporter = CreateBeatmapImporter(storage, realm, rulesets, beatmapUpdater); beatmapImporter.PostNotification = obj => PostNotification?.Invoke(obj); workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); @@ -71,8 +76,8 @@ namespace osu.Game.Beatmaps return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host); } - protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue? onlineLookupQueue) => - new BeatmapImporter(storage, realm, onlineLookupQueue); + protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapUpdater? beatmapUpdater) => + new BeatmapImporter(storage, realm, beatmapUpdater); /// /// Create a new beatmap set, backed by a model, diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs new file mode 100644 index 0000000000..8ccaf893a7 --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapUpdater.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. + +#nullable enable + +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Extensions; +using osu.Game.Database; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Beatmaps +{ + /// + /// Handles all processing required to ensure a local beatmap is in a consistent state with any changes. + /// + public class BeatmapUpdater + { + private readonly BeatmapManager beatmapManager; + private readonly BeatmapOnlineLookupQueue onlineLookupQueue; + private readonly BeatmapDifficultyCache difficultyCache; + + public BeatmapUpdater(BeatmapManager beatmapManager, BeatmapOnlineLookupQueue onlineLookupQueue, BeatmapDifficultyCache difficultyCache) + { + this.beatmapManager = beatmapManager; + this.onlineLookupQueue = onlineLookupQueue; + this.difficultyCache = difficultyCache; + } + + /// + /// Queue a beatmap for background processing. + /// + public void Queue(Live beatmap) + { + // For now, just fire off a task. + // TODO: Add actual queueing probably. + Task.Factory.StartNew(() => beatmap.PerformRead(Process)); + } + + /// + /// Run all processing on a beatmap immediately. + /// + public void Process(BeatmapSetInfo beatmapSet) + { + beatmapSet.Realm.Write(() => + { + onlineLookupQueue.Update(beatmapSet); + + foreach (var beatmap in beatmapSet.Beatmaps) + { + var working = beatmapManager.GetWorkingBeatmap(beatmap); + + // Because we aren't guaranteed all processing will happen on this thread, it's very hard to use the live realm object. + // This can be fixed by adding a synchronous flow to `BeatmapDifficultyCache`. + var detachedBeatmap = beatmap.Detach(); + + beatmap.StarRating = difficultyCache.GetDifficultyAsync(detachedBeatmap).GetResultSafely()?.Stars ?? 0; + beatmap.Length = calculateLength(working.Beatmap); + beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); + } + }); + } + + private double calculateLength(IBeatmap b) + { + if (!b.HitObjects.Any()) + return 0; + + var lastObject = b.HitObjects.Last(); + + //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). + double endTime = lastObject.GetEndTime(); + double startTime = b.HitObjects.First().StartTime; + + return endTime - startTime; + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7bbad3bb72..403c5d1b49 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -272,7 +272,7 @@ namespace osu.Game // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, difficultyCache, LocalConfig)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true)); dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API)); dependencies.Cache(ScoreDownloader = new ScoreModelDownloader(ScoreManager, API)); From fe570d8052356d3c5a64410719e3ea716a920f17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 18:57:57 +0900 Subject: [PATCH 201/803] Queue beatmaps for update after editing --- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index ee7fcb5b47..1b85207f22 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -324,6 +324,10 @@ namespace osu.Game.Beatmaps workingBeatmapCache.Invalidate(beatmapInfo); + Debug.Assert(beatmapInfo.BeatmapSet != null); + + beatmapUpdater?.Queue(Realm.Run(r => r.Find(setInfo.ID).ToLive(Realm))); + static string createBeatmapFilenameFromMetadata(BeatmapInfo beatmapInfo) { var metadata = beatmapInfo.Metadata; From 021b16f2f3635469fd5b9673424582dc3dfafebf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 19:25:18 +0900 Subject: [PATCH 202/803] Ensure `WorkingBeatmap` is invalidated after update --- osu.Game/Beatmaps/BeatmapUpdater.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 8ccaf893a7..7689f4d007 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -16,13 +16,13 @@ namespace osu.Game.Beatmaps /// public class BeatmapUpdater { - private readonly BeatmapManager beatmapManager; + private readonly IWorkingBeatmapCache workingBeatmapCache; private readonly BeatmapOnlineLookupQueue onlineLookupQueue; private readonly BeatmapDifficultyCache difficultyCache; - public BeatmapUpdater(BeatmapManager beatmapManager, BeatmapOnlineLookupQueue onlineLookupQueue, BeatmapDifficultyCache difficultyCache) + public BeatmapUpdater(IWorkingBeatmapCache workingBeatmapCache, BeatmapOnlineLookupQueue onlineLookupQueue, BeatmapDifficultyCache difficultyCache) { - this.beatmapManager = beatmapManager; + this.workingBeatmapCache = workingBeatmapCache; this.onlineLookupQueue = onlineLookupQueue; this.difficultyCache = difficultyCache; } @@ -48,17 +48,19 @@ namespace osu.Game.Beatmaps foreach (var beatmap in beatmapSet.Beatmaps) { - var working = beatmapManager.GetWorkingBeatmap(beatmap); - // Because we aren't guaranteed all processing will happen on this thread, it's very hard to use the live realm object. // This can be fixed by adding a synchronous flow to `BeatmapDifficultyCache`. var detachedBeatmap = beatmap.Detach(); beatmap.StarRating = difficultyCache.GetDifficultyAsync(detachedBeatmap).GetResultSafely()?.Stars ?? 0; + + var working = workingBeatmapCache.GetWorkingBeatmap(beatmap); beatmap.Length = calculateLength(working.Beatmap); beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); } }); + + workingBeatmapCache.Invalidate(beatmapSet); } private double calculateLength(IBeatmap b) From 6999933d33a4289f4dd5645079f2fb2d3f8f04e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 18:27:47 +0900 Subject: [PATCH 203/803] Split updater process into realm transaction and non-transaction --- osu.Game/Beatmaps/BeatmapManager.cs | 13 ++++++---- osu.Game/Beatmaps/BeatmapUpdater.cs | 39 +++++++++++++++-------------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 1b85207f22..effa0ab094 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -319,15 +319,18 @@ namespace osu.Game.Beatmaps AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo)); - Realm.Write(r => setInfo.CopyChangesToRealm(r.Find(setInfo.ID))); + Realm.Write(r => + { + var liveBeatmapSet = r.Find(setInfo.ID); + + setInfo.CopyChangesToRealm(liveBeatmapSet); + + beatmapUpdater?.Process(liveBeatmapSet, r); + }); } - workingBeatmapCache.Invalidate(beatmapInfo); - Debug.Assert(beatmapInfo.BeatmapSet != null); - beatmapUpdater?.Queue(Realm.Run(r => r.Find(setInfo.ID).ToLive(Realm))); - static string createBeatmapFilenameFromMetadata(BeatmapInfo beatmapInfo) { var metadata = beatmapInfo.Metadata; diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 7689f4d007..a24407a734 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using osu.Framework.Extensions; using osu.Game.Database; using osu.Game.Rulesets.Objects; +using Realms; namespace osu.Game.Beatmaps { @@ -40,27 +41,27 @@ namespace osu.Game.Beatmaps /// /// Run all processing on a beatmap immediately. /// - public void Process(BeatmapSetInfo beatmapSet) + public void Process(BeatmapSetInfo beatmapSet) => beatmapSet.Realm.Write(r => Process(beatmapSet, r)); + + public void Process(BeatmapSetInfo beatmapSet, Realm realm) { - beatmapSet.Realm.Write(() => - { - onlineLookupQueue.Update(beatmapSet); - - foreach (var beatmap in beatmapSet.Beatmaps) - { - // Because we aren't guaranteed all processing will happen on this thread, it's very hard to use the live realm object. - // This can be fixed by adding a synchronous flow to `BeatmapDifficultyCache`. - var detachedBeatmap = beatmap.Detach(); - - beatmap.StarRating = difficultyCache.GetDifficultyAsync(detachedBeatmap).GetResultSafely()?.Stars ?? 0; - - var working = workingBeatmapCache.GetWorkingBeatmap(beatmap); - beatmap.Length = calculateLength(working.Beatmap); - beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); - } - }); - + // Before we use below, we want to invalidate. workingBeatmapCache.Invalidate(beatmapSet); + + onlineLookupQueue.Update(beatmapSet); + + foreach (var beatmap in beatmapSet.Beatmaps) + { + // Because we aren't guaranteed all processing will happen on this thread, it's very hard to use the live realm object. + // This can be fixed by adding a synchronous flow to `BeatmapDifficultyCache`. + var detachedBeatmap = beatmap.Detach(); + + beatmap.StarRating = difficultyCache.GetDifficultyAsync(detachedBeatmap).GetResultSafely()?.Stars ?? 0; + + var working = workingBeatmapCache.GetWorkingBeatmap(beatmap); + beatmap.Length = calculateLength(working.Beatmap); + beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); + } } private double calculateLength(IBeatmap b) From 66a01d1ed29a261a0b5a490eef1e63b0177d2a5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 19:48:46 +0900 Subject: [PATCH 204/803] Allow song select to refresh the global `WorkingBeatmap` after an external update --- osu.Game/Beatmaps/BeatmapManager.cs | 6 ++++++ osu.Game/Beatmaps/IWorkingBeatmapCache.cs | 4 ++++ osu.Game/Beatmaps/WorkingBeatmapCache.cs | 3 +++ osu.Game/Screens/Select/SongSelect.cs | 18 ++++++++++++++++++ 4 files changed, 31 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index effa0ab094..ff176484b6 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -453,6 +453,12 @@ namespace osu.Game.Beatmaps void IWorkingBeatmapCache.Invalidate(BeatmapSetInfo beatmapSetInfo) => workingBeatmapCache.Invalidate(beatmapSetInfo); void IWorkingBeatmapCache.Invalidate(BeatmapInfo beatmapInfo) => workingBeatmapCache.Invalidate(beatmapInfo); + public event Action? OnInvalidated + { + add => workingBeatmapCache.OnInvalidated += value; + remove => workingBeatmapCache.OnInvalidated -= value; + } + public override bool IsAvailableLocally(BeatmapSetInfo model) => Realm.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); #endregion diff --git a/osu.Game/Beatmaps/IWorkingBeatmapCache.cs b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs index ad9cac6957..36c53fce99 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs @@ -3,6 +3,8 @@ #nullable disable +using System; + namespace osu.Game.Beatmaps { public interface IWorkingBeatmapCache @@ -25,5 +27,7 @@ namespace osu.Game.Beatmaps /// /// The beatmap info to invalidate any cached entries for. void Invalidate(BeatmapInfo beatmapInfo); + + event Action OnInvalidated; } } diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 4495fb8318..9d31c58709 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -76,10 +76,13 @@ namespace osu.Game.Beatmaps { Logger.Log($"Invalidating working beatmap cache for {info}"); workingCache.Remove(working); + OnInvalidated?.Invoke(working); } } } + public event Action OnInvalidated; + public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo) { if (beatmapInfo?.BeatmapSet == null) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 41fb55a856..d3635ea553 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -296,8 +296,24 @@ namespace osu.Game.Screens.Select base.LoadComplete(); modSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(ModSelect); + + beatmaps.OnInvalidated += workingBeatmapInvalidated; } + private void workingBeatmapInvalidated(WorkingBeatmap working) => Scheduler.AddOnce(w => + { + // The global beatmap may have already been updated (ie. by the editor). + // Only perform the actual switch if we still need to. + if (w == Beatmap.Value) + { + // Not sure if this refresh is required. + var beatmapInfo = beatmaps.QueryBeatmap(b => b.ID == w.BeatmapInfo.ID); + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); + } + + updateComponentFromBeatmap(Beatmap.Value); + }, working); + /// /// Creates the buttons to be displayed in the footer. /// @@ -700,6 +716,8 @@ namespace osu.Game.Screens.Select music.TrackChanged -= ensureTrackLooping; modSelectOverlayRegistration?.Dispose(); + + beatmaps.OnInvalidated -= workingBeatmapInvalidated; } /// From 30b3973c9fcef6a8a1a308376815d5559eea3d65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 18:39:44 +0900 Subject: [PATCH 205/803] Difficulty cache invalidation flow --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 5 +++++ osu.Game/Beatmaps/BeatmapUpdater.cs | 2 ++ osu.Game/Database/MemoryCachingComponent.cs | 10 ++++++++++ 3 files changed, 17 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index ef0fa36b16..493de5bef4 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -81,6 +81,11 @@ namespace osu.Game.Beatmaps }, true); } + public void Invalidate(IBeatmapInfo beatmap) + { + base.Invalidate(lookup => lookup.BeatmapInfo.Equals(beatmap)); + } + /// /// Retrieves a bindable containing the star difficulty of a that follows the currently-selected ruleset and mods. /// diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index a24407a734..e6703dd8d0 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -61,6 +61,8 @@ namespace osu.Game.Beatmaps var working = workingBeatmapCache.GetWorkingBeatmap(beatmap); beatmap.Length = calculateLength(working.Beatmap); beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); + + difficultyCache.Invalidate(beatmap); } } diff --git a/osu.Game/Database/MemoryCachingComponent.cs b/osu.Game/Database/MemoryCachingComponent.cs index 6e6d928dcc..215050460b 100644 --- a/osu.Game/Database/MemoryCachingComponent.cs +++ b/osu.Game/Database/MemoryCachingComponent.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; @@ -39,6 +40,15 @@ namespace osu.Game.Database return computed; } + protected void Invalidate(Func invalidationFunction) + { + foreach (var kvp in cache) + { + if (invalidationFunction(kvp.Key)) + cache.TryRemove(kvp.Key, out _); + } + } + protected bool CheckExists([NotNull] TLookup lookup, out TValue value) => cache.TryGetValue(lookup, out value); From 0c3d890f76588465df93aa2e80d57bc7199b4f17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 19:04:20 +0900 Subject: [PATCH 206/803] Fix reprocessing not working on import due to realm threading woes --- osu.Game/Beatmaps/BeatmapImporter.cs | 9 +++++++-- osu.Game/Beatmaps/BeatmapUpdater.cs | 17 +++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 0620e5d9b1..05c5a15505 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -80,8 +80,6 @@ namespace osu.Game.Beatmaps // If this is ever an issue, we can consider marking as pending delete but not resetting the IDs (but care will be required for // beatmaps, which don't have their own `DeletePending` state). - beatmapUpdater?.Process(beatmapSet, realm); - if (beatmapSet.OnlineID > 0) { var existingSetWithSameOnlineID = realm.All().SingleOrDefault(b => b.OnlineID == beatmapSet.OnlineID); @@ -99,6 +97,13 @@ namespace osu.Game.Beatmaps } } + protected override void PostImport(BeatmapSetInfo model, Realm realm) + { + base.PostImport(model, realm); + + beatmapUpdater?.Process(model); + } + private void validateOnlineIds(BeatmapSetInfo beatmapSet, Realm realm) { var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineID > 0).Select(b => b.OnlineID).ToList(); diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index e6703dd8d0..0397cfbea9 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -3,9 +3,9 @@ #nullable enable +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; -using osu.Framework.Extensions; using osu.Game.Database; using osu.Game.Rulesets.Objects; using Realms; @@ -52,17 +52,18 @@ namespace osu.Game.Beatmaps foreach (var beatmap in beatmapSet.Beatmaps) { - // Because we aren't guaranteed all processing will happen on this thread, it's very hard to use the live realm object. - // This can be fixed by adding a synchronous flow to `BeatmapDifficultyCache`. - var detachedBeatmap = beatmap.Detach(); + difficultyCache.Invalidate(beatmap); - beatmap.StarRating = difficultyCache.GetDifficultyAsync(detachedBeatmap).GetResultSafely()?.Stars ?? 0; + var working = workingBeatmapCache.GetWorkingBeatmap(beatmap.Detach()); + var ruleset = working.BeatmapInfo.Ruleset.CreateInstance(); - var working = workingBeatmapCache.GetWorkingBeatmap(beatmap); + Debug.Assert(ruleset != null); + + var calculator = ruleset.CreateDifficultyCalculator(working); + + beatmap.StarRating = calculator.Calculate().StarRating; beatmap.Length = calculateLength(working.Beatmap); beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); - - difficultyCache.Invalidate(beatmap); } } From 7692bac35a27618c8c5b74a80923d862d2340159 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 21:02:14 +0900 Subject: [PATCH 207/803] Simplify refetch (and ensure to invalidate after processing) --- osu.Game/Beatmaps/BeatmapManager.cs | 22 +++++++++++++--------- osu.Game/Beatmaps/BeatmapUpdater.cs | 5 ++++- osu.Game/Screens/Edit/Editor.cs | 4 +--- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index ff176484b6..26f538f65b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -430,26 +430,30 @@ namespace osu.Game.Beatmaps #region Implementation of IWorkingBeatmapCache - public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo? beatmapInfo) + /// + /// Retrieve a instance for the provided + /// + /// The beatmap to lookup. + /// Whether to force a refetch from the database to ensure is up-to-date. + /// A instance correlating to the provided . + public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, bool refetch = false) { // Detached sets don't come with files. // If we seem to be missing files, now is a good time to re-fetch. - if (beatmapInfo?.IsManaged == true || beatmapInfo?.BeatmapSet?.Files.Count == 0) + if (refetch || beatmapInfo.IsManaged || beatmapInfo.BeatmapSet?.Files.Count == 0) { - Realm.Run(r => - { - var refetch = r.Find(beatmapInfo.ID)?.Detach(); + workingBeatmapCache.Invalidate(beatmapInfo); - if (refetch != null) - beatmapInfo = refetch; - }); + Guid id = beatmapInfo.ID; + beatmapInfo = Realm.Run(r => r.Find(id)?.Detach()) ?? beatmapInfo; } - Debug.Assert(beatmapInfo?.IsManaged != true); + Debug.Assert(beatmapInfo.IsManaged != true); return workingBeatmapCache.GetWorkingBeatmap(beatmapInfo); } + WorkingBeatmap IWorkingBeatmapCache.GetWorkingBeatmap(BeatmapInfo beatmapInfo) => GetWorkingBeatmap(beatmapInfo); void IWorkingBeatmapCache.Invalidate(BeatmapSetInfo beatmapSetInfo) => workingBeatmapCache.Invalidate(beatmapSetInfo); void IWorkingBeatmapCache.Invalidate(BeatmapInfo beatmapInfo) => workingBeatmapCache.Invalidate(beatmapInfo); diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 0397cfbea9..caef094701 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps { difficultyCache.Invalidate(beatmap); - var working = workingBeatmapCache.GetWorkingBeatmap(beatmap.Detach()); + var working = workingBeatmapCache.GetWorkingBeatmap(beatmap); var ruleset = working.BeatmapInfo.Ruleset.CreateInstance(); Debug.Assert(ruleset != null); @@ -65,6 +65,9 @@ namespace osu.Game.Beatmaps beatmap.Length = calculateLength(working.Beatmap); beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); } + + // And invalidate again afterwards as re-fetching the most up-to-date database metadata will be required. + workingBeatmapCache.Invalidate(beatmapSet); } private double calculateLength(IBeatmap b) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7a9dae33b4..528a237ac4 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -630,9 +630,7 @@ namespace osu.Game.Screens.Edit // To update the game-wide beatmap with any changes, perform a re-fetch on exit/suspend. // This is required as the editor makes its local changes via EditorBeatmap // (which are not propagated outwards to a potentially cached WorkingBeatmap). - ((IWorkingBeatmapCache)beatmapManager).Invalidate(Beatmap.Value.BeatmapInfo); - var refetchedBeatmapInfo = beatmapManager.QueryBeatmap(b => b.ID == Beatmap.Value.BeatmapInfo.ID); - var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(refetchedBeatmapInfo); + var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo, true); if (!(refetchedBeatmap is DummyWorkingBeatmap)) { From 2c5e5fed6f8edd6f0238c5c8af09b440d9421aca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 21:02:29 +0900 Subject: [PATCH 208/803] Add test coverage of star rating and difficulty updating on editor save --- .../Visual/Editing/TestSceneEditorSaving.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index e9bbf1e33d..bcf02cd814 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -130,6 +131,54 @@ namespace osu.Game.Tests.Visual.Editing !ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT)); } + [Test] + public void TestLengthAndStarRatingUpdated() + { + WorkingBeatmap working = null; + double lastStarRating = 0; + double lastLength = 0; + + AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(500, new TimingControlPoint())); + AddStep("Change to placement mode", () => InputManager.Key(Key.Number2)); + AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre)); + AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left)); + AddAssert("One hitobject placed", () => EditorBeatmap.HitObjects.Count == 1); + + SaveEditor(); + AddStep("Get working beatmap", () => working = Game.BeatmapManager.GetWorkingBeatmap(EditorBeatmap.BeatmapInfo, true)); + + AddAssert("Beatmap length is zero", () => working.BeatmapInfo.Length == 0); + checkDifficultyIncreased(); + + AddStep("Move forward", () => InputManager.Key(Key.Right)); + AddStep("Place another hitcircle", () => InputManager.Click(MouseButton.Left)); + AddAssert("Two hitobjects placed", () => EditorBeatmap.HitObjects.Count == 2); + + SaveEditor(); + AddStep("Get working beatmap", () => working = Game.BeatmapManager.GetWorkingBeatmap(EditorBeatmap.BeatmapInfo, true)); + + checkDifficultyIncreased(); + checkLengthIncreased(); + + void checkLengthIncreased() + { + AddStep("Beatmap length increased", () => + { + Assert.That(working.BeatmapInfo.Length, Is.GreaterThan(lastLength)); + lastLength = working.BeatmapInfo.Length; + }); + } + + void checkDifficultyIncreased() + { + AddStep("Beatmap difficulty increased", () => + { + Assert.That(working.BeatmapInfo.StarRating, Is.GreaterThan(lastStarRating)); + lastStarRating = working.BeatmapInfo.StarRating; + }); + } + } + [Test] public void TestExitWithoutSaveFromExistingBeatmap() { From 31a447fda0532fcf15f8c8251ca890c533386492 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 21:25:23 +0900 Subject: [PATCH 209/803] Update parameter discards --- osu.Desktop/DiscordRichPresence.cs | 2 +- osu.Desktop/Program.cs | 8 +++--- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 2 +- .../JuiceStreamPathTest.cs | 2 +- .../TestSceneHyperDash.cs | 2 +- .../Edit/CatchHitObjectComposer.cs | 6 ++--- .../Edit/CatchHitObjectUtils.cs | 4 +-- .../Edit/CatchSelectionHandler.cs | 2 +- osu.Game.Rulesets.Catch/UI/Catcher.cs | 6 ++--- .../Beatmaps/ManiaBeatmapConverter.cs | 2 +- .../Difficulty/ManiaDifficultyCalculator.cs | 6 ++--- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 26 +++++++++---------- .../Mods/ManiaModRandom.cs | 2 +- .../Replays/ManiaAutoGenerator.cs | 4 +-- .../LegacyMainCirclePieceTest.cs | 2 +- .../Mods/TestSceneOsuModSpunOut.cs | 4 +-- .../Edit/OsuHitObjectComposer.cs | 4 +-- .../Mods/OsuModApproachDifferent.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 8 +++--- .../Mods/OsuModObjectScaleTween.cs | 8 +++--- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 8 +++--- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 8 +++--- .../Replays/OsuAutoGenerator.cs | 2 +- .../Scoring/OsuScoreProcessor.cs | 2 +- .../Skinning/Legacy/LegacyNewStyleSpinner.cs | 2 +- .../UI/ObjectOrderedHitPolicy.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- .../OsuHitObjectGenerationUtils_Reposition.cs | 4 +-- .../TestSceneTaikoSuddenDeath.cs | 2 +- .../Mods/TaikoModHidden.cs | 4 +-- osu.Game.Rulesets.Taiko/Objects/Hit.cs | 2 +- .../Objects/TaikoStrongableHitObject.cs | 2 +- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 8 +++--- .../Collections/IO/ImportCollectionsTest.cs | 2 +- osu.Game.Tests/Database/GeneralUsageTests.cs | 2 +- osu.Game.Tests/Database/RealmLiveTests.cs | 2 +- .../Skinning/LegacySkinTextureFallbackTest.cs | 2 +- .../TestSceneDrawableScrollingRuleset.cs | 4 +-- .../Visual/Gameplay/TestSceneFailAnimation.cs | 2 +- .../Visual/Gameplay/TestSceneFailJudgement.cs | 2 +- .../Visual/Gameplay/TestSceneSkinEditor.cs | 2 +- .../Gameplay/TestSceneSkinnableDrawable.cs | 6 ++--- .../Gameplay/TestSceneStoryboardWithOutro.cs | 4 +-- ...MultiplayerGameplayLeaderboardTestScene.cs | 2 +- .../TestSceneDrawableLoungeRoom.cs | 2 +- .../Multiplayer/TestSceneMatchStartControl.cs | 2 +- .../Visual/Online/TestSceneNewsSidebar.cs | 2 +- .../TestScenePlaylistsResultsScreen.cs | 4 +-- .../Visual/Settings/TestSceneSettingsPanel.cs | 2 +- .../SongSelect/TestSceneBeatmapInfoWedge.cs | 8 +++--- .../TestSceneBeatSyncedContainer.cs | 8 +++--- .../TestSceneBeatmapListingSearchControl.cs | 6 ++--- .../TestSceneFirstRunSetupOverlay.cs | 2 +- .../TestSceneModSelectOverlay.cs | 2 +- osu.Game.Tournament/Components/DateTextBox.cs | 2 +- .../Components/DrawableTeamFlag.cs | 2 +- .../Components/DrawableTeamTitle.cs | 2 +- .../Components/DrawableTournamentTeam.cs | 2 +- .../Screens/Drawings/DrawingsScreen.cs | 2 +- .../Screens/Editors/LadderEditorScreen.cs | 2 +- .../Components/DrawableTournamentRound.cs | 4 +-- .../Screens/Ladder/LadderScreen.cs | 2 +- osu.Game.Tournament/TournamentGame.cs | 2 +- osu.Game.Tournament/TournamentGameBase.cs | 2 +- osu.Game.Tournament/TournamentSceneManager.cs | 4 +-- .../ControlPoints/ControlPointInfo.cs | 4 +-- .../Drawables/BundledBeatmapDownloader.cs | 2 +- .../Beatmaps/Formats/JsonBeatmapDecoder.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 4 +-- .../Formats/LegacyStoryboardDecoder.cs | 2 +- .../Beatmaps/Legacy/LegacyControlPointInfo.cs | 4 +-- osu.Game/Collections/BeatmapCollection.cs | 2 +- .../Collections/CollectionFilterDropdown.cs | 4 +-- osu.Game/Collections/CollectionManager.cs | 2 +- osu.Game/Database/RealmAccess.cs | 2 +- osu.Game/Database/RealmObjectExtensions.cs | 14 +++++----- osu.Game/Extensions/TaskExtensions.cs | 2 +- .../Markdown/OsuMarkdownContainer.cs | 2 +- osu.Game/Graphics/UserInterface/BarGraph.cs | 2 +- .../Graphics/UserInterface/LoadingLayer.cs | 4 +-- .../Graphics/UserInterface/StarCounter.cs | 2 +- .../Bindings/DatabasedKeyBindingContainer.cs | 2 +- osu.Game/Input/IdleTracker.cs | 10 +++---- osu.Game/Online/BeatmapDownloadTracker.cs | 2 +- osu.Game/Online/Chat/ChannelManager.cs | 2 +- osu.Game/Online/HubClientConnector.cs | 2 +- .../OnlinePlayBeatmapAvailabilityTracker.cs | 2 +- osu.Game/Online/ScoreDownloadTracker.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- .../BeatmapListingFilterControl.cs | 6 ++--- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 2 +- .../BeatmapSet/Scores/ScoresContainer.cs | 2 +- osu.Game/Overlays/Comments/CommentEditor.cs | 2 +- .../Overlays/Comments/CommentsContainer.cs | 4 +-- .../Dashboard/Friends/FriendDisplay.cs | 2 +- osu.Game/Overlays/Login/LoginForm.cs | 2 +- .../Overlays/MedalSplash/DrawableMedal.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 ++--- osu.Game/Overlays/Music/PlaylistOverlay.cs | 2 +- .../Notifications/ProgressNotification.cs | 2 +- osu.Game/Overlays/OSD/TrackedSettingToast.cs | 2 +- .../Sections/Graphics/LayoutSettings.cs | 6 ++--- .../Sections/Maintenance/BeatmapSettings.cs | 10 +++---- .../Maintenance/CollectionsSettings.cs | 2 +- .../Sections/Maintenance/ScoreSettings.cs | 4 +-- .../Sections/Maintenance/SkinSettings.cs | 4 +-- .../Overlays/Settings/Sections/SkinSection.cs | 4 +-- .../Configuration/RulesetConfigManager.cs | 2 +- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 4 +-- .../Mods/DifficultyAdjustSettingsControl.cs | 4 +-- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 4 +-- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 26 +++++++++---------- osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 +- .../TernaryButtons/DrawableTernaryButton.cs | 2 +- .../Summary/Parts/ControlPointPart.cs | 2 +- .../Summary/Parts/GroupVisualisation.cs | 8 +++--- .../Timelines/Summary/Parts/TimelinePart.cs | 2 +- .../Compose/Components/BeatDivisorControl.cs | 2 +- .../Compose/Components/BlueprintContainer.cs | 2 +- .../Components/EditorSelectionHandler.cs | 2 +- .../Compose/Components/SelectionHandler.cs | 2 +- .../Components/Timeline/SamplePointPiece.cs | 6 ++--- .../Timeline/TimelineControlPointDisplay.cs | 2 +- .../Timeline/TimelineControlPointGroup.cs | 2 +- .../Screens/Edit/Compose/ComposeScreen.cs | 2 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 4 +-- osu.Game/Screens/Edit/EditorBeatmapSkin.cs | 2 +- osu.Game/Screens/Edit/Setup/DesignSection.cs | 2 +- .../Screens/Edit/Timing/ControlPointTable.cs | 4 +-- .../Edit/Timing/LabelledTimeSignature.cs | 2 +- .../Edit/Timing/SliderWithTextBoxInput.cs | 2 +- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 2 +- osu.Game/Screens/Edit/Timing/TimingSection.cs | 2 +- .../Edit/Timing/WaveformComparisonDisplay.cs | 2 +- osu.Game/Screens/Edit/Verify/IssueList.cs | 2 +- osu.Game/Screens/Menu/Disclaimer.cs | 2 +- .../OnlinePlay/Components/BeatmapTitle.cs | 2 +- .../Components/MatchBeatmapDetailArea.cs | 2 +- .../Components/OnlinePlayBackgroundSprite.cs | 2 +- .../Components/OverlinedPlaylistHeader.cs | 2 +- .../OnlinePlay/Components/ParticipantsList.cs | 2 +- .../Components/StarRatingRangeDisplay.cs | 2 +- .../OnlinePlay/DrawableRoomPlaylist.cs | 2 +- osu.Game/Screens/OnlinePlay/Header.cs | 4 +-- .../Lounge/Components/PlaylistCountPill.cs | 2 +- .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 +- .../Lounge/LoungeBackgroundScreen.cs | 2 +- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 2 +- .../Match/MultiplayerReadyButton.cs | 2 +- .../Match/MultiplayerSpectateButton.cs | 2 +- .../Playlist/MultiplayerPlaylistTabControl.cs | 2 +- .../Match/Playlist/MultiplayerQueueList.cs | 2 +- .../Multiplayer/MultiplayerLoungeSubScreen.cs | 2 +- .../Multiplayer/MultiplayerPlayer.cs | 2 +- .../Spectate/MultiSpectatorScreen.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 2 +- .../Playlists/PlaylistsRoomSubScreen.cs | 2 +- osu.Game/Screens/Play/FailAnimation.cs | 2 +- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 4 +-- .../Screens/Play/HUD/HoldForMenuButton.cs | 2 +- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- osu.Game/Screens/Play/KeyCounterDisplay.cs | 8 +++--- osu.Game/Screens/Play/Player.cs | 4 +-- osu.Game/Screens/Play/ReplayPlayer.cs | 2 +- .../Expanded/Statistics/ComboStatistic.cs | 2 +- .../Ranking/Statistics/StatisticsPanel.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- osu.Game/Screens/Select/BeatmapDetailArea.cs | 2 +- osu.Game/Screens/Select/BeatmapDetails.cs | 2 +- .../Screens/Select/Carousel/TopLocalRank.cs | 2 +- .../Screens/Select/Details/AdvancedStats.cs | 2 +- .../Screens/Select/PlayBeatmapDetailArea.cs | 2 +- osu.Game/Skinning/DefaultSkin.cs | 2 +- osu.Game/Skinning/Editor/SkinEditor.cs | 4 +-- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 2 +- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 12 ++++----- osu.Game/Skinning/SkinImporter.cs | 2 +- osu.Game/Skinning/SkinProvidingContainer.cs | 6 ++--- osu.Game/Storyboards/StoryboardSprite.cs | 10 +++---- osu.Game/Tests/Visual/ScreenTestScene.cs | 4 +-- 182 files changed, 309 insertions(+), 309 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index f2531c1cae..d0b6953c30 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -57,7 +57,7 @@ namespace osu.Desktop client.OnReady += onReady; // safety measure for now, until we performance test / improve backoff for failed connections. - client.OnConnectionFailed += (_, __) => client.Deinitialize(); + client.OnConnectionFailed += (_, _) => client.Deinitialize(); client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network); diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 7a5809e915..712f300671 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -129,18 +129,18 @@ namespace osu.Desktop [SupportedOSPlatform("windows")] private static void setupSquirrel() { - SquirrelAwareApp.HandleEvents(onInitialInstall: (version, tools) => + SquirrelAwareApp.HandleEvents(onInitialInstall: (_, tools) => { tools.CreateShortcutForThisExe(); tools.CreateUninstallerRegistryEntry(); - }, onAppUpdate: (version, tools) => + }, onAppUpdate: (_, tools) => { tools.CreateUninstallerRegistryEntry(); - }, onAppUninstall: (version, tools) => + }, onAppUninstall: (_, tools) => { tools.RemoveShortcutForThisExe(); tools.RemoveUninstallerRegistryEntry(); - }, onEveryRun: (version, tools, firstRun) => + }, onEveryRun: (_, _, _) => { // While setting the `ProcessAppUserModelId` fixes duplicate icons/shortcuts on the taskbar, it currently // causes the right-click context menu to function incorrectly. diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index b26fc3fd59..5ffda6504e 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -31,7 +31,7 @@ namespace osu.Game.Benchmarks realm = new RealmAccess(storage, OsuGameBase.CLIENT_DATABASE_FILENAME); - realm.Run(r => + realm.Run(_ => { realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo }))); }); diff --git a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs index 54d26a0f3d..0de992c1df 100644 --- a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Catch.Tests new JuiceStreamPathVertex(20, -5) })); - removeCount = path.RemoveVertices((_, i) => true); + removeCount = path.RemoveVertices((_, _) => true); Assert.That(removeCount, Is.EqualTo(1)); Assert.That(path.Vertices, Is.EqualTo(new[] { diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index 655edf7e08..4886942dc6 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Tests hyperDashCount = 0; // this needs to be done within the frame stable context due to how quickly hyperdash state changes occur. - Player.DrawableRuleset.FrameStableComponents.OnUpdate += d => + Player.DrawableRuleset.FrameStableComponents.OnUpdate += _ => { var catcher = Player.ChildrenOfType().FirstOrDefault(); diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 01156ab021..f31dc3ef9c 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -135,15 +135,15 @@ namespace osu.Game.Rulesets.Catch.Edit { switch (BlueprintContainer.CurrentTool) { - case SelectTool _: + case SelectTool: if (EditorBeatmap.SelectedHitObjects.Count == 0) return null; double minTime = EditorBeatmap.SelectedHitObjects.Min(hitObject => hitObject.StartTime); return getLastSnappableHitObject(minTime); - case FruitCompositionTool _: - case JuiceStreamCompositionTool _: + case FruitCompositionTool: + case JuiceStreamCompositionTool: if (!CursorInPlacementArea) return null; diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs index 4390234b59..889d3909bd 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs @@ -42,10 +42,10 @@ namespace osu.Game.Rulesets.Catch.Edit case Droplet droplet: return droplet is TinyDroplet ? PositionRange.EMPTY : new PositionRange(droplet.OriginalX); - case JuiceStream _: + case JuiceStream: return GetPositionRange(hitObject.NestedHitObjects); - case BananaShower _: + case BananaShower: // A banana shower occupies the whole screen width. return new PositionRange(0, CatchPlayfield.WIDTH); diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index 319cb1bfc9..5aac521d0b 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Catch.Edit { switch (hitObject) { - case BananaShower _: + case BananaShower: return false; case JuiceStream juiceStream: diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 6a3ec336d1..efc841dfac 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -389,13 +389,13 @@ namespace osu.Game.Rulesets.Catch.UI { switch (source) { - case Fruit _: + case Fruit: return caughtFruitPool.Get(); - case Banana _: + case Banana: return caughtBananaPool.Get(); - case Droplet _: + case Droplet: return caughtDropletPool.Get(); default: diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 2b4f497785..90cd7f57b5 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps switch (original) { - case IHasDistance _: + case IHasDistance: { var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap); conversion = generator; diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 8002410f70..979aaa1cf5 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -156,9 +156,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty { switch (m) { - case ManiaModNoFail _: - case ManiaModEasy _: - case ManiaModHalfTime _: + case ManiaModNoFail: + case ManiaModEasy: + case ManiaModHalfTime: scoreMultiplier *= 0.5; break; } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 98a492450e..4723416c30 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -146,56 +146,56 @@ namespace osu.Game.Rulesets.Mania { switch (mod) { - case ManiaModKey1 _: + case ManiaModKey1: value |= LegacyMods.Key1; break; - case ManiaModKey2 _: + case ManiaModKey2: value |= LegacyMods.Key2; break; - case ManiaModKey3 _: + case ManiaModKey3: value |= LegacyMods.Key3; break; - case ManiaModKey4 _: + case ManiaModKey4: value |= LegacyMods.Key4; break; - case ManiaModKey5 _: + case ManiaModKey5: value |= LegacyMods.Key5; break; - case ManiaModKey6 _: + case ManiaModKey6: value |= LegacyMods.Key6; break; - case ManiaModKey7 _: + case ManiaModKey7: value |= LegacyMods.Key7; break; - case ManiaModKey8 _: + case ManiaModKey8: value |= LegacyMods.Key8; break; - case ManiaModKey9 _: + case ManiaModKey9: value |= LegacyMods.Key9; break; - case ManiaModDualStages _: + case ManiaModDualStages: value |= LegacyMods.KeyCoop; break; - case ManiaModFadeIn _: + case ManiaModFadeIn: value |= LegacyMods.FadeIn; value &= ~LegacyMods.Hidden; // this is toggled on in the base call due to inheritance, but we don't want that. break; - case ManiaModMirror _: + case ManiaModMirror: value |= LegacyMods.Mirror; break; - case ManiaModRandom _: + case ManiaModRandom: value |= LegacyMods.Random; break; } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index c7c7a6003e..22347d21b8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Mods var rng = new Random((int)Seed.Value); int availableColumns = ((ManiaBeatmap)beatmap).TotalColumns; - var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => rng.Next()).ToList(); + var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(_ => rng.Next()).ToList(); beatmap.HitObjects.OfType().ForEach(h => h.Column = shuffledColumns[h.Column]); } diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 3814ad84f1..26572de412 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -57,11 +57,11 @@ namespace osu.Game.Rulesets.Mania.Replays { switch (point) { - case HitPoint _: + case HitPoint: actions.Add(columnActions[point.Column]); break; - case ReleasePoint _: + case ReleasePoint: actions.Remove(columnActions[point.Column]); break; } diff --git a/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs b/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs index 2c4310202e..7c4ab2f5f4 100644 --- a/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Tests skin.Setup(s => s.GetTexture(It.IsAny())).CallBase(); skin.Setup(s => s.GetTexture(It.IsIn(textureFilenames), It.IsAny(), It.IsAny())) - .Returns((string componentName, WrapMode _, WrapMode __) => new Texture(1, 1) { AssetName = componentName }); + .Returns((string componentName, WrapMode _, WrapMode _) => new Texture(1, 1) { AssetName = componentName }); Child = new DependencyProvidingContainer { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index fe8bba3ed8..4f6d6376bf 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods lastResult = null; spinner = nextSpinner; - spinner.OnNewResult += (o, result) => lastResult = result; + spinner.OnNewResult += (_, result) => lastResult = result; } return lastResult?.Type == HitResult.Great; @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods return false; spinner = nextSpinner; - spinner.OnNewResult += (o, result) => results.Add(result); + spinner.OnNewResult += (_, result) => results.Add(result); results.Clear(); } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 066a114f66..60896b17bf 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Edit }); selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy(); - selectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid(); + selectedHitObjects.CollectionChanged += (_, _) => updateDistanceSnapGrid(); placementObject = EditorBeatmap.PlacementObject.GetBoundCopy(); placementObject.ValueChanged += _ => updateDistanceSnapGrid(); @@ -204,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Edit switch (BlueprintContainer.CurrentTool) { - case SelectTool _: + case SelectTool: if (!EditorBeatmap.SelectedHitObjects.Any()) return; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs index 3d4a26b3ff..e25845f5ab 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableHitObject(DrawableHitObject drawable) { - drawable.ApplyCustomUpdateState += (drawableObject, state) => + drawable.ApplyCustomUpdateState += (drawableObject, _) => { if (!(drawableObject is DrawableHitCircle drawableHitCircle)) return; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index f9422e1ff9..11ceb0f710 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawableObject) { - case DrawableSliderTail _: + case DrawableSliderTail: using (drawableObject.BeginAbsoluteSequence(fadeStartTime)) drawableObject.FadeOut(fadeDuration); @@ -165,14 +165,14 @@ namespace osu.Game.Rulesets.Osu.Mods switch (hitObject) { - case Slider _: + case Slider: return (fadeOutStartTime, longFadeDuration); - case SliderTick _: + case SliderTick: double tickFadeOutDuration = Math.Min(hitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000); return (hitObject.StartTime - tickFadeOutDuration, tickFadeOutDuration); - case Spinner _: + case Spinner: return (fadeOutStartTime + longFadeDuration, fadeOutDuration); default: diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs index d96724929f..44942e9e37 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs @@ -44,13 +44,13 @@ namespace osu.Game.Rulesets.Osu.Mods // apply grow effect switch (drawable) { - case DrawableSliderHead _: - case DrawableSliderTail _: + case DrawableSliderHead: + case DrawableSliderTail: // special cases we should *not* be scaling. break; - case DrawableSlider _: - case DrawableHitCircle _: + case DrawableSlider: + case DrawableHitCircle: { using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) drawable.ScaleTo(StartScale.Value).Then().ScaleTo(EndScale, h.TimePreempt, Easing.OutSine); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 51994a3e1a..5a08df3803 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -34,10 +34,10 @@ namespace osu.Game.Rulesets.Osu.Mods { switch (drawable) { - case DrawableSliderHead _: - case DrawableSliderTail _: - case DrawableSliderTick _: - case DrawableSliderRepeat _: + case DrawableSliderHead: + case DrawableSliderTail: + case DrawableSliderTick: + case DrawableSliderRepeat: return; default: diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index dcb47347ef..a5468ff613 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Osu.Objects public Slider() { - SamplesBindable.CollectionChanged += (_, __) => UpdateNestedSamples(); + SamplesBindable.CollectionChanged += (_, _) => UpdateNestedSamples(); Path.Version.ValueChanged += _ => updateNestedPositions(); } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 8b9dc71a8b..120ce32612 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -120,19 +120,19 @@ namespace osu.Game.Rulesets.Osu { switch (mod) { - case OsuModAutopilot _: + case OsuModAutopilot: value |= LegacyMods.Autopilot; break; - case OsuModSpunOut _: + case OsuModSpunOut: value |= LegacyMods.SpunOut; break; - case OsuModTarget _: + case OsuModTarget: value |= LegacyMods.Target; break; - case OsuModTouchDevice _: + case OsuModTouchDevice: value |= LegacyMods.TouchDevice; break; } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 27029afece..b0155c02cf 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Replays hitWindows = slider.TailCircle.HitWindows; break; - case Spinner _: + case Spinner: hitWindows = defaultHitWindows; break; } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 7a71ef6c65..34a1bd40e9 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Scoring { switch (hitObject) { - case HitCircle _: + case HitCircle: return new OsuHitCircleJudgementResult(hitObject, judgement); default: diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs index e4ca0d2ea8..d5cc469ca9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy break; - case DrawableSpinnerBonusTick _: + case DrawableSpinnerBonusTick: if (state == ArmedState.Hit) glow.FlashColour(Color4.White, 200); diff --git a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs index fba225d464..6330208d37 100644 --- a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.UI switch (obj) { - case DrawableSpinner _: + case DrawableSpinner: continue; case DrawableSlider slider: diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index ad27428010..3179b37d5a 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.UI // note: `Slider`'s `ProxiedLayer` is added when its nested `DrawableHitCircle` is loaded. switch (drawable) { - case DrawableSpinner _: + case DrawableSpinner: spinnerProxies.Add(drawable.CreateProxy()); break; diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index c413226e63..3a156d4d25 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -88,11 +88,11 @@ namespace osu.Game.Rulesets.Osu.Utils switch (hitObject) { - case HitCircle _: + case HitCircle: shift = clampHitCircleToPlayfield(current); break; - case Slider _: + case Slider: shift = clampSliderToPlayfield(current); break; } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs index 51772df4a7..cdfab4a215 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Taiko.Tests AddStep("Setup judgements", () => { judged = false; - Player.ScoreProcessor.NewJudgement += b => judged = true; + Player.ScoreProcessor.NewJudgement += _ => judged = true; }); AddUntilStep("swell judged", () => judged); AddAssert("failed", () => Player.GameplayState.HasFailed); diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index 69eace4302..e065bb43fd 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -48,8 +48,8 @@ namespace osu.Game.Rulesets.Taiko.Mods { switch (hitObject) { - case DrawableDrumRollTick _: - case DrawableHit _: + case DrawableDrumRollTick: + case DrawableHit: double preempt = drawableRuleset.TimeRange.Value / drawableRuleset.ControlPointAt(hitObject.HitObject.StartTime).Multiplier; double start = hitObject.HitObject.StartTime - preempt * fade_out_start_time; double duration = preempt * fade_out_duration; diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs index 8bc0dc6df0..20f3304c30 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Objects DisplayColour.Value = Type == HitType.Centre ? COLOUR_CENTRE : COLOUR_RIM; }); - SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples()); + SamplesBindable.BindCollectionChanged((_, _) => updateTypeFromSamples()); } private void updateTypeFromSamples() diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs index b7bdd98d2a..d4d59d5d44 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Taiko.Objects protected TaikoStrongableHitObject() { IsStrongBindable.BindValueChanged(_ => updateSamplesFromType()); - SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples()); + SamplesBindable.BindCollectionChanged((_, _) => updateTypeFromSamples()); } private void updateTypeFromSamples() diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 4e0c8029fb..4ef7c24464 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -245,7 +245,7 @@ namespace osu.Game.Rulesets.Taiko.UI barLinePlayfield.Add(barLine); break; - case DrawableTaikoHitObject _: + case DrawableTaikoHitObject: base.Add(h); break; @@ -261,7 +261,7 @@ namespace osu.Game.Rulesets.Taiko.UI case DrawableBarLine barLine: return barLinePlayfield.Remove(barLine); - case DrawableTaikoHitObject _: + case DrawableTaikoHitObject: return base.Remove(h); default: @@ -280,12 +280,12 @@ namespace osu.Game.Rulesets.Taiko.UI switch (result.Judgement) { - case TaikoStrongJudgement _: + case TaikoStrongJudgement: if (result.IsHit) hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == ((DrawableStrongNestedHit)judgedObject).ParentHitObject)?.VisualiseSecondHit(result); break; - case TaikoDrumRollTickJudgement _: + case TaikoDrumRollTickJudgement: if (!result.IsHit) break; diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index 7f466925a4..c31aafa67f 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Collections.IO public async Task TestImportMalformedDatabase() { bool exceptionThrown = false; - UnhandledExceptionEventHandler setException = (_, __) => exceptionThrown = true; + UnhandledExceptionEventHandler setException = (_, _) => exceptionThrown = true; using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index c28a0c63af..65f805bafb 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Database realm.RegisterCustomSubscription(r => { - var subscription = r.All().QueryAsyncWithNotifications((sender, changes, error) => + var subscription = r.All().QueryAsyncWithNotifications((_, _, _) => { realm.Run(_ => { diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 416216062e..00a667521d 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -189,7 +189,7 @@ namespace osu.Game.Tests.Database }); // Can't be used, even from within a valid context. - realm.Run(threadContext => + realm.Run(_ => { Assert.Throws(() => { diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs index efab884d37..22aa78838a 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs @@ -157,7 +157,7 @@ namespace osu.Game.Tests.NonVisual.Skinning { // use an incrementing width to allow assertion matching on correct textures as they turn from uploads into actual textures. int width = 1; - Textures = fileNames.ToDictionary(fileName => fileName, fileName => new TextureUpload(new Image(width, width++))); + Textures = fileNames.ToDictionary(fileName => fileName, _ => new TextureUpload(new Image(width, width++))); } public TextureUpload Get(string name) => Textures.GetValueOrDefault(name); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index f9a3695d65..a79ba0ae5d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -322,8 +322,8 @@ namespace osu.Game.Tests.Visual.Gameplay { switch (h) { - case TestPooledHitObject _: - case TestPooledParentHitObject _: + case TestPooledHitObject: + case TestPooledParentHitObject: return null; case TestParentHitObject p: diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs index 0b737f5110..ce01bf2fb5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void LoadComplete() { base.LoadComplete(); - HealthProcessor.FailConditions += (_, __) => true; + HealthProcessor.FailConditions += (_, _) => true; } private double lastFrequency = double.MaxValue; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs index 90a4b536bb..5e87eff717 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void LoadComplete() { base.LoadComplete(); - HealthProcessor.FailConditions += (_, __) => true; + HealthProcessor.FailConditions += (_, _) => true; } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 3bebf2b68b..f319290441 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestToggleEditor() { - AddToggleStep("toggle editor visibility", visible => skinEditor.ToggleVisibility()); + AddToggleStep("toggle editor visibility", _ => skinEditor.ToggleVisibility()); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 757a261de6..16593effd6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Gameplay Child = new SkinProvidingContainer(secondarySource) { RelativeSizeAxes = Axes.Both, - Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation")) + Child = consumer = new SkinConsumer("test", _ => new NamedBox("Default Implementation")) } }; }); @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; }); - AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation")))); + AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", _ => new NamedBox("Default Implementation")))); AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox); AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1); } @@ -155,7 +155,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; }); - AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation")))); + AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", _ => new NamedBox("Default Implementation")))); AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox); AddStep("disable", () => target.Disable()); AddAssert("consumer using base source", () => consumer.Drawable is BaseSourceBox); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 68d024e63f..002e35f742 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Gameplay base.SetUpSteps(); AddStep("enable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, true)); AddStep("set dim level to 0", () => LocalConfig.SetValue(OsuSetting.DimLevel, 0)); - AddStep("reset fail conditions", () => currentFailConditions = (_, __) => false); + AddStep("reset fail conditions", () => currentFailConditions = (_, _) => false); AddStep("set storyboard duration to 2s", () => currentStoryboardDuration = 2000); AddStep("set ShowResults = true", () => showResults = true); } @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Gameplay { CreateTest(() => { - AddStep("fail on first judgement", () => currentFailConditions = (_, __) => true); + AddStep("fail on first judgement", () => currentFailConditions = (_, _) => true); // Fail occurs at 164ms with the provided beatmap. // Fail animation runs for 2.5s realtime but the gameplay time change is *variable* due to the frequency transform being applied, so we need a bit of lenience. diff --git a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs index 43ca47778a..631f2e707a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer // To emulate `MultiplayerClient.CurrentMatchPlayingUserIds` we need a bindable list of *only IDs*. // This tracks the list of users 1:1. - MultiplayerUsers.BindCollectionChanged((c, e) => + MultiplayerUsers.BindCollectionChanged((_, e) => { switch (e.Action) { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs index 5b4e0a88aa..be1f21a7b2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var mockLounge = new Mock(); mockLounge .Setup(l => l.Join(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>())) - .Callback, Action>((a, b, c, d) => + .Callback, Action>((_, _, _, d) => { Task.Run(() => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index 0cdc144b6a..a800b21bc9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Multiplayer setRoomCountdown(countdownStart.Duration); break; - case StopCountdownRequest _: + case StopCountdownRequest: multiplayerRoom.Countdown = null; raiseRoomUpdated(); break; diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs index f28eaf5ad0..266e98db15 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs @@ -219,7 +219,7 @@ namespace osu.Game.Tests.Visual.Online { base.LoadComplete(); - Metadata.BindValueChanged(metadata => + Metadata.BindValueChanged(_ => { foreach (var b in this.ChildrenOfType()) b.Action = () => YearChanged?.Invoke(b.Year); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index a46e675370..8a04cd96fe 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -187,8 +187,8 @@ namespace osu.Game.Tests.Visual.Playlists // pre-check for requests we should be handling (as they are scheduled below). switch (request) { - case ShowPlaylistUserScoreRequest _: - case IndexPlaylistScoresRequest _: + case ShowPlaylistUserScoreRequest: + case IndexPlaylistScoresRequest: break; default: diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs index abf65602f9..9791bb6248 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Settings public void ToggleVisibility() { AddWaitStep("wait some", 5); - AddToggleStep("toggle visibility", visible => settings.ToggleVisibility()); + AddToggleStep("toggle visibility", _ => settings.ToggleVisibility()); } [Test] diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 6490fd822e..f9d18f4236 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -91,19 +91,19 @@ namespace osu.Game.Tests.Visual.SongSelect switch (instance) { - case OsuRuleset _: + case OsuRuleset: testInfoLabels(5); break; - case TaikoRuleset _: + case TaikoRuleset: testInfoLabels(5); break; - case CatchRuleset _: + case CatchRuleset: testInfoLabels(5); break; - case ManiaRuleset _: + case ManiaRuleset: testInfoLabels(4); break; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 6cf66963a5..368babc9b5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Set time before zero", () => { - beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) => + beatContainer.NewBeat = (i, timingControlPoint, _, _) => { lastActuationTime = gameplayClockContainer.CurrentTime; lastTimingPoint = timingControlPoint; @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Set time before zero", () => { - beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) => + beatContainer.NewBeat = (i, timingControlPoint, _, _) => { lastBeatIndex = i; lastBpm = timingControlPoint.BPM; @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("bind event", () => { - beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) => lastBpm = timingControlPoint.BPM; + beatContainer.NewBeat = (_, timingControlPoint, _, _) => lastBpm = timingControlPoint.BPM; }); AddUntilStep("wait for trigger", () => lastBpm != null); @@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.UserInterface actualEffectPoint = null; beatContainer.AllowMistimedEventFiring = false; - beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) => + beatContainer.NewBeat = (_, _, effectControlPoint, _) => { if (Precision.AlmostEquals(gameplayClockContainer.CurrentTime + earlyActivationMilliseconds, expectedEffectPoint.Time, BeatSyncedContainer.MISTIMED_ALLOWANCE)) actualEffectPoint = effectControlPoint; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index 1107fad834..44f2da2b95 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -77,13 +77,13 @@ namespace osu.Game.Tests.Visual.UserInterface }; control.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true); - control.General.BindCollectionChanged((u, v) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().Underscore())) : "")}", true); + control.General.BindCollectionChanged((_, _) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().Underscore())) : "")}", true); control.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true); control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); - control.Extra.BindCollectionChanged((u, v) => extra.Text = $"Extra: {(control.Extra.Any() ? string.Join('.', control.Extra.Select(i => i.ToString().ToLowerInvariant())) : "")}", true); - control.Ranks.BindCollectionChanged((u, v) => ranks.Text = $"Ranks: {(control.Ranks.Any() ? string.Join('.', control.Ranks.Select(i => i.ToString())) : "")}", true); + control.Extra.BindCollectionChanged((_, _) => extra.Text = $"Extra: {(control.Extra.Any() ? string.Join('.', control.Extra.Select(i => i.ToString().ToLowerInvariant())) : "")}", true); + control.Ranks.BindCollectionChanged((_, _) => ranks.Text = $"Ranks: {(control.Ranks.Any() ? string.Join('.', control.Ranks.Select(i => i.ToString())) : "")}", true); control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true); control.ExplicitContent.BindValueChanged(e => explicitMap.Text = $"Explicit Maps: {e.NewValue}", true); }); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs index a3ae55670a..b845b85e1f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.UserInterface notificationOverlay.Reset(); performer.Setup(g => g.PerformFromScreen(It.IsAny>(), It.IsAny>())) - .Callback((Action action, IEnumerable types) => action(null)); + .Callback((Action action, IEnumerable _) => action(null)); notificationOverlay.Setup(n => n.Post(It.IsAny())) .Callback((Notification n) => lastNotification = n); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 31061dc109..006707d064 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -387,7 +387,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("double time not visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).All(panel => panel.Filtered.Value)); AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModNightcore).Any(panel => !panel.Filtered.Value)); - AddStep("make double time valid again", () => modSelectOverlay.IsValidMod = m => true); + AddStep("make double time valid again", () => modSelectOverlay.IsValidMod = _ => true); AddUntilStep("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => !panel.Filtered.Value)); AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(b => b.Mod is OsuModNightcore).Any(panel => !panel.Filtered.Value)); } diff --git a/osu.Game.Tournament/Components/DateTextBox.cs b/osu.Game.Tournament/Components/DateTextBox.cs index 8eccb9e0e0..76d12a6b03 100644 --- a/osu.Game.Tournament/Components/DateTextBox.cs +++ b/osu.Game.Tournament/Components/DateTextBox.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tournament.Components { base.Current = new Bindable(string.Empty); - ((OsuTextBox)Control).OnCommit += (sender, newText) => + ((OsuTextBox)Control).OnCommit += (sender, _) => { try { diff --git a/osu.Game.Tournament/Components/DrawableTeamFlag.cs b/osu.Game.Tournament/Components/DrawableTeamFlag.cs index 6e9c1120e4..348fd8cd76 100644 --- a/osu.Game.Tournament/Components/DrawableTeamFlag.cs +++ b/osu.Game.Tournament/Components/DrawableTeamFlag.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Components FillMode = FillMode.Fill }; - (flag = team.FlagName.GetBoundCopy()).BindValueChanged(acronym => flagSprite.Texture = textures.Get($@"Flags/{team.FlagName}"), true); + (flag = team.FlagName.GetBoundCopy()).BindValueChanged(_ => flagSprite.Texture = textures.Get($@"Flags/{team.FlagName}"), true); } } } diff --git a/osu.Game.Tournament/Components/DrawableTeamTitle.cs b/osu.Game.Tournament/Components/DrawableTeamTitle.cs index b7e936026c..e64e08a921 100644 --- a/osu.Game.Tournament/Components/DrawableTeamTitle.cs +++ b/osu.Game.Tournament/Components/DrawableTeamTitle.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tournament.Components { if (team == null) return; - (acronym = team.Acronym.GetBoundCopy()).BindValueChanged(acronym => Text.Text = team?.FullName.Value ?? string.Empty, true); + (acronym = team.Acronym.GetBoundCopy()).BindValueChanged(_ => Text.Text = team?.FullName.Value ?? string.Empty, true); } } } diff --git a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs index 84cae92a39..eb1dde21e7 100644 --- a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs +++ b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tournament.Components { if (Team == null) return; - (acronym = Team.Acronym.GetBoundCopy()).BindValueChanged(acronym => AcronymText.Text = Team?.Acronym.Value?.ToUpperInvariant() ?? string.Empty, true); + (acronym = Team.Acronym.GetBoundCopy()).BindValueChanged(_ => AcronymText.Text = Team?.Acronym.Value?.ToUpperInvariant() ?? string.Empty, true); } } } diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 58ba3c1e8b..32da4d1b36 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -219,7 +219,7 @@ namespace osu.Game.Tournament.Screens.Drawings } } - writeOp = writeOp?.ContinueWith(t => { writeAction(); }) ?? Task.Run(writeAction); + writeOp = writeOp?.ContinueWith(_ => { writeAction(); }) ?? Task.Run(writeAction); } private void reloadTeams() diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs index 6052bcdeb7..4261828df2 100644 --- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tournament.Screens.Editors AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches")); - LadderInfo.Matches.CollectionChanged += (_, __) => updateMessage(); + LadderInfo.Matches.CollectionChanged += (_, _) => updateMessage(); updateMessage(); } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs index 544725996a..466b9ed482 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs @@ -49,10 +49,10 @@ namespace osu.Game.Tournament.Screens.Ladder.Components }; name = round.Name.GetBoundCopy(); - name.BindValueChanged(n => textName.Text = ((losers ? "Losers " : "") + round.Name).ToUpperInvariant(), true); + name.BindValueChanged(_ => textName.Text = ((losers ? "Losers " : "") + round.Name).ToUpperInvariant(), true); description = round.Description.GetBoundCopy(); - description.BindValueChanged(n => textDescription.Text = round.Description.Value?.ToUpperInvariant(), true); + description.BindValueChanged(_ => textDescription.Text = round.Description.Value?.ToUpperInvariant(), true); } } } diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index f274503894..23bfa84afc 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tournament.Screens.Ladder foreach (var match in LadderInfo.Matches) addMatch(match); - LadderInfo.Rounds.CollectionChanged += (_, __) => layout.Invalidate(); + LadderInfo.Rounds.CollectionChanged += (_, _) => layout.Invalidate(); LadderInfo.Matches.CollectionChanged += (_, args) => { switch (args.Action) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index c2c6c271cb..537fbfc038 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -137,7 +137,7 @@ namespace osu.Game.Tournament heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0; }), true); - windowMode.BindValueChanged(mode => ScheduleAfterChildren(() => + windowMode.BindValueChanged(_ => ScheduleAfterChildren(() => { windowMode.Value = WindowMode.Windowed; }), true); diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 95d2adc4fa..ccbe77fa95 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -267,7 +267,7 @@ namespace osu.Game.Tournament } else { - req.Success += res => { populate(); }; + req.Success += _ => { populate(); }; req.Failure += _ => { user.OnlineID = 1; diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index afbfc2d368..296b259d72 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -204,12 +204,12 @@ namespace osu.Game.Tournament switch (currentScreen) { - case MapPoolScreen _: + case MapPoolScreen: chatContainer.FadeIn(TournamentScreen.FADE_DELAY); chatContainer.ResizeWidthTo(1, 500, Easing.OutQuint); break; - case GameplayScreen _: + case GameplayScreen: chatContainer.FadeIn(TournamentScreen.FADE_DELAY); chatContainer.ResizeWidthTo(0.5f, 500, Easing.OutQuint); break; diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index eda7ef0bcc..4be6b5eede 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -254,12 +254,12 @@ namespace osu.Game.Beatmaps.ControlPoints switch (newPoint) { - case TimingControlPoint _: + case TimingControlPoint: // Timing points are a special case and need to be added regardless of fallback availability. existing = BinarySearch(TimingPoints, time); break; - case EffectControlPoint _: + case EffectControlPoint: existing = EffectPointAt(time); break; } diff --git a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs index 9b97df906b..80af4108c7 100644 --- a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs +++ b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs @@ -102,7 +102,7 @@ namespace osu.Game.Beatmaps.Drawables // Matches osu-stable, in order to provide new users with roughly the same randomised selection of bundled beatmaps. var random = new LegacyRandom(DateTime.UtcNow.Year * 1000 + (DateTime.UtcNow.DayOfYear / 7)); - downloadableFilenames.AddRange(sourceFilenames.OrderBy(x => random.NextDouble()).Take(limit ?? int.MaxValue)); + downloadableFilenames.AddRange(sourceFilenames.OrderBy(_ => random.NextDouble()).Take(limit ?? int.MaxValue)); } catch { } } diff --git a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs index 2f11c18993..4f292a9a1f 100644 --- a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs @@ -12,7 +12,7 @@ namespace osu.Game.Beatmaps.Formats { public static void Register() { - AddDecoder("{", m => new JsonBeatmapDecoder()); + AddDecoder("{", _ => new JsonBeatmapDecoder()); } protected override void ParseStreamInto(LineBufferedReader stream, Beatmap output) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 984e88d3a9..03c63ff4f2 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -373,11 +373,11 @@ namespace osu.Game.Beatmaps.Formats switch (hitObject) { - case IHasPath _: + case IHasPath: type |= LegacyHitObjectType.Slider; break; - case IHasDuration _: + case IHasDuration: if (onlineRulesetID == 3) type |= LegacyHitObjectType.Hold; else diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 3021688aaf..b8f60f0bc6 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps.Formats { // note that this isn't completely correct AddDecoder(@"osu file format v", m => new LegacyStoryboardDecoder(Parsing.ParseInt(m.Split('v').Last()))); - AddDecoder(@"[Events]", m => new LegacyStoryboardDecoder()); + AddDecoder(@"[Events]", _ => new LegacyStoryboardDecoder()); SetFallbackDecoder(() => new LegacyStoryboardDecoder()); } diff --git a/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs b/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs index 19432f0c58..5c3c72c9e4 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs @@ -56,12 +56,12 @@ namespace osu.Game.Beatmaps.Legacy { switch (newPoint) { - case SampleControlPoint _: + case SampleControlPoint: // intentionally don't use SamplePointAt (we always need to consider the first sample point). var existing = BinarySearch(SamplePoints, time); return newPoint.IsRedundant(existing); - case DifficultyControlPoint _: + case DifficultyControlPoint: return newPoint.IsRedundant(DifficultyPointAt(time)); default: diff --git a/osu.Game/Collections/BeatmapCollection.cs b/osu.Game/Collections/BeatmapCollection.cs index 23b2ef60dd..742d757bec 100644 --- a/osu.Game/Collections/BeatmapCollection.cs +++ b/osu.Game/Collections/BeatmapCollection.cs @@ -36,7 +36,7 @@ namespace osu.Game.Collections public BeatmapCollection() { - BeatmapHashes.CollectionChanged += (_, __) => onChange(); + BeatmapHashes.CollectionChanged += (_, _) => onChange(); Name.ValueChanged += _ => onChange(); } diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index 46d90b930c..d099eb6e1b 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -67,7 +67,7 @@ namespace osu.Game.Collections // An extra bindable is enough to subvert this behaviour. base.Current = Current; - collections.BindCollectionChanged((_, __) => collectionsChanged(), true); + collections.BindCollectionChanged((_, _) => collectionsChanged(), true); Current.BindValueChanged(filterChanged, true); } @@ -233,7 +233,7 @@ namespace osu.Game.Collections if (collectionBeatmaps != null) { - collectionBeatmaps.CollectionChanged += (_, __) => collectionChanged(); + collectionBeatmaps.CollectionChanged += (_, _) => collectionChanged(); beatmap.BindValueChanged(_ => collectionChanged(), true); } diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index c6e52a1b75..796b3c426c 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -261,7 +261,7 @@ namespace osu.Game.Collections private void backgroundSave() { int current = Interlocked.Increment(ref lastSave); - Task.Delay(100).ContinueWith(task => + Task.Delay(100).ContinueWith(_ => { if (current != lastSave) return; diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 68f9dcb541..3ea7a14826 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -454,7 +454,7 @@ namespace osu.Game.Database public IDisposable SubscribeToPropertyChanged(Func modelAccessor, Expression> propertyLookup, Action onChanged) where TModel : RealmObjectBase { - return RegisterCustomSubscription(r => + return RegisterCustomSubscription(_ => { string propertyName = getMemberName(propertyLookup); diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 25416c7fa2..a771aa04df 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -20,7 +20,7 @@ namespace osu.Game.Database { private static readonly IMapper write_mapper = new MapperConfiguration(c => { - c.ShouldMapField = fi => false; + c.ShouldMapField = _ => false; c.ShouldMapProperty = pi => pi.SetMethod?.IsPublic == true; c.CreateMap() @@ -70,7 +70,7 @@ namespace osu.Game.Database } }); - c.Internal().ForAllMaps((typeMap, expression) => + c.Internal().ForAllMaps((_, expression) => { expression.ForAllMembers(m => { @@ -87,7 +87,7 @@ namespace osu.Game.Database c.CreateMap() .ConstructUsing(_ => new BeatmapSetInfo(null)) .MaxDepth(2) - .AfterMap((s, d) => + .AfterMap((_, d) => { foreach (var beatmap in d.Beatmaps) beatmap.BeatmapSet = d; @@ -97,7 +97,7 @@ namespace osu.Game.Database // Only hasn't been done yet as we detach at the point of BeatmapInfo less often. c.CreateMap() .MaxDepth(2) - .AfterMap((s, d) => + .AfterMap((_, d) => { for (int i = 0; i < d.BeatmapSet?.Beatmaps.Count; i++) { @@ -121,7 +121,7 @@ namespace osu.Game.Database .ConstructUsing(_ => new BeatmapSetInfo(null)) .MaxDepth(2) .ForMember(b => b.Files, cc => cc.Ignore()) - .AfterMap((s, d) => + .AfterMap((_, d) => { foreach (var beatmap in d.Beatmaps) beatmap.BeatmapSet = d; @@ -135,14 +135,14 @@ namespace osu.Game.Database private static void applyCommonConfiguration(IMapperConfigurationExpression c) { - c.ShouldMapField = fi => false; + c.ShouldMapField = _ => false; // This is specifically to avoid mapping explicit interface implementations. // If we want to limit this further, we can avoid mapping properties with no setter that are not IList<>. // Takes a bit of effort to determine whether this is the case though, see https://stackoverflow.com/questions/951536/how-do-i-tell-whether-a-type-implements-ilist c.ShouldMapProperty = pi => pi.GetMethod?.IsPublic == true; - c.Internal().ForAllMaps((typeMap, expression) => + c.Internal().ForAllMaps((_, expression) => { expression.ForAllMembers(m => { diff --git a/osu.Game/Extensions/TaskExtensions.cs b/osu.Game/Extensions/TaskExtensions.cs index 5165ae59b5..b4a0c02e35 100644 --- a/osu.Game/Extensions/TaskExtensions.cs +++ b/osu.Game/Extensions/TaskExtensions.cs @@ -31,7 +31,7 @@ namespace osu.Game.Extensions { var tcs = new TaskCompletionSource(); - task.ContinueWith(t => + task.ContinueWith(_ => { // the previous task has finished execution or been cancelled, so we can run the provided continuation. diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index aa156efee9..a2370a76cb 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -27,7 +27,7 @@ namespace osu.Game.Graphics.Containers.Markdown { switch (markdownObject) { - case YamlFrontMatterBlock _: + case YamlFrontMatterBlock: // Don't parse YAML Frontmatter break; diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 00ed6c1c92..f55875ac58 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -74,7 +74,7 @@ namespace osu.Game.Graphics.UserInterface } //I'm using ToList() here because Where() returns an Enumerable which can change it's elements afterwards - RemoveRange(Children.Where((bar, index) => index >= value.Count()).ToList()); + RemoveRange(Children.Where((_, index) => index >= value.Count()).ToList()); } } } diff --git a/osu.Game/Graphics/UserInterface/LoadingLayer.cs b/osu.Game/Graphics/UserInterface/LoadingLayer.cs index 407823fc2a..b3655eaab4 100644 --- a/osu.Game/Graphics/UserInterface/LoadingLayer.cs +++ b/osu.Game/Graphics/UserInterface/LoadingLayer.cs @@ -55,12 +55,12 @@ namespace osu.Game.Graphics.UserInterface switch (e) { // blocking scroll can cause weird behaviour when this layer is used within a ScrollContainer. - case ScrollEvent _: + case ScrollEvent: return false; // blocking touch events causes the ISourcedFromTouch versions to not be fired, potentially impeding behaviour of drawables *above* the loading layer that may utilise these. // note that this will not work well if touch handling elements are beneath this loading layer (something to consider for the future). - case TouchEvent _: + case TouchEvent: return false; } diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index a8051a94d9..d9274bf2cb 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -68,7 +68,7 @@ namespace osu.Game.Graphics.UserInterface AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(star_spacing), - ChildrenEnumerable = Enumerable.Range(0, StarCount).Select(i => CreateStar()) + ChildrenEnumerable = Enumerable.Range(0, StarCount).Select(_ => CreateStar()) } }; } diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 974abc4036..14a041b459 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -51,7 +51,7 @@ namespace osu.Game.Input.Bindings protected override void LoadComplete() { - realmSubscription = realm.RegisterForNotifications(queryRealmKeyBindings, (sender, changes, error) => + realmSubscription = realm.RegisterForNotifications(queryRealmKeyBindings, (sender, _, _) => { // The first fire of this is a bit redundant as this is being called in base.LoadComplete, // but this is safest in case the subscription is restored after a context recycle. diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs index 279cfb5a4e..45036b3e41 100644 --- a/osu.Game/Input/IdleTracker.cs +++ b/osu.Game/Input/IdleTracker.cs @@ -80,11 +80,11 @@ namespace osu.Game.Input switch (e) { - case KeyDownEvent _: - case KeyUpEvent _: - case MouseDownEvent _: - case MouseUpEvent _: - case MouseMoveEvent _: + case KeyDownEvent: + case KeyUpEvent: + case MouseDownEvent: + case MouseUpEvent: + case MouseMoveEvent: return updateLastInteractionTime(); default: diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index 1e3add6201..19708cc07d 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -40,7 +40,7 @@ namespace osu.Game.Online // Used to interact with manager classes that don't support interface types. Will eventually be replaced. var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; - realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, _, _) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 4c762d002d..48dfaadfa5 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -465,7 +465,7 @@ namespace osu.Game.Online.Chat default: var req = new JoinChannelRequest(channel); req.Success += () => joinChannel(channel, fetchInitialMessages); - req.Failure += ex => LeaveChannel(channel); + req.Failure += _ => LeaveChannel(channel); api.Queue(req); return channel; } diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index f9000c01ef..61e9eaa8c0 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -64,7 +64,7 @@ namespace osu.Game.Online this.preferMessagePack = preferMessagePack; apiState.BindTo(api.State); - apiState.BindValueChanged(state => connectIfPossible(), true); + apiState.BindValueChanged(_ => connectIfPossible(), true); } public void Reconnect() diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 032215f5d3..2fd8445980 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -101,7 +101,7 @@ namespace osu.Game.Online.Rooms // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = realm.RegisterForNotifications(r => filteredBeatmaps(), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(_ => filteredBeatmaps(), (_, changes, _) => { if (changes == null) return; diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index cab6bd0722..680bb16264 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -48,7 +48,7 @@ namespace osu.Game.Online realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || (!string.IsNullOrEmpty(s.Hash) && s.Hash == TrackedItem.Hash)) - && !s.DeletePending), (items, changes, ___) => + && !s.DeletePending), (items, _, _) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 1d9e2042ec..b0ba62a6ac 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -481,7 +481,7 @@ namespace osu.Game { switch (handler) { - case MidiHandler _: + case MidiHandler: return new InputSection.HandlerSection(handler); // return null for handlers that shouldn't have settings. diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 4457705676..767b8646e3 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -161,13 +161,13 @@ namespace osu.Game.Overlays.BeatmapListing queueUpdateSearch(true); }); - searchControl.General.CollectionChanged += (_, __) => queueUpdateSearch(); + searchControl.General.CollectionChanged += (_, _) => queueUpdateSearch(); searchControl.Ruleset.BindValueChanged(_ => queueUpdateSearch()); searchControl.Category.BindValueChanged(_ => queueUpdateSearch()); searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); - searchControl.Extra.CollectionChanged += (_, __) => queueUpdateSearch(); - searchControl.Ranks.CollectionChanged += (_, __) => queueUpdateSearch(); + searchControl.Extra.CollectionChanged += (_, _) => queueUpdateSearch(); + searchControl.Ranks.CollectionChanged += (_, _) => queueUpdateSearch(); searchControl.Played.BindValueChanged(_ => queueUpdateSearch()); searchControl.ExplicitContent.BindValueChanged(_ => queueUpdateSearch()); diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 1a57a89957..b27d9b3f1e 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -156,7 +156,7 @@ namespace osu.Game.Overlays.BeatmapSet { base.LoadComplete(); - ruleset.ValueChanged += r => updateDisplay(); + ruleset.ValueChanged += _ => updateDisplay(); // done here so everything can bind in intialization and get the first trigger Beatmap.TriggerChange(); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 2ea94bb412..c2e54d0d7b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -217,7 +217,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores scope.BindValueChanged(_ => getScores()); ruleset.BindValueChanged(_ => getScores()); - modSelector.SelectedMods.CollectionChanged += (_, __) => getScores(); + modSelector.SelectedMods.CollectionChanged += (_, _) => getScores(); Beatmap.BindValueChanged(onBeatmapChanged); user.BindValueChanged(onUserChanged, true); diff --git a/osu.Game/Overlays/Comments/CommentEditor.cs b/osu.Game/Overlays/Comments/CommentEditor.cs index 2e8080ba43..e2a7e78356 100644 --- a/osu.Game/Overlays/Comments/CommentEditor.cs +++ b/osu.Game/Overlays/Comments/CommentEditor.cs @@ -117,7 +117,7 @@ namespace osu.Game.Overlays.Comments } }); - textBox.OnCommit += (u, v) => + textBox.OnCommit += (_, _) => { if (commitButton.IsBlocked.Value) return; diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 4ab6f4f88d..4a836e0e62 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -251,8 +251,8 @@ namespace osu.Game.Overlays.Comments if (bundle.HasMore) { int loadedTopLevelComments = 0; - pinnedContent.Children.OfType().ForEach(p => loadedTopLevelComments++); - content.Children.OfType().ForEach(p => loadedTopLevelComments++); + pinnedContent.Children.OfType().ForEach(_ => loadedTopLevelComments++); + content.Children.OfType().ForEach(_ => loadedTopLevelComments++); moreButton.Current.Value = bundle.TopLevelCount - loadedTopLevelComments; moreButton.IsLoading = false; diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs index bfeaf50b53..bfd356193d 100644 --- a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs +++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs @@ -145,7 +145,7 @@ namespace osu.Game.Overlays.Dashboard.Friends controlBackground.Colour = colourProvider.Background5; apiFriends.BindTo(api.Friends); - apiFriends.BindCollectionChanged((_, __) => Schedule(() => Users = apiFriends.ToList()), true); + apiFriends.BindCollectionChanged((_, _) => Schedule(() => Users = apiFriends.ToList()), true); } protected override void LoadComplete() diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs index 08522b2921..f545e2892f 100644 --- a/osu.Game/Overlays/Login/LoginForm.cs +++ b/osu.Game/Overlays/Login/LoginForm.cs @@ -109,7 +109,7 @@ namespace osu.Game.Overlays.Login } }; - password.OnCommit += (sender, newText) => performLogin(); + password.OnCommit += (_, _) => performLogin(); if (api?.LastLoginError?.Message is string error) errorText.AddErrors(new[] { error }); diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 53e8452dd1..1c007c913e 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.MedalSplash s.Font = s.Font.With(size: 16); }); - medalContainer.OnLoadComplete += d => + medalContainer.OnLoadComplete += _ => { unlocked.Position = new Vector2(0f, medalContainer.DrawSize.Y / 2 + 10); infoFlow.Position = new Vector2(0f, unlocked.Position.Y + 90); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 811db393d6..69cb3a49fc 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -40,7 +40,7 @@ namespace osu.Game.Overlays.Mods /// public Bindable>> AvailableMods { get; } = new Bindable>>(new Dictionary>()); - private Func isValidMod = m => true; + private Func isValidMod = _ => true; /// /// A function determining whether each mod in the column should be displayed. @@ -689,11 +689,11 @@ namespace osu.Game.Overlays.Mods switch (e) { - case ClickEvent _: + case ClickEvent: OnClicked?.Invoke(); return true; - case MouseEvent _: + case MouseEvent: return true; } diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 3c25fc0421..e33fc8064f 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -85,7 +85,7 @@ namespace osu.Game.Overlays.Music }, }; - filter.Search.OnCommit += (sender, newText) => + filter.Search.OnCommit += (_, _) => { list.FirstVisibleSet?.PerformRead(set => { diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index d76df5b1ed..719e77db83 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -129,7 +129,7 @@ namespace osu.Game.Overlays.Notifications case ProgressNotificationState.Completed: loadingSpinner.Hide(); NotificationContent.MoveToY(-DrawSize.Y / 2, 200, Easing.OutQuint); - this.FadeOut(200).Finally(d => Completed()); + this.FadeOut(200).Finally(_ => Completed()); break; } } diff --git a/osu.Game/Overlays/OSD/TrackedSettingToast.cs b/osu.Game/Overlays/OSD/TrackedSettingToast.cs index c66811a3e9..3bed5e7e4c 100644 --- a/osu.Game/Overlays/OSD/TrackedSettingToast.cs +++ b/osu.Game/Overlays/OSD/TrackedSettingToast.cs @@ -68,7 +68,7 @@ namespace osu.Game.Overlays.OSD if (val) selectedOption = 0; break; - case Enum _: + case Enum: var values = Enum.GetValues(description.RawValue.GetType()); optionCount = values.Length; selectedOption = Convert.ToInt32(description.RawValue); diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 2c8e809379..28642f12a1 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -160,13 +160,13 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSettings.ForEach(s => bindPreviewEvent(s.Current)); - windowModeDropdown.Current.BindValueChanged(mode => + windowModeDropdown.Current.BindValueChanged(_ => { updateDisplayModeDropdowns(); updateScreenModeWarning(); }, true); - windowModes.BindCollectionChanged((sender, args) => + windowModes.BindCollectionChanged((_, _) => { if (windowModes.Count > 1) windowModeDropdown.Show(); @@ -190,7 +190,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics updateDisplayModeDropdowns(); }), true); - scalingMode.BindValueChanged(mode => + scalingMode.BindValueChanged(_ => { scalingSettings.ClearTransforms(); scalingSettings.AutoSizeDuration = transition_duration; diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs index 8c8c2c96ff..453dbd2e18 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importBeatmapsButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true)); + legacyImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(_ => Schedule(() => importBeatmapsButton.Enabled.Value = true)); } }); } @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => { deleteBeatmapsButton.Enabled.Value = false; - Task.Run(() => beatmaps.Delete()).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); + Task.Run(() => beatmaps.Delete()).ContinueWith(_ => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); })); } }); @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() => { deleteBeatmapVideosButton.Enabled.Value = false; - Task.Run(beatmaps.DeleteAllVideos).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); + Task.Run(beatmaps.DeleteAllVideos).ContinueWith(_ => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); })); } }); @@ -70,7 +70,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { restoreButton.Enabled.Value = false; - Task.Run(beatmaps.RestoreAll).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); + Task.Run(beatmaps.RestoreAll).ContinueWith(_ => Schedule(() => restoreButton.Enabled.Value = true)); } }, undeleteButton = new SettingsButton @@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { undeleteButton.Enabled.Value = false; - Task.Run(beatmaps.UndeleteAll).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); + Task.Run(beatmaps.UndeleteAll).ContinueWith(_ => Schedule(() => undeleteButton.Enabled.Value = true)); } } }); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 74e9ebc084..5367f644ca 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importCollectionsButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); + legacyImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(_ => Schedule(() => importCollectionsButton.Enabled.Value = true)); } }); } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs index 9c1281f371..70e11d45dc 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importScoresButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true)); + legacyImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(_ => Schedule(() => importScoresButton.Enabled.Value = true)); } }); } @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => { deleteScoresButton.Enabled.Value = false; - Task.Run(() => scores.Delete()).ContinueWith(t => Schedule(() => deleteScoresButton.Enabled.Value = true)); + Task.Run(() => scores.Delete()).ContinueWith(_ => Schedule(() => deleteScoresButton.Enabled.Value = true)); })); } }); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs index 0fff9fef26..c95b1d4dd8 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importSkinsButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Skins).ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true)); + legacyImportManager.ImportFromStableAsync(StableContent.Skins).ContinueWith(_ => Schedule(() => importSkinsButton.Enabled.Value = true)); } }); } @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => { deleteSkinsButton.Enabled.Value = false; - Task.Run(() => skins.Delete()).ContinueWith(t => Schedule(() => deleteSkinsButton.Enabled.Value = true)); + Task.Run(() => skins.Delete()).ContinueWith(_ => Schedule(() => deleteSkinsButton.Enabled.Value = true)); })); } }); diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 6494b59b09..741b6b5815 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -82,12 +82,12 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSubscription = realm.RegisterForNotifications(r => realm.Realm.All() + realmSubscription = realm.RegisterForNotifications(_ => realm.Realm.All() .Where(s => !s.DeletePending) .OrderByDescending(s => s.Protected) // protected skins should be at the top. .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase), skinsChanged); - configBindable.BindValueChanged(id => Scheduler.AddOnce(updateSelectedSkinFromConfig)); + configBindable.BindValueChanged(_ => Scheduler.AddOnce(updateSelectedSkinFromConfig)); dropdownBindable.BindValueChanged(dropdownSelectionChanged); } diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 6c56f3a912..5a03d66b84 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Configuration databasedSettings.Add(setting); } - bindable.ValueChanged += b => + bindable.ValueChanged += _ => { lock (pendingWrites) pendingWrites.Add(lookup); diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 3e93e576ba..c8196b6865 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -129,10 +129,10 @@ namespace osu.Game.Rulesets.Edit switch (e) { - case ScrollEvent _: + case ScrollEvent: return false; - case DoubleClickEvent _: + case DoubleClickEvent: return false; case MouseButtonEvent mouse: diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 9307e36b99..a42d4a049a 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -55,8 +55,8 @@ namespace osu.Game.Rulesets.Mods { base.LoadComplete(); - Current.BindValueChanged(current => updateCurrentFromSlider()); - beatmap.BindValueChanged(b => updateCurrentFromSlider(), true); + Current.BindValueChanged(_ => updateCurrentFromSlider()); + beatmap.BindValueChanged(_ => updateCurrentFromSlider(), true); sliderDisplayCurrent.BindValueChanged(number => { diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 2f5707562e..aea6e12a07 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -165,7 +165,7 @@ namespace osu.Game.Rulesets.Mods public void ApplyToDrawableHitObject(DrawableHitObject drawable) { - drawable.OnNewResult += (o, result) => + drawable.OnNewResult += (_, result) => { if (ratesForRewinding.ContainsKey(result.HitObject)) return; if (!shouldProcessResult(result)) return; @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Mods updateTargetRate(); }; - drawable.OnRevertResult += (o, result) => + drawable.OnRevertResult += (_, result) => { if (!ratesForRewinding.ContainsKey(result.HitObject)) return; if (!shouldProcessResult(result)) return; diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index a1aad3a6b9..daa4b7c797 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mods protected ModTimeRamp() { // for preview purpose at song select. eventually we'll want to be able to update every frame. - FinalRate.BindValueChanged(val => applyRateAdjustment(double.PositiveInfinity), true); + FinalRate.BindValueChanged(_ => applyRateAdjustment(double.PositiveInfinity), true); AdjustPitch.BindValueChanged(applyPitchAdjustment); } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 8bde71dcd7..e098472999 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -126,55 +126,55 @@ namespace osu.Game.Rulesets { switch (mod) { - case ModNoFail _: + case ModNoFail: value |= LegacyMods.NoFail; break; - case ModEasy _: + case ModEasy: value |= LegacyMods.Easy; break; - case ModHidden _: + case ModHidden: value |= LegacyMods.Hidden; break; - case ModHardRock _: + case ModHardRock: value |= LegacyMods.HardRock; break; - case ModPerfect _: + case ModPerfect: value |= LegacyMods.Perfect; break; - case ModSuddenDeath _: + case ModSuddenDeath: value |= LegacyMods.SuddenDeath; break; - case ModNightcore _: + case ModNightcore: value |= LegacyMods.Nightcore; break; - case ModDoubleTime _: + case ModDoubleTime: value |= LegacyMods.DoubleTime; break; - case ModRelax _: + case ModRelax: value |= LegacyMods.Relax; break; - case ModHalfTime _: + case ModHalfTime: value |= LegacyMods.HalfTime; break; - case ModFlashlight _: + case ModFlashlight: value |= LegacyMods.Flashlight; break; - case ModCinema _: + case ModCinema: value |= LegacyMods.Cinema; break; - case ModAutoplay _: + case ModAutoplay: value |= LegacyMods.Autoplay; break; } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index f1ed23bb21..7c37913576 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.UI { switch (e) { - case MouseDownEvent _: + case MouseDownEvent: if (mouseDisabled.Value) return true; // importantly, block upwards propagation so global bindings also don't fire. diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs index 82a9db956a..55302833c1 100644 --- a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons { base.LoadComplete(); - Button.Bindable.BindValueChanged(selected => updateSelectionState(), true); + Button.Bindable.BindValueChanged(_ => updateSelectionState(), true); Action = onAction; } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index ee82ded39d..54ef5a2bd7 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts controlPointGroups.UnbindAll(); controlPointGroups.BindTo(beatmap.ControlPointInfo.Groups); - controlPointGroups.BindCollectionChanged((sender, args) => + controlPointGroups.BindCollectionChanged((_, args) => { switch (args.Action) { diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index cccb646ba4..e058cae191 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts // Run in constructor so IsRedundant calls can work correctly. controlPoints.BindTo(Group.ControlPoints); - controlPoints.BindCollectionChanged((_, __) => + controlPoints.BindCollectionChanged((_, _) => { ClearInternal(); @@ -40,15 +40,15 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { switch (point) { - case TimingControlPoint _: + case TimingControlPoint: AddInternal(new ControlPointVisualisation(point) { Y = 0, }); break; - case DifficultyControlPoint _: + case DifficultyControlPoint: AddInternal(new ControlPointVisualisation(point) { Y = 0.25f, }); break; - case SampleControlPoint _: + case SampleControlPoint: AddInternal(new ControlPointVisualisation(point) { Y = 0.5f, }); break; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs index c1ae2ff8d1..54914f4b23 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { AddInternal(this.content = content ?? new Container { RelativeSizeAxes = Axes.Both }); - beatmap.ValueChanged += b => + beatmap.ValueChanged += _ => { updateRelativeChildSize(); }; diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 6a94fce78a..40403e08ad 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -293,7 +293,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.LoadComplete(); BeatDivisor.BindValueChanged(_ => updateState(), true); - divisorTextBox.OnCommit += (_, __) => setPresets(); + divisorTextBox.OnCommit += (_, _) => setPresets(); Schedule(() => GetContainingInputManager().ChangeFocus(divisorTextBox)); } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index d38aac1173..540fbf9a72 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { - SelectedItems.CollectionChanged += (selectedObjects, args) => + SelectedItems.CollectionChanged += (_, args) => { switch (args.Action) { diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 1c9faadda1..0bdfc5b0a0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // bring in updates from selection changes EditorBeatmap.HitObjectUpdated += _ => Scheduler.AddOnce(UpdateTernaryStates); - SelectedItems.CollectionChanged += (sender, args) => Scheduler.AddOnce(UpdateTernaryStates); + SelectedItems.CollectionChanged += (_, _) => Scheduler.AddOnce(UpdateTernaryStates); } protected override void DeleteItems(IEnumerable items) => EditorBeatmap.RemoveRange(items); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 91af3fde16..8419d3b380 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { InternalChild = SelectionBox = CreateSelectionBox(); - SelectedItems.CollectionChanged += (sender, args) => + SelectedItems.CollectionChanged += (_, _) => { Scheduler.AddOnce(updateVisibility); }; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 08fc8383c3..648ffd9609 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -38,8 +38,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load() { - volume.BindValueChanged(volume => updateText()); - bank.BindValueChanged(bank => updateText(), true); + volume.BindValueChanged(_ => updateText()); + bank.BindValueChanged(_ => updateText(), true); } protected override bool OnClick(ClickEvent e) @@ -119,7 +119,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }); // on commit, ensure that the value is correct by sourcing it from the objects' control points again. // this ensures that committing empty text causes a revert to the previous value. - bank.OnCommit += (_, __) => bank.Current.Value = getCommonBank(relevantControlPoints); + bank.OnCommit += (_, _) => bank.Current.Value = getCommonBank(relevantControlPoints); volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue)); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs index 544c3f8369..cfc71256e8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline controlPointGroups.UnbindAll(); controlPointGroups.BindTo(beatmap.ControlPointInfo.Groups); - controlPointGroups.BindCollectionChanged((sender, args) => + controlPointGroups.BindCollectionChanged((_, args) => { switch (args.Action) { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs index 4017a745e1..10355045be 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline base.LoadComplete(); controlPoints.BindTo(Group.ControlPoints); - controlPoints.BindCollectionChanged((_, __) => + controlPoints.BindCollectionChanged((_, _) => { ClearInternal(); diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index ef670c9fcf..3d18b00e75 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Edit.Compose if (composer == null) return; - EditorBeatmap.SelectedHitObjects.BindCollectionChanged((_, __) => updateClipboardActionAvailability()); + EditorBeatmap.SelectedHitObjects.BindCollectionChanged((_, _) => updateClipboardActionAvailability()); clipboard.BindValueChanged(_ => updateClipboardActionAvailability()); composer.OnLoadComplete += _ => updateClipboardActionAvailability(); updateClipboardActionAvailability(); diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 582c8f5272..0a6f9974b0 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -105,8 +105,8 @@ namespace osu.Game.Screens.Edit { switch (controlPoint) { - case DifficultyControlPoint _: - case SampleControlPoint _: + case DifficultyControlPoint: + case SampleControlPoint: // skip legacy types. continue; diff --git a/osu.Game/Screens/Edit/EditorBeatmapSkin.cs b/osu.Game/Screens/Edit/EditorBeatmapSkin.cs index 244fd5f1e9..475c894b30 100644 --- a/osu.Game/Screens/Edit/EditorBeatmapSkin.cs +++ b/osu.Game/Screens/Edit/EditorBeatmapSkin.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit ComboColours = new BindableList(); if (Skin.Configuration.ComboColours != null) ComboColours.AddRange(Skin.Configuration.ComboColours.Select(c => (Colour4)c)); - ComboColours.BindCollectionChanged((_, __) => updateColours()); + ComboColours.BindCollectionChanged((_, _) => updateColours()); } private void invokeSkinChanged() => BeatmapSkinChanged?.Invoke(); diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index 12a6843067..40bbfeaf7d 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Edit.Setup EnableCountdown.Current.BindValueChanged(_ => updateBeatmap()); CountdownSpeed.Current.BindValueChanged(_ => updateBeatmap()); - CountdownOffset.OnCommit += (_, __) => onOffsetCommitted(); + CountdownOffset.OnCommit += (_, _) => onOffsetCommitted(); widescreenSupport.Current.BindValueChanged(_ => updateBeatmap()); epilepsyWarning.Current.BindValueChanged(_ => updateBeatmap()); diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index b5c162ab11..b51da2c53d 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedGroup.BindValueChanged(group => + selectedGroup.BindValueChanged(_ => { // TODO: This should scroll the selected row into view. updateSelectedGroup(); @@ -153,7 +153,7 @@ namespace osu.Game.Screens.Edit.Timing protected override void LoadComplete() { base.LoadComplete(); - controlPoints.CollectionChanged += (_, __) => createChildren(); + controlPoints.CollectionChanged += (_, _) => createChildren(); } private void createChildren() diff --git a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs index d05777e793..998e49a6ab 100644 --- a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs +++ b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Edit.Timing base.LoadComplete(); Current.BindValueChanged(_ => updateFromCurrent(), true); - numeratorBox.OnCommit += (_, __) => updateFromNumeratorBox(); + numeratorBox.OnCommit += (_, _) => updateFromNumeratorBox(); } private void updateFromCurrent() diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs index 6a804c7e3e..4dcb5ad2ba 100644 --- a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Edit.Timing Current.TriggerChange(); }; - Current.BindValueChanged(val => + Current.BindValueChanged(_ => { decimal decimalValue = slider.Current.Value.ToDecimal(NumberFormatInfo.InvariantInfo); textBox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}"); diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index c33dcc6e91..fd218209d4 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -137,7 +137,7 @@ namespace osu.Game.Screens.Edit.Timing }, true); controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups); - controlPointGroups.BindCollectionChanged((sender, args) => + controlPointGroups.BindCollectionChanged((_, _) => { table.ControlGroups = controlPointGroups; changeHandler?.SaveState(); diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 8413409d84..9b86969db1 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit.Timing { Label = "BPM"; - OnCommit += (val, isNew) => + OnCommit += (_, isNew) => { if (!isNew) return; diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 1cdcfdf167..2956a28547 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(_ => updateTimingGroup(), true); controlPointGroups.BindTo(editorBeatmap.ControlPointInfo.Groups); - controlPointGroups.BindCollectionChanged((_, __) => updateTimingGroup()); + controlPointGroups.BindCollectionChanged((_, _) => updateTimingGroup()); beatLength.BindValueChanged(_ => regenerateDisplay(true), true); diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index 7ce70b4b5f..bffda4ec41 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Edit.Verify base.LoadComplete(); verify.InterpretedDifficulty.BindValueChanged(_ => refresh()); - verify.HiddenIssueTypes.BindCollectionChanged((_, __) => refresh()); + verify.HiddenIssueTypes.BindCollectionChanged((_, _) => refresh()); refresh(); } diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index f77123af52..a81658a4b6 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -222,7 +222,7 @@ namespace osu.Game.Screens.Menu .Then(5500) .FadeOut(250) .ScaleTo(0.9f, 250, Easing.InQuint) - .Finally(d => + .Finally(_ => { if (nextScreen != null) this.Push(nextScreen); diff --git a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs index 1dc05def0f..39a887f820 100644 --- a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.OnlinePlay.Components [BackgroundDependencyLoader] private void load() { - Playlist.CollectionChanged += (_, __) => updateText(); + Playlist.CollectionChanged += (_, _) => updateText(); updateText(); } diff --git a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs index bed55e8a79..9a48769405 100644 --- a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs +++ b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.OnlinePlay.Components switch (tab) { - case BeatmapDetailAreaPlaylistTabItem _: + case BeatmapDetailAreaPlaylistTabItem: playlistArea.Show(); break; diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs index 795f776496..fe89eaf591 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.OnlinePlay.Components InternalChild = sprite = CreateBackgroundSprite(); CurrentPlaylistItem.BindValueChanged(_ => updateBeatmap()); - Playlist.CollectionChanged += (_, __) => updateBeatmap(); + Playlist.CollectionChanged += (_, _) => updateBeatmap(); updateBeatmap(); } diff --git a/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs b/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs index 43ad5e97ec..a268d4d917 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { base.LoadComplete(); - Playlist.BindCollectionChanged((_, __) => Details.Value = Playlist.GetTotalDuration(), true); + Playlist.BindCollectionChanged((_, _) => Details.Value = Playlist.GetTotalDuration(), true); } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs index d8515d2bd2..2f38321e13 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.OnlinePlay.Components [BackgroundDependencyLoader] private void load() { - RecentParticipants.CollectionChanged += (_, __) => updateParticipants(); + RecentParticipants.CollectionChanged += (_, _) => updateParticipants(); updateParticipants(); } diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index 4cf785ce60..b6ef080e44 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Components base.LoadComplete(); DifficultyRange.BindValueChanged(_ => updateRange()); - Playlist.BindCollectionChanged((_, __) => updateRange(), true); + Playlist.BindCollectionChanged((_, _) => updateRange(), true); } private void updateRange() diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 489bf128e8..5193fe5cbf 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -181,7 +181,7 @@ namespace osu.Game.Screens.OnlinePlay // schedules added as the properties may change value while the drawable items haven't been created yet. SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(scrollToSelection)); - Items.BindCollectionChanged((_, __) => Scheduler.AddOnce(scrollToSelection), true); + Items.BindCollectionChanged((_, _) => Scheduler.AddOnce(scrollToSelection), true); } private void scrollToSelection() diff --git a/osu.Game/Screens/OnlinePlay/Header.cs b/osu.Game/Screens/OnlinePlay/Header.cs index 06befe0971..a9e9f046e2 100644 --- a/osu.Game/Screens/OnlinePlay/Header.cs +++ b/osu.Game/Screens/OnlinePlay/Header.cs @@ -38,8 +38,8 @@ namespace osu.Game.Screens.OnlinePlay }; // unnecessary to unbind these as this header has the same lifetime as the screen stack we are attaching to. - stack.ScreenPushed += (_, __) => updateSubScreenTitle(); - stack.ScreenExited += (_, __) => updateSubScreenTitle(); + stack.ScreenPushed += (_, _) => updateSubScreenTitle(); + stack.ScreenExited += (_, _) => updateSubScreenTitle(); } private void updateSubScreenTitle() => title.Screen = stack.CurrentScreen as IOnlinePlaySubScreen; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index 5bdd24221c..474463d5f1 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components base.LoadComplete(); PlaylistItemStats.BindValueChanged(_ => updateCount()); - Playlist.BindCollectionChanged((_, __) => updateCount(), true); + Playlist.BindCollectionChanged((_, _) => updateCount(), true); } private void updateCount() diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 9784e5aace..8a2aeb9e5e 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -250,7 +250,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge base.LoadComplete(); ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(passwordTextBox)); - passwordTextBox.OnCommit += (_, __) => performJoin(); + passwordTextBox.OnCommit += (_, _) => performJoin(); } private void performJoin() diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs index d81fee24e0..58ccb24f7a 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge public LoungeBackgroundScreen() { SelectedRoom.BindValueChanged(onSelectedRoomChanged); - playlist.BindCollectionChanged((_, __) => PlaylistItem = playlist.GetCurrentItem()); + playlist.BindCollectionChanged((_, _) => PlaylistItem = playlist.GetCurrentItem()); } private void onSelectedRoomChanged(ValueChangedEvent room) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index f7f3c27ede..09bc496da5 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -304,7 +304,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge joiningRoomOperation = ongoingOperationTracker?.BeginOperation(); - RoomManager?.JoinRoom(room, password, r => + RoomManager?.JoinRoom(room, password, _ => { Open(room); joiningRoomOperation?.Dispose(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 20762b2f22..c5d6da1ebc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match switch (room?.Countdown) { - case MatchStartCountdown _: + case MatchStartCountdown: newCountdown = room.Countdown; break; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs index b5bb4a766f..b55a7d0731 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { var clickOperation = ongoingOperationTracker.BeginOperation(); - Client.ToggleSpectate().ContinueWith(t => endOperation()); + Client.ToggleSpectate().ContinueWith(_ => endOperation()); void endOperation() => clickOperation?.Dispose(); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistTabControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistTabControl.cs index 9877c15f2f..f7abc91227 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistTabControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistTabControl.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist protected override void LoadComplete() { base.LoadComplete(); - QueueItems.BindCollectionChanged((_, __) => Text.Text = QueueItems.Count > 0 ? $"Queue ({QueueItems.Count})" : "Queue", true); + QueueItems.BindCollectionChanged((_, _) => Text.Text = QueueItems.Count > 0 ? $"Queue ({QueueItems.Count})" : "Queue", true); } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 04342788db..39740e650f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist protected override void LoadComplete() { base.LoadComplete(); - roomPlaylist.BindCollectionChanged((_, __) => InvalidateLayout()); + roomPlaylist.BindCollectionChanged((_, _) => InvalidateLayout()); } public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderBy(item => item.Model.PlaylistOrder); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index ae65e1d969..d49f894ad7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void load() { isConnected.BindTo(client.IsConnected); - isConnected.BindValueChanged(c => Scheduler.AddOnce(poll), true); + isConnected.BindValueChanged(_ => Scheduler.AddOnce(poll), true); } private void poll() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index df0f94688b..a82da7b185 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -125,7 +125,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer client.GameplayStarted += onGameplayStarted; client.ResultsReady += onResultsReady; - ScoreProcessor.HasCompleted.BindValueChanged(completed => + ScoreProcessor.HasCompleted.BindValueChanged(_ => { // wait for server to tell us that results are ready (see SubmitScore implementation) loadingDisplay.Show(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index e048c12686..8270bb3b6f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -133,7 +133,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(users) { Expanded = { Value = true }, - }, l => + }, _ => { foreach (var instance in instances) leaderboard.AddClock(instance.UserId, instance.GameplayClock); diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 1d9ea5f77d..0bf5f2604c 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -94,7 +94,7 @@ namespace osu.Game.Screens.OnlinePlay base.LoadComplete(); subScreenSelectedItem?.BindValueChanged(_ => UpdateSelectedItem()); - Playlist.BindCollectionChanged((_, __) => UpdateSelectedItem(), true); + Playlist.BindCollectionChanged((_, _) => UpdateSelectedItem(), true); } protected void UpdateSelectedItem() diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index ff657e16fb..8a9c4db6ad 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists } }, true); - Room.MaxAttempts.BindValueChanged(attempts => progressSection.Alpha = Room.MaxAttempts.Value != null ? 1 : 0, true); + Room.MaxAttempts.BindValueChanged(_ => progressSection.Alpha = Room.MaxAttempts.Value != null ? 1 : 0, true); } protected override Drawable CreateMainContent() => new Container diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index ca485aa8ea..c1223d7262 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -198,7 +198,7 @@ namespace osu.Game.Screens.Play dropOffScreen(obj, failTime, rotation, originalScale, originalPosition); // need to reapply the fail drop after judgement state changes - obj.ApplyCustomUpdateState += (o, _) => dropOffScreen(obj, failTime, rotation, originalScale, originalPosition); + obj.ApplyCustomUpdateState += (_, _) => dropOffScreen(obj, failTime, rotation, originalScale, originalPosition); appliedObjects.Add(obj); } diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 99b9362700..c2652e9212 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -142,7 +142,7 @@ namespace osu.Game.Screens.Play }, }; - State.ValueChanged += s => InternalButtons.Deselect(); + State.ValueChanged += _ => InternalButtons.Deselect(); updateRetryCount(); } @@ -267,7 +267,7 @@ namespace osu.Game.Screens.Play { switch (e) { - case ScrollEvent _: + case ScrollEvent: if (ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) return globalAction.TriggerEvent(e); diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index aa9a2ab242..39a8f1e783 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -218,7 +218,7 @@ namespace osu.Game.Screens.Play.HUD overlayCircle.ScaleTo(0, 100) .Then().FadeOut().ScaleTo(1).FadeIn(500) - .OnComplete(a => + .OnComplete(_ => { icon.ScaleTo(1, 100); circularProgress.FadeOut(100).OnComplete(_ => diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index fe27e1fc24..8f80644d52 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -352,7 +352,7 @@ namespace osu.Game.Screens.Play // When the scoring mode changes, relative positions of elements may change (see DefaultSkin.GetDrawableComponent). // This is a best effort implementation for cases where users haven't customised layouts. scoringMode = config.GetBindable(OsuSetting.ScoreDisplayMode); - scoringMode.BindValueChanged(val => Reload()); + scoringMode.BindValueChanged(_ => Reload()); } } } diff --git a/osu.Game/Screens/Play/KeyCounterDisplay.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs index ef28632d76..b6094726c0 100644 --- a/osu.Game/Screens/Play/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -157,10 +157,10 @@ namespace osu.Game.Screens.Play { switch (e) { - case KeyDownEvent _: - case KeyUpEvent _: - case MouseDownEvent _: - case MouseUpEvent _: + case KeyDownEvent: + case KeyUpEvent: + case MouseDownEvent: + case MouseUpEvent: return Target.Children.Any(c => c.TriggerEvent(e)); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4a8460ff46..4040adc48d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -173,7 +173,7 @@ namespace osu.Game.Screens.Play PrepareReplay(); - ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(Score.ScoreInfo); + ScoreProcessor.NewJudgement += _ => ScoreProcessor.PopulateScore(Score.ScoreInfo); ScoreProcessor.OnResetFromReplayFrame += () => ScoreProcessor.PopulateScore(Score.ScoreInfo); gameActive.BindValueChanged(_ => updatePauseOnFocusLostState(), true); @@ -315,7 +315,7 @@ namespace osu.Game.Screens.Play GameplayClockContainer.Start(); }); - DrawableRuleset.IsPaused.BindValueChanged(paused => + DrawableRuleset.IsPaused.BindValueChanged(_ => { updateGameplayState(); updateSampleDisabledState(); diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 2a5e356df8..64b4853a67 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Play protected override bool CheckModsAllowFailure() => false; public ReplayPlayer(Score score, PlayerConfiguration configuration = null) - : this((_, __) => score, configuration) + : this((_, _) => score, configuration) { } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs index 5d7b04743e..3505786b64 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Spacing = new Vector2(10, 0), Children = new[] { - base.CreateContent().With(d => + base.CreateContent().With(_ => { Anchor = Anchor.CentreLeft; Origin = Anchor.CentreLeft; diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 384f4c4d4f..ab68dec92d 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Ranking.Statistics Task.Run(() => { playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods); - }, loadCancellation.Token).ContinueWith(t => Schedule(() => + }, loadCancellation.Token).ContinueWith(_ => Schedule(() => { bool hitEventsAvailable = newScore.HitEvents.Count != 0; Container container; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 87a1fb4ccb..72e6c7d159 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -959,7 +959,7 @@ namespace osu.Game.Screens.Select { // root should always remain selected. if not, PerformSelection will not be called. State.Value = CarouselItemState.Selected; - State.ValueChanged += state => State.Value = CarouselItemState.Selected; + State.ValueChanged += _ => State.Value = CarouselItemState.Selected; this.carousel = carousel; } diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index dfcd43189b..bf6803f551 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Select { switch (tab) { - case BeatmapDetailAreaDetailTabItem _: + case BeatmapDetailAreaDetailTabItem: Details.Show(); break; diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index fdc0d4efdd..2fe62c4a4e 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -217,7 +217,7 @@ namespace osu.Game.Screens.Select }); }; - lookup.Failure += e => + lookup.Failure += _ => { Schedule(() => { diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index a948e5b0bd..cc01f61c57 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Select.Carousel + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) .OrderByDescending(s => s.TotalScore), - (items, changes, ___) => + (items, _, _) => { Rank = items.FirstOrDefault()?.Rank; // Required since presence is changed via IsPresent override diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 59eb3fa607..693f182065 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -96,7 +96,7 @@ namespace osu.Game.Screens.Select.Details modSettingChangeTracker?.Dispose(); modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); - modSettingChangeTracker.SettingChanged += m => + modSettingChangeTracker.SettingChanged += _ => { debouncedStatisticsUpdate?.Cancel(); debouncedStatisticsUpdate = Scheduler.AddDelayed(updateStatistics, 100); diff --git a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs index 4d3c844753..a19acddb48 100644 --- a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select { switch (item) { - case BeatmapDetailAreaDetailTabItem _: + case BeatmapDetailAreaDetailTabItem: return TabType.Details; case BeatmapDetailAreaLeaderboardTabItem leaderboardTab: diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 43b3cb0aa4..2467b7d039 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -74,7 +74,7 @@ namespace osu.Game.Skinning switch (target.Target) { case SkinnableTarget.SongSelect: - var songSelectComponents = new SkinnableTargetComponentsContainer(container => + var songSelectComponents = new SkinnableTargetComponentsContainer(_ => { // do stuff when we need to. }); diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 7e9f1d0e8e..649b63dda4 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -190,13 +190,13 @@ namespace osu.Game.Skinning.Editor // schedule ensures this only happens when the skin editor is visible. // also avoid some weird endless recursion / bindable feedback loop (something to do with tracking skins across three different bindable types). // probably something which will be factored out in a future database refactor so not too concerning for now. - currentSkin.BindValueChanged(skin => + currentSkin.BindValueChanged(_ => { hasBegunMutating = false; Scheduler.AddOnce(skinChanged); }, true); - SelectedComponents.BindCollectionChanged((_, __) => Scheduler.AddOnce(populateSettings), true); + SelectedComponents.BindCollectionChanged((_, _) => Scheduler.AddOnce(populateSettings), true); } public void UpdateTargetScreen(Drawable targetScreen) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index de33d49941..000917f728 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Skinning.Editor var editor = new SkinEditor(); - editor.State.BindValueChanged(visibility => updateComponentVisibility()); + editor.State.BindValueChanged(_ => updateComponentVisibility()); skinEditor = editor; diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 2aaf0ba18e..49914c53aa 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -120,16 +120,16 @@ namespace osu.Game.Skinning currentConfig.MinimumColumnWidth = minWidth; break; - case string _ when pair.Key.StartsWith("Colour", StringComparison.Ordinal): + case string when pair.Key.StartsWith("Colour", StringComparison.Ordinal): HandleColours(currentConfig, line); break; // Custom sprite paths - case string _ when pair.Key.StartsWith("NoteImage", StringComparison.Ordinal): - case string _ when pair.Key.StartsWith("KeyImage", StringComparison.Ordinal): - case string _ when pair.Key.StartsWith("Hit", StringComparison.Ordinal): - case string _ when pair.Key.StartsWith("Stage", StringComparison.Ordinal): - case string _ when pair.Key.StartsWith("Lighting", StringComparison.Ordinal): + case string when pair.Key.StartsWith("NoteImage", StringComparison.Ordinal): + case string when pair.Key.StartsWith("KeyImage", StringComparison.Ordinal): + case string when pair.Key.StartsWith("Hit", StringComparison.Ordinal): + case string when pair.Key.StartsWith("Stage", StringComparison.Ordinal): + case string when pair.Key.StartsWith("Lighting", StringComparison.Ordinal): currentConfig.ImageLookups[pair.Key] = pair.Value; break; } diff --git a/osu.Game/Skinning/SkinImporter.cs b/osu.Game/Skinning/SkinImporter.cs index 1b5de9abb5..ed31a8c3f5 100644 --- a/osu.Game/Skinning/SkinImporter.cs +++ b/osu.Game/Skinning/SkinImporter.cs @@ -214,7 +214,7 @@ namespace osu.Game.Skinning { try { - realm.Write(r => skin.Hash = ComputeHash(skin)); + realm.Write(_ => skin.Hash = ComputeHash(skin)); } catch (Exception e) { diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index d7399b0141..e447570931 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -269,9 +269,9 @@ namespace osu.Game.Skinning { switch (lookup) { - case GlobalSkinColours _: - case SkinComboColourLookup _: - case SkinCustomColourLookup _: + case GlobalSkinColours: + case SkinComboColourLookup: + case SkinCustomColourLookup: if (provider.AllowColourLookup) return skin.GetConfig(lookup); diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 838dc618f2..a759482b5d 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -109,20 +109,20 @@ namespace osu.Game.Storyboards generateCommands(generated, getCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value, (d, value, duration, easing) => d.RotateTo(value, duration, easing)); generateCommands(generated, getCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value, (d, value, duration, easing) => d.FadeColour(value, duration, easing)); generateCommands(generated, getCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing)); - generateCommands(generated, getCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, (d, value, duration, easing) => d.TransformBlendingMode(value, duration), + generateCommands(generated, getCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, (d, value, duration, _) => d.TransformBlendingMode(value, duration), false); if (drawable is IVectorScalable vectorScalable) { - generateCommands(generated, getCommands(g => g.VectorScale, triggeredGroups), (d, value) => vectorScalable.VectorScale = value, - (d, value, duration, easing) => vectorScalable.VectorScaleTo(value, duration, easing)); + generateCommands(generated, getCommands(g => g.VectorScale, triggeredGroups), (_, value) => vectorScalable.VectorScale = value, + (_, value, duration, easing) => vectorScalable.VectorScaleTo(value, duration, easing)); } if (drawable is IFlippable flippable) { - generateCommands(generated, getCommands(g => g.FlipH, triggeredGroups), (d, value) => flippable.FlipH = value, (d, value, duration, easing) => flippable.TransformFlipH(value, duration), + generateCommands(generated, getCommands(g => g.FlipH, triggeredGroups), (_, value) => flippable.FlipH = value, (_, value, duration, _) => flippable.TransformFlipH(value, duration), false); - generateCommands(generated, getCommands(g => g.FlipV, triggeredGroups), (d, value) => flippable.FlipV = value, (d, value, duration, easing) => flippable.TransformFlipV(value, duration), + generateCommands(generated, getCommands(g => g.FlipV, triggeredGroups), (_, value) => flippable.FlipV = value, (_, value, duration, _) => flippable.TransformFlipV(value, duration), false); } diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs index 9adf6458cd..803db07fa0 100644 --- a/osu.Game/Tests/Visual/ScreenTestScene.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -49,8 +49,8 @@ namespace osu.Game.Tests.Visual } }); - Stack.ScreenPushed += (lastScreen, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed → {newScreen}"); - Stack.ScreenExited += (lastScreen, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed ← {newScreen}"); + Stack.ScreenPushed += (_, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed → {newScreen}"); + Stack.ScreenExited += (_, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed ← {newScreen}"); } protected void LoadScreen(OsuScreen screen) => Stack.Push(screen); From 81bfe1406386a8c40132ccdc39afefdbf5049609 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 21:28:24 +0900 Subject: [PATCH 210/803] Update `DotSettings` to match framework (in most cases) --- osu.sln.DotSettings | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 8fd750a50d..32dbb62e5e 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -7,19 +7,22 @@ ExplicitlyExcluded SOLUTION WARNING + WARNING + WARNING WARNING WARNING HINT HINT WARNING + WARNING + WARNING WARNING True WARNING WARNING HINT DO_NOT_SHOW - WARNING - WARNING + HINT HINT HINT WARNING @@ -58,14 +61,17 @@ HINT HINT HINT - WARNING + HINT + DO_NOT_SHOW WARNING WARNING WARNING WARNING + DO_NOT_SHOW WARNING WARNING WARNING + DO_NOT_SHOW WARNING WARNING WARNING @@ -73,7 +79,7 @@ WARNING HINT WARNING - HINT + DO_NOT_SHOW DO_NOT_SHOW WARNING DO_NOT_SHOW @@ -107,7 +113,6 @@ HINT HINT WARNING - DO_NOT_SHOW DO_NOT_SHOW WARNING WARNING @@ -118,16 +123,13 @@ WARNING WARNING WARNING - HINT + HINT WARNING WARNING WARNING WARNING HINT - HINT WARNING - HINT - HINT HINT HINT HINT @@ -135,8 +137,10 @@ HINT HINT WARNING + HINT + HINT WARNING - WARNING + HINT WARNING WARNING WARNING @@ -145,6 +149,7 @@ DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW + WARNING WARNING WARNING WARNING @@ -213,8 +218,7 @@ WARNING SUGGESTION DO_NOT_SHOW - - True + HINT DO_NOT_SHOW WARNING WARNING @@ -230,8 +234,8 @@ HINT HINT HINT - HINT HINT + HINT HINT DO_NOT_SHOW WARNING @@ -265,6 +269,7 @@ Explicit ExpressionBody BlockBody + ExplicitlyTyped True NEXT_LINE True From 14327943a2d0eb550ed1d4f8bddaef9a2c8d8246 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 21:28:30 +0900 Subject: [PATCH 211/803] 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 f2790d2520..9cd1709211 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 0802a98858..9e7c0020c3 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 a272df20d4..5315831a33 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From df30bedab6f14d45466dd1c6391dec1ad99bf871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 Jun 2022 16:01:52 +0200 Subject: [PATCH 212/803] Update framework again --- 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 9cd1709211..8f4a102cce 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 9e7c0020c3..b9033f1bb9 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 5315831a33..a1e4cf3ba0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From a83c45b50e4f126bfa218d4bd8e4d8a2bf1e5145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 Jun 2022 16:07:14 +0200 Subject: [PATCH 213/803] Fix compile failures from framework breaking change --- osu.Game.Tournament/TournamentGameBase.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index ccbe77fa95..75c9f17d4c 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tournament dependencies.Cache(new TournamentVideoResourceStore(storage)); - Textures.AddStore(new TextureLoaderStore(new StorageBackedResourceStore(storage))); + Textures.AddTextureSource(new TextureLoaderStore(new StorageBackedResourceStore(storage))); dependencies.CacheAs(new StableInfo(storage)); } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index b0ba62a6ac..7d39aad226 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -244,7 +244,7 @@ namespace osu.Game dependencies.CacheAs(Storage); var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures"))); - largeStore.AddStore(Host.CreateTextureLoaderStore(new OnlineStore())); + largeStore.AddTextureSource(Host.CreateTextureLoaderStore(new OnlineStore())); dependencies.Cache(largeStore); dependencies.CacheAs(this); From e0c2228b4181d707702873c9d4943003debb7b26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 Jun 2022 16:40:35 +0200 Subject: [PATCH 214/803] Revert downgrades in code inspection settings --- osu.sln.DotSettings | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 32dbb62e5e..a55b3b5074 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -22,7 +22,8 @@ WARNING HINT DO_NOT_SHOW - HINT + WARNING + WARNING HINT HINT WARNING @@ -61,7 +62,7 @@ HINT HINT HINT - HINT + WARNING DO_NOT_SHOW WARNING WARNING @@ -79,7 +80,7 @@ WARNING HINT WARNING - DO_NOT_SHOW + HINT DO_NOT_SHOW WARNING DO_NOT_SHOW @@ -113,6 +114,7 @@ HINT HINT WARNING + DO_NOT_SHOW DO_NOT_SHOW WARNING WARNING @@ -129,7 +131,10 @@ WARNING WARNING HINT + HINT WARNING + HINT + HINT HINT HINT HINT @@ -140,7 +145,7 @@ HINT HINT WARNING - HINT + WARNING WARNING WARNING WARNING @@ -218,7 +223,8 @@ WARNING SUGGESTION DO_NOT_SHOW - HINT + + True DO_NOT_SHOW WARNING WARNING @@ -234,8 +240,8 @@ HINT HINT HINT - HINT HINT + HINT HINT DO_NOT_SHOW WARNING From a039f9878a6b4eca158982dce24fdd4ca0dd8584 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 25 Jun 2022 19:33:44 +0900 Subject: [PATCH 215/803] Adjust menu cursor rotation to follow mouse, rather than be fixated around original click location --- osu.Game/Graphics/Cursor/MenuCursor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 4544633318..b387dbf9a8 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -51,6 +51,9 @@ namespace osu.Game.Graphics.Cursor { if (dragRotationState != DragRotationState.NotDragging) { + if (Vector2.Distance(positionMouseDown, e.MousePosition) > 100) + positionMouseDown = Interpolation.ValueAt(0.001f, positionMouseDown, e.MousePosition, 0, Clock.ElapsedFrameTime); + var position = e.MousePosition; float distance = Vector2Extensions.Distance(position, positionMouseDown); From 2ecab454aa4dbde8ca37f2d07a0f93be80e9dff0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 25 Jun 2022 19:43:39 +0900 Subject: [PATCH 216/803] Adjust animations and tolerances to make things feel snappier --- osu.Game/Graphics/Cursor/MenuCursor.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index b387dbf9a8..10ed76ebdd 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -51,15 +51,16 @@ namespace osu.Game.Graphics.Cursor { if (dragRotationState != DragRotationState.NotDragging) { - if (Vector2.Distance(positionMouseDown, e.MousePosition) > 100) - positionMouseDown = Interpolation.ValueAt(0.001f, positionMouseDown, e.MousePosition, 0, Clock.ElapsedFrameTime); + // make the rotation centre point floating. + if (Vector2.Distance(positionMouseDown, e.MousePosition) > 60) + positionMouseDown = Interpolation.ValueAt(0.005f, positionMouseDown, e.MousePosition, 0, Clock.ElapsedFrameTime); var position = e.MousePosition; float distance = Vector2Extensions.Distance(position, positionMouseDown); // don't start rotating until we're moved a minimum distance away from the mouse down location, // else it can have an annoying effect. - if (dragRotationState == DragRotationState.DragStarted && distance > 30) + if (dragRotationState == DragRotationState.DragStarted && distance > 80) dragRotationState = DragRotationState.Rotating; // don't rotate when distance is zero to avoid NaN @@ -74,7 +75,7 @@ namespace osu.Game.Graphics.Cursor if (diff > 180) diff -= 360; degrees = activeCursor.Rotation + diff; - activeCursor.RotateTo(degrees, 600, Easing.OutQuint); + activeCursor.RotateTo(degrees, 120, Easing.OutQuint); } } @@ -114,7 +115,7 @@ namespace osu.Game.Graphics.Cursor if (dragRotationState != DragRotationState.NotDragging) { - activeCursor.RotateTo(0, 600 * (1 + Math.Abs(activeCursor.Rotation / 720)), Easing.OutElasticHalf); + activeCursor.RotateTo(0, 400 * (0.5f + Math.Abs(activeCursor.Rotation / 960)), Easing.OutElasticQuarter); dragRotationState = DragRotationState.NotDragging; } From 1a22377e19d45ee88dab9e9effba1260b7bacf47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 26 Jun 2022 16:42:28 +0900 Subject: [PATCH 217/803] Fix test deadlock due to TPL threadpool saturation As found in aggressive CI runs: ```csharp 00007F6F527F32B0 00007f74f937c72d (MethodDesc 00007f74f5545a78 + 0x22d System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken)), calling (MethodDesc 00007f74f51c0838 + 0 System.Threading.Monitor.Wait(System.Object, Int32)) 00007F6F527F3330 00007f74f93861ca (MethodDesc 00007f74f5259100 + 0x10a System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken)), calling (MethodDesc 00007f74f5545a78 + 0 System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken)) 00007F6F527F3390 00007f74f9385d11 (MethodDesc 00007f74f52590e8 + 0x81 System.Threading.Tasks.Task.InternalWaitCore(Int32, System.Threading.CancellationToken)), calling (MethodDesc 00007f74f5259100 + 0 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken)) 00007F6F527F33E0 00007f74fc3823af (MethodDesc 00007f74f5934c08 + 0x4f System.Threading.Tasks.Task`1[[System.Boolean, System.Private.CoreLib]].GetResultCore(Boolean)), calling (MethodDesc 00007f74f52590e8 + 0 System.Threading.Tasks.Task.InternalWaitCore(Int32, System.Threading.CancellationToken)) 00007F6F527F3400 00007f74fb0aad92 (MethodDesc 00007f74fb28b000 + 0x122 osu.Game.Tests.Visual.OnlinePlay.OnlinePlayTestScene+<>c__DisplayClass21_0.b__1(osu.Game.Online.API.APIRequest)), calling (MethodDesc 00007f74f5934be8 + 0 System.Threading.Tasks.Task`1[[System.Boolean, System.Private.CoreLib]].get_Result()) 00007F6F527F3470 00007f74fa9c6dd7 (MethodDesc 00007f74f921f6c8 + 0x77 osu.Game.Online.API.DummyAPIAccess.PerformAsync(osu.Game.Online.API.APIRequest)) 00007F6F527F34C0 00007f74fb13c718 (MethodDesc 00007f74fb4e0c50 + 0x538 osu.Game.Database.OnlineLookupCache`3+d__13[[System.Int32, System.Private.CoreLib],[System.__Canon, System.Private.CoreLib],[System.__Canon, System.Private.CoreLib]].MoveNext()) ``` Going with the simplest solution, as what we had was pretty ugly to start with and I don't want to overthink this, just push a fix and hope for the best. --- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 282312c9c1..6577057c17 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -4,14 +4,14 @@ #nullable disable using System; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Database; +using osu.Framework.Logging; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay; @@ -72,22 +72,18 @@ namespace osu.Game.Tests.Visual.OnlinePlay ((DummyAPIAccess)API).HandleRequest = request => { - TaskCompletionSource tcs = new TaskCompletionSource(); - - // Because some of the handlers use realm, we need to ensure the game is still alive when firing. - // If we don't, a stray `PerformAsync` could hit an `ObjectDisposedException` if running too late. - Scheduler.Add(() => + try { - bool result = handler.HandleRequest(request, API.LocalUser.Value, beatmapManager); - tcs.SetResult(result); - }, false); - -#pragma warning disable RS0030 - // We can't GetResultSafely() here (will fail with "Can't use GetResultSafely from inside an async operation."), but Wait is safe enough due to - // the task being a TaskCompletionSource. - // Importantly, this doesn't deadlock because of the scheduler call above running inline where feasible (see the `false` argument). - return tcs.Task.Result; -#pragma warning restore RS0030 + return handler.HandleRequest(request, API.LocalUser.Value, beatmapManager); + } + catch (ObjectDisposedException) + { + // These requests can be fired asynchronously, but potentially arrive after game components + // have been disposed (ie. realm in BeatmapManager). + // This only happens in tests and it's easiest to ignore them for now. + Logger.Log($"Handled {nameof(ObjectDisposedException)} in test request handling"); + return true; + } }; }); From cd9eaf215b9d3c9a10271d008a8a3baa25f0407c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 02:58:01 +0900 Subject: [PATCH 218/803] Attempt to fix flaky `PlaylistOverlay` test --- .../Visual/UserInterface/TestScenePlaylistOverlay.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs index 4c35ec40b5..ee5ef2f364 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs @@ -27,6 +27,8 @@ namespace osu.Game.Tests.Visual.UserInterface private Live first; + private const int item_count = 100; + [SetUp] public void Setup() => Schedule(() => { @@ -46,7 +48,7 @@ namespace osu.Game.Tests.Visual.UserInterface beatmapSets.Clear(); - for (int i = 0; i < 100; i++) + for (int i = 0; i < item_count; i++) { beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo().ToLiveUnmanaged()); } @@ -59,6 +61,13 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestRearrangeItems() { + AddUntilStep("wait for load complete", () => + { + return this + .ChildrenOfType() + .Count(i => i.ChildrenOfType().First().DelayedLoadCompleted) > 6; + }); + AddUntilStep("wait for animations to complete", () => !playlistOverlay.Transforms.Any()); AddStep("hold 1st item handle", () => From b5a8889fb80e359d0afb17a7d1e76dc4b66df941 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 03:04:49 +0900 Subject: [PATCH 219/803] Attempt to fix flaky `EditorNavigation` test Conditionals taken from https://github.com/ppy/osu/blob/f8830c6850128456266c82de83273204f8b74ac0/osu.Game/Tests/Visual/EditorTestScene.cs#L61-L62 where it seemed to resolve other similar cases. --- osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs index 85b50a9b21..273e8abcd7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs @@ -9,10 +9,12 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; +using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Select; using osu.Game.Tests.Resources; @@ -38,7 +40,10 @@ namespace osu.Game.Tests.Visual.Editing AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); - AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded); + AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor + && editor.IsLoaded + && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true + && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); AddStep("test gameplay", () => { var testGameplayButton = this.ChildrenOfType().Single(); From da61d0547ffb39614a6c7b93610ac26c77e1fc67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 15:24:25 +0900 Subject: [PATCH 220/803] Include archive name in import log output --- osu.Game/Database/RealmArchiveModelImporter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index a3349d9918..76f6db1384 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -296,7 +296,8 @@ namespace osu.Game.Database try { - LogForModel(item, @"Beginning import..."); + // Log output here will be missing a valid hash in non-batch imports. + LogForModel(item, $@"Beginning import from {archive?.Name ?? "unknown"}..."); // TODO: do we want to make the transaction this local? not 100% sure, will need further investigation. using (var transaction = realm.BeginWrite()) From 13c8d330094370093b33f724d84f4a914761e528 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 15:29:01 +0900 Subject: [PATCH 221/803] Fix second case of empty beatmaps being reported to sentry as errors --- osu.Game/Beatmaps/BeatmapImporter.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 237088036c..d89541844b 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -18,7 +18,6 @@ using osu.Game.Database; using osu.Game.Extensions; using osu.Game.IO; using osu.Game.IO.Archives; -using osu.Game.Models; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Skinning; @@ -49,7 +48,7 @@ namespace osu.Game.Beatmaps protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) { if (archive != null) - beatmapSet.Beatmaps.AddRange(createBeatmapDifficulties(beatmapSet.Files, realm)); + beatmapSet.Beatmaps.AddRange(createBeatmapDifficulties(beatmapSet, realm)); foreach (BeatmapInfo b in beatmapSet.Beatmaps) { @@ -200,23 +199,32 @@ namespace osu.Game.Beatmaps /// /// Create all required s for the provided archive. /// - private List createBeatmapDifficulties(IList files, Realm realm) + private List createBeatmapDifficulties(BeatmapSetInfo beatmapSet, Realm realm) { var beatmaps = new List(); - foreach (var file in files.Where(f => f.Filename.EndsWith(".osu", StringComparison.OrdinalIgnoreCase))) + foreach (var file in beatmapSet.Files.Where(f => f.Filename.EndsWith(".osu", StringComparison.OrdinalIgnoreCase))) { using (var memoryStream = new MemoryStream(Files.Store.Get(file.File.GetStoragePath()))) // we need a memory stream so we can seek { IBeatmap decoded; + using (var lineReader = new LineBufferedReader(memoryStream, true)) + { + if (lineReader.PeekLine() == null) + { + LogForModel(beatmapSet, $"No content found in beatmap file {file.Filename}."); + continue; + } + decoded = Decoder.GetDecoder(lineReader).Decode(lineReader); + } string hash = memoryStream.ComputeSHA2Hash(); if (beatmaps.Any(b => b.Hash == hash)) { - Logger.Log($"Skipping import of {file.Filename} due to duplicate file content.", LoggingTarget.Database); + LogForModel(beatmapSet, $"Skipping import of {file.Filename} due to duplicate file content."); continue; } @@ -227,7 +235,7 @@ namespace osu.Game.Beatmaps if (ruleset?.Available != true) { - Logger.Log($"Skipping import of {file.Filename} due to missing local ruleset {decodedInfo.Ruleset.OnlineID}.", LoggingTarget.Database); + LogForModel(beatmapSet, $"Skipping import of {file.Filename} due to missing local ruleset {decodedInfo.Ruleset.OnlineID}."); continue; } From 01487a11859af1cd36719fe250d34dff919c66cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 15:52:45 +0900 Subject: [PATCH 222/803] Add assertion on realm re-fetch being not-null --- osu.Game/Database/ModelManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Database/ModelManager.cs b/osu.Game/Database/ModelManager.cs index 9603412178..4224c92f2c 100644 --- a/osu.Game/Database/ModelManager.cs +++ b/osu.Game/Database/ModelManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using osu.Framework.Platform; @@ -46,6 +47,7 @@ namespace osu.Game.Database Realm.Realm.Write(realm => { var managed = realm.Find(item.ID); + Debug.Assert(managed != null); operation(managed); item.Files.Clear(); From b70b365411ab9c820da30829b6dbcc5c42c3b15a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 15:53:05 +0900 Subject: [PATCH 223/803] Ensure files are cleaned up even on test step failure in `TestAddAudioTrack` --- .../Editing/TestSceneEditorBeatmapCreation.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index a204fc5686..fcbf63da79 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -95,18 +95,23 @@ namespace osu.Game.Tests.Visual.Editing string extractedFolder = $"{temp}_extracted"; Directory.CreateDirectory(extractedFolder); - using (var zip = ZipArchive.Open(temp)) - zip.WriteToDirectory(extractedFolder); + try + { + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); - bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"))); + bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"))); - File.Delete(temp); - Directory.Delete(extractedFolder, true); + // ensure audio file is copied to beatmap as "audio.mp3" rather than original filename. + Assert.That(Beatmap.Value.Metadata.AudioFile == "audio.mp3"); - // ensure audio file is copied to beatmap as "audio.mp3" rather than original filename. - Assert.That(Beatmap.Value.Metadata.AudioFile == "audio.mp3"); - - return success; + return success; + } + finally + { + File.Delete(temp); + Directory.Delete(extractedFolder, true); + } }); AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000); From 10d1bbb1316f427598d2cd714ebc7b645061e030 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 15:58:51 +0900 Subject: [PATCH 224/803] Log global working beatmap updates in `OsuGameBase` instead of `OsuGame` --- osu.Game/OsuGame.cs | 1 - osu.Game/OsuGameBase.cs | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index f51a18b36f..7724241d5a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -612,7 +612,6 @@ namespace osu.Game { beatmap.OldValue?.CancelAsyncLoad(); beatmap.NewValue?.BeginAsyncLoad(); - Logger.Log($"Game-wide working beatmap updated to {beatmap.NewValue}"); } private void modsChanged(ValueChangedEvent> mods) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7d39aad226..57369419d2 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -490,10 +490,12 @@ namespace osu.Game } } - private void onBeatmapChanged(ValueChangedEvent valueChangedEvent) + private void onBeatmapChanged(ValueChangedEvent beatmap) { if (IsLoaded && !ThreadSafety.IsUpdateThread) throw new InvalidOperationException("Global beatmap bindable must be changed from update thread."); + + Logger.Log($"Game-wide working beatmap updated to {beatmap.NewValue}"); } private void onRulesetChanged(ValueChangedEvent r) From f6a6538e9698f402ce612b8abecef67ec533aac5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 27 Jun 2022 15:01:53 +0900 Subject: [PATCH 225/803] Add more properties to IBeatmapOnlineInfo --- osu.Game/Beatmaps/IBeatmapOnlineInfo.cs | 33 ++++++++++++++++--- .../API/Requests/Responses/APIBeatmap.cs | 3 ++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs b/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs index 160b7cf0ae..e1634e7d24 100644 --- a/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs @@ -13,25 +13,50 @@ namespace osu.Game.Beatmaps /// int? MaxCombo { get; } + /// + /// The approach rate. + /// + float ApproachRate { get; } + + /// + /// The circle size. + /// + float CircleSize { get; } + + /// + /// The drain rate. + /// + float DrainRate { get; } + + /// + /// The overall difficulty. + /// + float OverallDifficulty { get; } + /// /// The amount of circles in this beatmap. /// - public int CircleCount { get; } + int CircleCount { get; } /// /// The amount of sliders in this beatmap. /// - public int SliderCount { get; } + int SliderCount { get; } + + /// + /// The amount of spinners in tihs beatmap. + /// + int SpinnerCount { get; } /// /// The amount of plays this beatmap has. /// - public int PlayCount { get; } + int PlayCount { get; } /// /// The amount of passes this beatmap has. /// - public int PassCount { get; } + int PassCount { get; } APIFailTimes? FailTimes { get; } } diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index 258708de2b..735fde333d 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -69,6 +69,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"count_sliders")] public int SliderCount { get; set; } + [JsonProperty(@"count_spinners")] + public int SpinnerCount { get; set; } + [JsonProperty(@"version")] public string DifficultyName { get; set; } = string.Empty; From 0579780bb8c38e25675755a4bc5cf045c2e1e7aa Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 27 Jun 2022 15:05:49 +0900 Subject: [PATCH 226/803] Add IBeatmapOnlineInfo parameter and use to extract more data --- .../Difficulty/CatchDifficultyAttributes.cs | 5 +++-- .../Difficulty/ManiaDifficultyAttributes.cs | 5 +++-- .../Difficulty/OsuDifficultyAttributes.cs | 10 ++++++++-- .../Difficulty/TaikoDifficultyAttributes.cs | 5 +++-- osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 4 +++- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs index 9951d736c3..c2f5aa3629 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Newtonsoft.Json; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; namespace osu.Game.Rulesets.Catch.Difficulty @@ -31,9 +32,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty yield return (ATTRIB_ID_MAX_COMBO, MaxCombo); } - public override void FromDatabaseAttributes(IReadOnlyDictionary values) + public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) { - base.FromDatabaseAttributes(values); + base.FromDatabaseAttributes(values, onlineInfo); StarRating = values[ATTRIB_ID_AIM]; ApproachRate = values[ATTRIB_ID_APPROACH_RATE]; diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index 8a5161be79..023c45fbcf 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Newtonsoft.Json; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; namespace osu.Game.Rulesets.Mania.Difficulty @@ -37,9 +38,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier); } - public override void FromDatabaseAttributes(IReadOnlyDictionary values) + public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) { - base.FromDatabaseAttributes(values); + base.FromDatabaseAttributes(values, onlineInfo); MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO]; StarRating = values[ATTRIB_ID_DIFFICULTY]; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 19217015c1..abab44b96a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using Newtonsoft.Json; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; @@ -96,9 +97,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor); } - public override void FromDatabaseAttributes(IReadOnlyDictionary values) + public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) { - base.FromDatabaseAttributes(values); + base.FromDatabaseAttributes(values, onlineInfo); AimDifficulty = values[ATTRIB_ID_AIM]; SpeedDifficulty = values[ATTRIB_ID_SPEED]; @@ -108,6 +109,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty StarRating = values[ATTRIB_ID_DIFFICULTY]; FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT); SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR]; + + DrainRate = onlineInfo.DrainRate; + HitCircleCount = onlineInfo.CircleCount; + SliderCount = onlineInfo.SliderCount; + SpinnerCount = onlineInfo.SpinnerCount; } #region Newtonsoft.Json implicit ShouldSerialize() methods diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index 6c617a22a4..eec59afc5b 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Newtonsoft.Json; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; namespace osu.Game.Rulesets.Taiko.Difficulty @@ -57,9 +58,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow); } - public override void FromDatabaseAttributes(IReadOnlyDictionary values) + public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) { - base.FromDatabaseAttributes(values); + base.FromDatabaseAttributes(values, onlineInfo); MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO]; StarRating = values[ATTRIB_ID_DIFFICULTY]; diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index e0844a5c8a..1f08790408 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Difficulty @@ -74,7 +75,8 @@ namespace osu.Game.Rulesets.Difficulty /// Reads osu-web database attribute mappings into this object. /// /// The attribute mappings. - public virtual void FromDatabaseAttributes(IReadOnlyDictionary values) + /// The where more information about the beatmap may be extracted from (such as AR/CS/OD/etc). + public virtual void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) { } } From 0fd2c010e5f8de3ec1ba777925d426dfcb63b55e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 27 Jun 2022 15:06:42 +0900 Subject: [PATCH 227/803] Remove NRT disables from attributes classes --- .../Difficulty/CatchDifficultyAttributes.cs | 2 -- .../Difficulty/ManiaDifficultyAttributes.cs | 2 -- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs | 2 -- .../Difficulty/TaikoDifficultyAttributes.cs | 2 -- osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 5 ++--- 5 files changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs index c2f5aa3629..2d01153f98 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using Newtonsoft.Json; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index 023c45fbcf..680732f26e 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using Newtonsoft.Json; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index abab44b96a..4b06502e32 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index eec59afc5b..380ab4a4fc 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using Newtonsoft.Json; using osu.Game.Beatmaps; diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index 1f08790408..b0e83d6abe 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -1,8 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; @@ -31,7 +30,7 @@ namespace osu.Game.Rulesets.Difficulty /// /// The mods which were applied to the beatmap. /// - public Mod[] Mods { get; set; } + public Mod[] Mods { get; set; } = Array.Empty(); /// /// The combined star rating of all skills. From 5a7d339cc871aeacaf1f2623f289eb2b599e7253 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 16:19:31 +0900 Subject: [PATCH 228/803] Centralise and harden editor-ready-for-use check Not only does this combine the check into one location, but it also adds a check on the global `WorkingBeatmap` being updated, which is the only way I can see the following failure happening: ```csharp 05:19:07 osu.Game.Tests: osu.Game.Tests.Visual.Editing.TestSceneEditorBeatmapCreation.TestAddAudioTrack 05:19:07 Failed TestAddAudioTrack [161 ms] 05:19:07 Error Message: 05:19:07 TearDown : System.NullReferenceException : Object reference not set to an instance of an object. 05:19:07 Stack Trace: 05:19:07 --TearDown 05:19:07 at osu.Game.Database.ModelManager`1.<>c__DisplayClass7_0.b__0(TModel managed) in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Database\ModelManager.cs:line 36 05:19:07 at osu.Game.Database.ModelManager`1.<>c__DisplayClass8_0.b__0(Realm realm) in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Database\ModelManager.cs:line 49 05:19:07 at osu.Game.Database.RealmExtensions.Write(Realm realm, Action`1 function) in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Database\RealmExtensions.cs:line 14 05:19:07 at osu.Game.Database.ModelManager`1.performFileOperation(TModel item, Action`1 operation) in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Database\ModelManager.cs:line 46 05:19:07 at osu.Game.Database.ModelManager`1.AddFile(TModel item, Stream contents, String filename) in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Database\ModelManager.cs:line 36 05:19:07 at osu.Game.Screens.Edit.Setup.ResourcesSection.ChangeAudioTrack(FileInfo source) in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Screens\Edit\Setup\ResourcesSection.cs:line 115 05:19:07 at osu.Game.Tests.Visual.Editing.TestSceneEditorBeatmapCreation.b__13_0() in C:\BuildAgent\work\ecd860037212ac52\osu.Game.Tests\Visual\Editing\TestSceneEditorBeatmapCreation.cs:line 101 05:19:07 at osu.Framework.Testing.Drawables.Steps.AssertButton.checkAssert() 05:19:07 at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered) 05:19:07 at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition) ``` --- .../Editor/TestSceneSliderVelocityAdjust.cs | 9 ++---- .../Editing/TestSceneEditorBeatmapCreation.cs | 2 -- .../Editing/TestSceneEditorNavigation.cs | 7 +---- osu.Game/Screens/Edit/Editor.cs | 29 +++++++++++++++++++ osu.Game/Tests/Visual/EditorTestScene.cs | 8 ++--- 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs index 3d9fe37e0f..ef9e332253 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs @@ -10,7 +10,6 @@ using osu.Framework.Input; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit; @@ -41,10 +40,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); - private bool editorComponentsReady => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true - && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true - && editor?.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; - [TestCase(true)] [TestCase(false)] public void TestVelocityChangeSavesCorrectly(bool adjustVelocity) @@ -52,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor double? velocity = null; AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader())); - AddUntilStep("wait for editor load", () => editorComponentsReady); + AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true); AddStep("seek to first control point", () => editorClock.Seek(editorBeatmap.ControlPointInfo.TimingPoints.First().Time)); AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3)); @@ -91,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("exit", () => InputManager.Key(Key.Escape)); AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader())); - AddUntilStep("wait for editor load", () => editorComponentsReady); + AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true); AddStep("seek to slider", () => editorClock.Seek(slider.StartTime)); AddAssert("slider has correct velocity", () => slider.Velocity == velocity); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index fcbf63da79..b711d55e15 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -36,8 +36,6 @@ namespace osu.Game.Tests.Visual.Editing { protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); - protected override bool EditorComponentsReady => Editor.ChildrenOfType().SingleOrDefault()?.IsLoaded == true; - protected override bool IsolateSavingFromDatabase => false; [Resolved] diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs index 273e8abcd7..62ca97d5e0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs @@ -9,12 +9,10 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; -using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Select; using osu.Game.Tests.Resources; @@ -40,10 +38,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); - AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor - && editor.IsLoaded - && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true - && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); AddStep("test gameplay", () => { var testGameplayButton = this.ChildrenOfType().Single(); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 20bdf75b7d..9dbb5f4cfe 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -21,6 +21,7 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -39,6 +40,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose; +using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.Design; using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Edit.Setup; @@ -97,6 +99,32 @@ namespace osu.Game.Screens.Edit public IBindable SamplePlaybackDisabled => samplePlaybackDisabled; + /// + /// Ensure all asynchronously loading pieces of the editor are in a good state. + /// This exists here for convenience for tests, not for actual use. + /// Eventually we'd probably want a better way to signal this. + /// + public bool ReadyForUse + { + get + { + if (!workingBeatmapUpdated) + return false; + + var loadedScreen = screenContainer?.Children.SingleOrDefault(s => s.IsLoaded); + + if (loadedScreen == null) + return false; + + if (loadedScreen is EditorScreenWithTimeline) + return loadedScreen.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; + + return true; + } + } + + private bool workingBeatmapUpdated; + private readonly Bindable samplePlaybackDisabled = new Bindable(); private bool canSave; @@ -219,6 +247,7 @@ namespace osu.Game.Screens.Edit // this assumes that nothing during the rest of this load() method is accessing Beatmap.Value (loadableBeatmap should be preferred). // generally this is quite safe, as the actual load of editor content comes after menuBar.Mode.ValueChanged is fired in its own LoadComplete. Beatmap.Value = loadableBeatmap; + workingBeatmapUpdated = true; }); OsuMenuItem undoMenuItem; diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 3f5fee5768..31036247ab 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -17,9 +17,7 @@ using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; -using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Menu; using osu.Game.Skinning; @@ -58,15 +56,13 @@ namespace osu.Game.Tests.Visual Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); } - protected virtual bool EditorComponentsReady => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true - && Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; - public override void SetUpSteps() { base.SetUpSteps(); AddStep("load editor", LoadEditor); - AddUntilStep("wait for editor to load", () => EditorComponentsReady); + AddUntilStep("wait for editor to load", () => Editor?.ReadyForUse == true); + AddUntilStep("wait for beatmap updated", () => !Beatmap.IsDefault); } protected virtual void LoadEditor() From 13dcaf82ada9d3e1a96322c49a194da0652b525d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 16:50:07 +0900 Subject: [PATCH 229/803] Fix chat tests failing 1/10000 runs https://github.com/ppy/osu/blob/31a447fda0532fcf15f8c8251ca890c533386492/osu.Game/Online/Chat/ChannelManager.cs#L412-L414 Sigh. --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 4 ++-- osu.Game/Online/API/DummyAPIAccess.cs | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 1fb0195368..73bad6e631 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -549,7 +549,7 @@ namespace osu.Game.Tests.Visual.Online private Channel createPrivateChannel() { - int id = RNG.Next(0, 10000); + int id = RNG.Next(0, DummyAPIAccess.DUMMY_USER_ID - 1); return new Channel(new APIUser { Id = id, @@ -559,7 +559,7 @@ namespace osu.Game.Tests.Visual.Online private Channel createAnnounceChannel() { - int id = RNG.Next(0, 10000); + int id = RNG.Next(0, DummyAPIAccess.DUMMY_USER_ID - 1); return new Channel { Name = $"Announce {id}", diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 245760a00a..07d544260e 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -15,10 +15,12 @@ namespace osu.Game.Online.API { public class DummyAPIAccess : Component, IAPIProvider { + public const int DUMMY_USER_ID = 1001; + public Bindable LocalUser { get; } = new Bindable(new APIUser { Username = @"Dummy", - Id = 1001, + Id = DUMMY_USER_ID, }); public BindableList Friends { get; } = new BindableList(); From 8c3c1f095eaf966c3cc08691531e78582ae659ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 17:04:28 +0900 Subject: [PATCH 230/803] Update rollback test expectations I can't find a better way to do this. It's very hard to trigger an actual failure in the import process these days. For now I've just made this work with the new assumptions. May be worth removing the test in the future if this ever backfires. --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index be11476c73..8c28f8cedd 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; @@ -607,6 +608,12 @@ namespace osu.Game.Tests.Database using (var outStream = File.Open(brokenTempFilename, FileMode.CreateNew)) using (var zip = ZipArchive.Open(brokenOsz)) { + foreach (var entry in zip.Entries.ToArray()) + { + if (entry.Key.EndsWith(".osu", StringComparison.InvariantCulture)) + zip.RemoveEntry(entry); + } + zip.AddEntry("broken.osu", brokenOsu, false); zip.SaveTo(outStream, CompressionType.Deflate); } @@ -627,7 +634,7 @@ namespace osu.Game.Tests.Database checkSingleReferencedFileCount(realm.Realm, 18); - Assert.AreEqual(1, loggedExceptionCount); + Assert.AreEqual(0, loggedExceptionCount); File.Delete(brokenTempFilename); }); From 401d9c1baed7bc9d60a88d286566a2e8537c1aff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 17:13:46 +0900 Subject: [PATCH 231/803] Fix `TestStoryboardSkipOutro` occasionally failing due to strict timings --- osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 002e35f742..0ba8583b56 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -52,10 +52,11 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestStoryboardSkipOutro() { + AddStep("set storyboard duration to long", () => currentStoryboardDuration = 200000); CreateTest(null); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space)); - AddAssert("player is no longer current screen", () => !Player.IsCurrentScreen()); + AddUntilStep("player is no longer current screen", () => !Player.IsCurrentScreen()); AddUntilStep("wait for score shown", () => Player.IsScoreShown); } From f6a61472c413357bd46add03f2924d0662929dfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 17:59:40 +0900 Subject: [PATCH 232/803] Fix occasional failure in realm test proceedings due to incorrect `Debug.Assert` After a `BlockAllOperations`, the restoration of the `updateRealm` instance is not instance. It is posted to a `SynchronizationContext`. The assertion which has been removed in this commit was assuming it would always be an immediate operation. To ensure this works as expected, I've tracked the initialised state via a new `bool`. ```csharp System.TimeoutException : Attempting to block for migration took too long. 1) Host threw exception System.AggregateException: One or more errors occurred. (: ) ---> NUnit.Framework.AssertionException: : at osu.Framework.Logging.ThrowingTraceListener.Fail(String message1, String message2) at System.Diagnostics.TraceInternal.Fail(String message, String detailMessage) at System.Diagnostics.TraceInternal.TraceProvider.Fail(String message, String detailMessage) at System.Diagnostics.Debug.Fail(String message, String detailMessage) at osu.Game.Database.RealmAccess.BlockAllOperations() in /opt/buildagent/work/ecd860037212ac52/osu.Game/Database/RealmAccess.cs:line 813 at osu.Game.OsuGameBase.<>c__DisplayClass108_1.b__0() in /opt/buildagent/work/ecd860037212ac52/osu.Game/OsuGameBase.cs:line 449 at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() at osu.Framework.Threading.Scheduler.Update() at osu.Framework.Graphics.Drawable.UpdateSubTree() at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() ``` https://teamcity.ppy.sh/buildConfiguration/Osu_Build/322?hideProblemsFromDependencies=false&hideTestsFromDependencies=false&expandBuildTestsSection=true --- osu.Game/Database/RealmAccess.cs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 3ea7a14826..089a783177 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -102,6 +102,12 @@ namespace osu.Game.Database private Realm? updateRealm; + /// + /// Tracks whether a realm was ever fetched from this instance. + /// After a fetch occurs, blocking operations will be guaranteed to restore any subscriptions. + /// + private bool hasInitialisedOnce; + private bool isSendingNotificationResetEvents; public Realm Realm => ensureUpdateRealm(); @@ -121,6 +127,7 @@ namespace osu.Game.Database if (updateRealm == null) { updateRealm = getRealmInstance(); + hasInitialisedOnce = true; Logger.Log(@$"Opened realm ""{updateRealm.Config.DatabasePath}"" at version {updateRealm.Config.SchemaVersion}"); @@ -806,13 +813,7 @@ namespace osu.Game.Database lock (realmLock) { - if (updateRealm == null) - { - // null realm means the update thread has not yet retrieved its instance. - // we don't need to worry about reviving the update instance in this case, so don't bother with the SynchronizationContext. - Debug.Assert(!ThreadSafety.IsUpdateThread); - } - else + if (hasInitialisedOnce) { if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); @@ -827,12 +828,12 @@ namespace osu.Game.Database action.Value?.Dispose(); customSubscriptionsResetMap[action.Key] = null; } + + updateRealm?.Dispose(); + updateRealm = null; } Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); - - updateRealm?.Dispose(); - updateRealm = null; } const int sleep_length = 200; From 5af1106fa5f0e9f49cadbbca052c3ee5d65d2df5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 18:04:52 +0900 Subject: [PATCH 233/803] Remove unused using statement --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 8c28f8cedd..6f5a79c9da 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; From 0ba29b6fa64eac5a908903fbbfbe0270662879a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 18:28:00 +0900 Subject: [PATCH 234/803] Use `currentScreen` instead to make sure the screen we care about is loaded --- osu.Game/Screens/Edit/Editor.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 9dbb5f4cfe..81ae08aea1 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -111,13 +111,11 @@ namespace osu.Game.Screens.Edit if (!workingBeatmapUpdated) return false; - var loadedScreen = screenContainer?.Children.SingleOrDefault(s => s.IsLoaded); - - if (loadedScreen == null) + if (currentScreen?.IsLoaded != true) return false; - if (loadedScreen is EditorScreenWithTimeline) - return loadedScreen.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; + if (currentScreen is EditorScreenWithTimeline) + return currentScreen.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; return true; } From 50c1c066deff819c782f77e6bcd1814ed8b3b2a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 18:41:27 +0900 Subject: [PATCH 235/803] Add various logging of global `WorkingBeatmap` state changes --- osu.Game/OsuGame.cs | 6 ++++++ osu.Game/Overlays/MusicController.cs | 3 +++ osu.Game/Screens/Menu/MainMenu.cs | 3 +++ osu.Game/Screens/Select/SongSelect.cs | 7 +++++-- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index f51a18b36f..1458af1ea1 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -486,6 +486,7 @@ namespace osu.Game /// public void PresentBeatmap(IBeatmapSetInfo beatmap, Predicate difficultyCriteria = null) { + Logger.Log($"Beginning {nameof(PresentBeatmap)} with beatmap {beatmap}"); Live databasedSet = null; if (beatmap.OnlineID > 0) @@ -522,6 +523,7 @@ namespace osu.Game } else { + Logger.Log($"Completing {nameof(PresentBeatmap)} with beatmap {beatmap} ruleset {selection.Ruleset}"); Ruleset.Value = selection.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection); } @@ -534,6 +536,8 @@ namespace osu.Game /// public void PresentScore(IScoreInfo score, ScorePresentType presentType = ScorePresentType.Results) { + Logger.Log($"Beginning {nameof(PresentScore)} with score {score}"); + // The given ScoreInfo may have missing properties if it was retrieved from online data. Re-retrieve it from the database // to ensure all the required data for presenting a replay are present. ScoreInfo databasedScoreInfo = null; @@ -568,6 +572,8 @@ namespace osu.Game PerformFromScreen(screen => { + Logger.Log($"{nameof(PresentScore)} updating beatmap ({databasedBeatmap}) and ruleset ({databasedScore.ScoreInfo.Ruleset} to match score"); + Ruleset.Value = databasedScore.ScoreInfo.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index bce7e9b797..1c2c9f2b48 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -14,6 +14,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Database; @@ -106,10 +107,12 @@ namespace osu.Game.Overlays if (beatmap.Disabled) return; + Logger.Log($"{nameof(MusicController)} skipping to next track to {nameof(EnsurePlayingSomething)}"); NextTrack(); } else if (!IsPlaying) { + Logger.Log($"{nameof(MusicController)} starting playback to {nameof(EnsurePlayingSomething)}"); Play(); } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 6a74a6bd6e..ba63902b46 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Beatmaps; @@ -302,6 +303,8 @@ namespace osu.Game.Screens.Menu public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset) { + Logger.Log($"{nameof(MainMenu)} completing {nameof(PresentBeatmap)} with beatmap {beatmap} ruleset {ruleset}"); + Beatmap.Value = beatmap; Ruleset.Value = ruleset; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 41fb55a856..b89acfa570 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -494,7 +494,7 @@ namespace osu.Game.Screens.Select // clear pending task immediately to track any potential nested debounce operation. selectionChangedDebounce = null; - Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ShortName ?? "null"}"); + Logger.Log($"Song select updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ShortName ?? "null"}"); if (transferRulesetValue()) { @@ -519,7 +519,7 @@ namespace osu.Game.Screens.Select // In these cases, the other component has already loaded the beatmap, so we don't need to do so again. if (!EqualityComparer.Default.Equals(beatmap, Beatmap.Value.BeatmapInfo)) { - Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\""); + Logger.Log($"Song select changing beatmap from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap?.ToString() ?? "null"}\""); Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap); } @@ -737,7 +737,10 @@ namespace osu.Game.Screens.Select bool isNewTrack = !lastTrack.TryGetTarget(out var last) || last != track; if (!track.IsRunning && (music.UserPauseRequested != true || isNewTrack)) + { + Logger.Log($"Song select decided to {nameof(ensurePlayingSelected)}"); music.Play(true); + } lastTrack.SetTarget(track); } From 1fc4fa68204c5cb3e9b1a02509ab5afcebdc4638 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 19:20:15 +0900 Subject: [PATCH 236/803] Remove unnecessary `Task.Run` workaround in tests --- .../Database/RealmSubscriptionRegistrationTests.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index b8ce036da1..c74341b5c9 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; @@ -84,11 +83,7 @@ namespace osu.Game.Tests.Database realm.Run(r => r.Refresh()); - // Without forcing the write onto its own thread, realm will internally run the operation synchronously, which can cause a deadlock with `WaitSafely`. - Task.Run(async () => - { - await realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); - }).WaitSafely(); + realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())).WaitSafely(); realm.Run(r => r.Refresh()); From c39c99bd43ce32f1f58ac274703e9dd3f542bcf6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 19:20:28 +0900 Subject: [PATCH 237/803] Ensure all async writes are completed before realm is disposed --- osu.Game/Database/RealmAccess.cs | 42 +++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 089a783177..2618fe373e 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -388,26 +388,43 @@ namespace osu.Game.Database } } + private readonly CountdownEvent pendingAsyncWrites = new CountdownEvent(0); + /// /// Write changes to realm asynchronously, guaranteeing order of execution. /// /// The work to run. public Task WriteAsync(Action action) { - // Regardless of calling Realm.GetInstance or Realm.GetInstanceAsync, there is a blocking overhead on retrieval. - // Adding a forced Task.Run resolves this. + // Required to ensure the write is tracked and accounted for before disposal. + // Can potentially be avoided if we have a need to do so in the future. + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"{nameof(WriteAsync)} must be called from the update thread."); - return Task.Run(async () => + lock (realmLock) { - total_writes_async.Value++; + // CountdownEvent will fail if already at zero. + if (!pendingAsyncWrites.TryAddCount()) + pendingAsyncWrites.Reset(1); - // Not attempting to use Realm.GetInstanceAsync as there's seemingly no benefit to us (for now) and it adds complexity due to locking - // concerns in getRealmInstance(). On a quick check, it looks to be more suited to cases where realm is connecting to an online sync - // server, which we don't use. May want to report upstream or revisit in the future. - using (var realm = getRealmInstance()) - // ReSharper disable once AccessToDisposedClosure (WriteAsync should be marked as [InstantHandle]). - await realm.WriteAsync(() => action(realm)); - }); + // Regardless of calling Realm.GetInstance or Realm.GetInstanceAsync, there is a blocking overhead on retrieval. + // Adding a forced Task.Run resolves this. + var writeTask = Task.Run(async () => + { + total_writes_async.Value++; + + // Not attempting to use Realm.GetInstanceAsync as there's seemingly no benefit to us (for now) and it adds complexity due to locking + // concerns in getRealmInstance(). On a quick check, it looks to be more suited to cases where realm is connecting to an online sync + // server, which we don't use. May want to report upstream or revisit in the future. + using (var realm = getRealmInstance()) + // ReSharper disable once AccessToDisposedClosure (WriteAsync should be marked as [InstantHandle]). + await realm.WriteAsync(() => action(realm)); + + pendingAsyncWrites.Signal(); + }); + + return writeTask; + } } /// @@ -910,6 +927,9 @@ namespace osu.Game.Database public void Dispose() { + if (!pendingAsyncWrites.Wait(10000)) + Logger.Log("Realm took too long waiting on pending async writes", level: LogLevel.Error); + lock (realmLock) { updateRealm?.Dispose(); From 83982d258d96590722da57126219dfc1c920a7fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 19:34:28 +0900 Subject: [PATCH 238/803] Throw immediately if attempting to `WriteAsync` after disposed --- osu.Game/Database/RealmAccess.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 2618fe373e..7a9561fc5e 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -396,6 +396,9 @@ namespace osu.Game.Database /// The work to run. public Task WriteAsync(Action action) { + if (isDisposed) + throw new ObjectDisposedException(nameof(RealmAccess)); + // Required to ensure the write is tracked and accounted for before disposal. // Can potentially be avoided if we have a need to do so in the future. if (!ThreadSafety.IsUpdateThread) From 62038850406fe5cccd1151cca141c1adb802ef1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 19:34:42 +0900 Subject: [PATCH 239/803] Add test coverage of realm async writes --- osu.Game.Tests/Database/GeneralUsageTests.cs | 61 ++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 65f805bafb..db6e97dab2 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -2,11 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Database { @@ -33,6 +36,64 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestAsyncWriteAsync() + { + RunTestWithRealmAsync(async (realm, _) => + { + await realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); + + realm.Run(r => r.Refresh()); + + Assert.That(realm.Run(r => r.All().Count()), Is.EqualTo(1)); + }); + } + + [Test] + public void TestAsyncWrite() + { + RunTestWithRealm((realm, _) => + { + realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())).WaitSafely(); + + realm.Run(r => r.Refresh()); + + Assert.That(realm.Run(r => r.All().Count()), Is.EqualTo(1)); + }); + } + + [Test] + public void TestAsyncWriteAfterDisposal() + { + RunTestWithRealm((realm, _) => + { + realm.Dispose(); + Assert.ThrowsAsync(() => realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo()))); + }); + } + + [Test] + public void TestAsyncWriteBeforeDisposal() + { + ManualResetEventSlim resetEvent = new ManualResetEventSlim(); + + RunTestWithRealm((realm, _) => + { + var writeTask = realm.WriteAsync(r => + { + // ensure that disposal blocks for our execution + Assert.That(resetEvent.Wait(100), Is.False); + + r.Add(TestResources.CreateTestBeatmapSetInfo()); + }); + + realm.Dispose(); + resetEvent.Set(); + + writeTask.WaitSafely(); + }); + } + /// /// Test to ensure that a `CreateContext` call nested inside a subscription doesn't cause any deadlocks /// due to context fetching semaphores. From aa7d54f8b6c31fd7e485721da2de000b4855414e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 20:10:15 +0900 Subject: [PATCH 240/803] Add logging for various cases of `SongSelect.FinaliseSelection` being aborted --- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 4 ++++ osu.Game/Screens/Select/SongSelect.cs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 0f589a8b35..dbd679104e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; @@ -78,7 +79,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override bool SelectItem(PlaylistItem item) { if (operationInProgress.Value) + { + Logger.Log($"{nameof(SelectedItem)} aborted due to {nameof(operationInProgress)}"); return false; + } // If the client is already in a room, update via the client. // Otherwise, update the playlist directly in preparation for it to be submitted to the API on match creation. diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 41fb55a856..4e45abab3f 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -360,7 +360,10 @@ namespace osu.Game.Screens.Select { // This is very important as we have not yet bound to screen-level bindables before the carousel load is completed. if (!Carousel.BeatmapSetsLoaded) + { + Logger.Log($"{nameof(FinaliseSelection)} aborted as carousel beatmaps are not yet loaded"); return; + } if (ruleset != null) Ruleset.Value = ruleset; From 6c6de9ce533d5cd99b38390b396cabdc3ec34e9f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 27 Jun 2022 20:40:02 +0900 Subject: [PATCH 241/803] Fix typo --- osu.Game/Overlays/MusicController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 1c2c9f2b48..4a10f30a7a 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -107,7 +107,7 @@ namespace osu.Game.Overlays if (beatmap.Disabled) return; - Logger.Log($"{nameof(MusicController)} skipping to next track to {nameof(EnsurePlayingSomething)}"); + Logger.Log($"{nameof(MusicController)} skipping next track to {nameof(EnsurePlayingSomething)}"); NextTrack(); } else if (!IsPlaying) From 3e3843dcf9a8a943f730a3e74ca2cd5f44dd20c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 00:32:36 +0900 Subject: [PATCH 242/803] Fix potential crash on `CloseAllOverlays` due to collection mutation --- 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 7f4e4af455..01f0b09a3e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -249,7 +249,7 @@ namespace osu.Game /// Whether the toolbar should also be hidden. public void CloseAllOverlays(bool hideToolbar = true) { - foreach (var overlay in focusedOverlays) + foreach (var overlay in focusedOverlays.ToArray()) overlay.Hide(); if (hideToolbar) Toolbar.Hide(); From b28cfc222b22a417f69f6449f36465c5a20cb385 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 01:19:20 +0900 Subject: [PATCH 243/803] Schedule unregister instead --- osu.Game/OsuGame.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 01f0b09a3e..ef42522f9d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -231,7 +231,7 @@ namespace osu.Game /// /// Unregisters a blocking that was not created by itself. /// - private void unregisterBlockingOverlay(OverlayContainer overlayContainer) + private void unregisterBlockingOverlay(OverlayContainer overlayContainer) => Schedule(() => { externalOverlays.Remove(overlayContainer); @@ -239,7 +239,7 @@ namespace osu.Game focusedOverlays.Remove(focusedOverlayContainer); overlayContainer.Expire(); - } + }); #endregion @@ -249,7 +249,7 @@ namespace osu.Game /// Whether the toolbar should also be hidden. public void CloseAllOverlays(bool hideToolbar = true) { - foreach (var overlay in focusedOverlays.ToArray()) + foreach (var overlay in focusedOverlays) overlay.Hide(); if (hideToolbar) Toolbar.Hide(); From 569fde4b47976898cf1df4cd5965e54af7caa238 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 01:34:24 +0900 Subject: [PATCH 244/803] Add messages to all `InvalidOperationException`s Without this, they can be very non-descript and hard to track down --- osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs | 8 ++++---- osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs | 8 ++++---- .../Visual/SongSelect/TestSceneAdvancedStats.cs | 2 +- osu.Game/Online/API/Requests/Responses/APIScore.cs | 2 +- osu.Game/Online/Rooms/MultiplayerScore.cs | 2 +- .../Profile/Sections/Ranks/DrawableProfileScore.cs | 2 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 +- osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs index a779fae510..14da07bc2d 100644 --- a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs +++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs @@ -83,14 +83,14 @@ namespace osu.Game.Tests.NonVisual public override event Action NewResult { - add => throw new InvalidOperationException(); - remove => throw new InvalidOperationException(); + add => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context"); + remove => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context"); } public override event Action RevertResult { - add => throw new InvalidOperationException(); - remove => throw new InvalidOperationException(); + add => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); + remove => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); } public override Playfield Playfield { get; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 70d7f6a28b..707f807e64 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -273,14 +273,14 @@ namespace osu.Game.Tests.Visual.Gameplay public override event Action NewResult { - add => throw new InvalidOperationException(); - remove => throw new InvalidOperationException(); + add => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context"); + remove => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context"); } public override event Action RevertResult { - add => throw new InvalidOperationException(); - remove => throw new InvalidOperationException(); + add => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); + remove => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); } public override Playfield Playfield { get; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index 77670c38f3..4510fda11d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.SongSelect { AddStep("set beatmap", () => advancedStats.BeatmapInfo = new BeatmapInfo { - Ruleset = rulesets.GetRuleset(3) ?? throw new InvalidOperationException(), + Ruleset = rulesets.GetRuleset(3) ?? throw new InvalidOperationException("osu!mania ruleset not found"), Difficulty = new BeatmapDifficulty { CircleSize = 5, diff --git a/osu.Game/Online/API/Requests/Responses/APIScore.cs b/osu.Game/Online/API/Requests/Responses/APIScore.cs index 1b56362aec..f236607761 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScore.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScore.cs @@ -88,7 +88,7 @@ namespace osu.Game.Online.API.Requests.Responses /// public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) { - var ruleset = rulesets.GetRuleset(RulesetID) ?? throw new InvalidOperationException(); + var ruleset = rulesets.GetRuleset(RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {RulesetID} not found locally"); var rulesetInstance = ruleset.CreateInstance(); diff --git a/osu.Game/Online/Rooms/MultiplayerScore.cs b/osu.Game/Online/Rooms/MultiplayerScore.cs index 13f14bca5b..6f597e5b10 100644 --- a/osu.Game/Online/Rooms/MultiplayerScore.cs +++ b/osu.Game/Online/Rooms/MultiplayerScore.cs @@ -79,7 +79,7 @@ namespace osu.Game.Online.Rooms TotalScore = TotalScore, MaxCombo = MaxCombo, BeatmapInfo = beatmap, - Ruleset = rulesets.GetRuleset(playlistItem.RulesetID) ?? throw new InvalidOperationException(), + Ruleset = rulesets.GetRuleset(playlistItem.RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {playlistItem.RulesetID} not found locally"), Statistics = Statistics, User = User, Accuracy = Accuracy, diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 54a262f6a6..90a357a281 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -136,7 +136,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Spacing = new Vector2(2), Children = Score.Mods.Select(mod => { - var ruleset = rulesets.GetRuleset(Score.RulesetID) ?? throw new InvalidOperationException(); + var ruleset = rulesets.GetRuleset(Score.RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {Score.RulesetID} not found locally"); return new ModIcon(ruleset.CreateInstance().CreateModFromAcronym(mod.Acronym)) { diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 0a6f9974b0..96425e8bc8 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -122,7 +122,7 @@ namespace osu.Game.Screens.Edit public BeatmapInfo BeatmapInfo { get => beatmapInfo; - set => throw new InvalidOperationException(); + set => throw new InvalidOperationException($"Can't set {nameof(BeatmapInfo)} on {nameof(EditorBeatmap)}"); } public BeatmapMetadata Metadata => beatmapInfo.Metadata; diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index fc29c5aac5..db1d94aee2 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Spectator FrameSendAttempts++; if (ShouldFailSendingFrames) - return Task.FromException(new InvalidOperationException()); + return Task.FromException(new InvalidOperationException($"Intentional fail via {ShouldFailSendingFrames}")); return ((ISpectatorClient)this).UserSentFrames(api.LocalUser.Value.Id, bundle); } From 54fe6b7df3ae62e0dc7a61f28ef0e3ff85aee06e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 01:43:09 +0900 Subject: [PATCH 245/803] Fix incorrect string interpolation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index db1d94aee2..2531f3c485 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Spectator FrameSendAttempts++; if (ShouldFailSendingFrames) - return Task.FromException(new InvalidOperationException($"Intentional fail via {ShouldFailSendingFrames}")); + return Task.FromException(new InvalidOperationException($"Intentional fail via {nameof(ShouldFailSendingFrames)}")); return ((ISpectatorClient)this).UserSentFrames(api.LocalUser.Value.Id, bundle); } From 1a0228415d526599dbba42ba8cc08ba767142367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 Jun 2022 22:26:00 +0200 Subject: [PATCH 246/803] Wait for and dismiss notification in editor navigation test --- osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs index 62ca97d5e0..a21ef6f897 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; @@ -35,6 +36,8 @@ namespace osu.Game.Tests.Visual.Editing () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.IsLoaded); + AddUntilStep("wait for completion notification", () => Game.Notifications.ChildrenOfType().Count() == 1); + AddStep("dismiss notifications", () => Game.Notifications.Hide()); AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); From c1075d113fb13b586a216444946057f936c8d86a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 13:58:35 +0900 Subject: [PATCH 247/803] Add logging around current channel changes and join requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tracking down a flaky test (https://teamcity.ppy.sh/buildConfiguration/Osu_Build/553?hideProblemsFromDependencies=false&expandBuildTestsSection=true&hideTestsFromDependencies=false): ```csharp TearDown : System.TimeoutException : "PM Channel 1 displayed" timed out --TearDown at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0() at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered) at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition) --- End of stack trace from previous location --- at osu.Framework.Testing.TestSceneTestRunner.TestRunner.RunTestBlocking(TestScene test) at osu.Game.Tests.Visual.OsuTestScene.OsuTestSceneTestRunner.RunTestBlocking(TestScene test) in /opt/buildagent/work/ecd860037212ac52/osu.Game/Tests/Visual/OsuTestScene.cs:line 503 at osu.Framework.Testing.TestScene.RunTestsFromNUnit() ------- Stdout: ------- [runtime] 2022-06-27 23:18:55 [verbose]: 💨 Class: TestSceneChatOverlay [runtime] 2022-06-27 23:18:55 [verbose]: 🔶 Test: TestKeyboardNextChannel [runtime] 2022-06-27 23:18:55 [verbose]: Chat is now polling every 60000 ms [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #1 Setup request handler [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #2 Add test channels [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #3 Show overlay with channels [runtime] 2022-06-27 23:18:55 [verbose]: Unhandled Request Type: osu.Game.Online.API.Requests.CreateChannelRequest [network] 2022-06-27 23:18:55 [verbose]: Failing request osu.Game.Online.API.Requests.CreateChannelRequest (System.InvalidOperationException: DummyAPIAccess cannot process this request.) [runtime] 2022-06-27 23:18:55 [verbose]: Unhandled Request Type: osu.Game.Online.API.Requests.CreateChannelRequest [network] 2022-06-27 23:18:55 [verbose]: Failing request osu.Game.Online.API.Requests.CreateChannelRequest (System.InvalidOperationException: DummyAPIAccess cannot process this request.) [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #4 Select channel 1 [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #5 Channel 1 is visible [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #6 Press document next keys [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #7 Channel 2 is visible [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #8 Press document next keys [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #9 PM Channel 1 displayed [network] 2022-06-27 23:18:55 [verbose]: Request to https://a.ppy.sh/587 failed with System.Net.WebException: NotFound. [network] 2022-06-27 23:18:55 [verbose]: Request to https://a.ppy.sh/503 failed with System.Net.WebException: NotFound. [runtime] 2022-06-27 23:19:05 [verbose]: 💥 Failed (on attempt 5,550) [runtime] 2022-06-27 23:19:05 [verbose]: ⏳ Currently loading components (0) [runtime] 2022-06-27 23:19:05 [verbose]: 🧵 Task schedulers [runtime] 2022-06-27 23:19:05 [verbose]: LoadComponentsAsync (standard) concurrency:4 running:0 pending:0 [runtime] 2022-06-27 23:19:05 [verbose]: LoadComponentsAsync (long load) concurrency:4 running:0 pending:0 [runtime] 2022-06-27 23:19:05 [verbose]: 🎱 Thread pool [runtime] 2022-06-27 23:19:05 [verbose]: worker: min 1 max 32,767 available 32,766 [runtime] 2022-06-27 23:19:05 [verbose]: completion: min 1 max 1,000 available 1,000 [runtime] 2022-06-27 23:19:05 [debug]: Focus on "ChatTextBox" no longer valid as a result of unfocusIfNoLongerValid. [runtime] 2022-06-27 23:19:05 [debug]: Focus changed from ChatTextBox to nothing. ``` This kind of logging should be helpful: ```csharp [runtime] 2022-06-28 04:59:57 [verbose]: 🔸 Step #5 Channel 1 is visible [runtime] 2022-06-28 04:59:57 [verbose]: 🔸 Step #6 Press document next keys [runtime] 2022-06-28 04:59:57 [verbose]: Current channel changed to #channel-2 [runtime] 2022-06-28 04:59:57 [debug]: Pressed (DocumentNext) handled by TestSceneChatOverlay+TestChatOverlay. [runtime] 2022-06-28 04:59:57 [debug]: KeyDownEvent(PageDown, False) handled by ManualInputManager+LocalPlatformActionContainer. [runtime] 2022-06-28 04:59:57 [verbose]: 🔸 Step #7 Channel 2 is visible [runtime] 2022-06-28 04:59:57 [verbose]: 🔸 Step #8 Press document next keys [runtime] 2022-06-28 04:59:57 [verbose]: Current channel changed to test user 685 [runtime] 2022-06-28 04:59:57 [debug]: Pressed (DocumentNext) handled by TestSceneChatOverlay+TestChatOverlay. [runtime] 2022-06-28 04:59:57 [debug]: KeyDownEvent(PageDown, False) handled by ManualInputManager+LocalPlatformActionContainer. [runtime] 2022-06-28 04:59:57 [verbose]: 🔸 Step #9 PM Channel 1 displayed [runtime] 2022-06-28 04:59:57 [verbose]: 🔸 Step #10 Press document next keys [runtime] 2022-06-28 04:59:57 [verbose]: Current channel changed to test user 218 ``` --- .../Visual/Online/TestSceneChatOverlay.cs | 2 +- osu.Game/Online/Chat/ChannelManager.cs | 30 +++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 73bad6e631..ff2817c439 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -472,8 +472,8 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); - waitForChannel1Visible(); + AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); waitForChannel2Visible(); diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 48dfaadfa5..ec84b0643d 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -133,12 +133,14 @@ namespace osu.Game.Online.Chat ?? JoinChannel(new Channel(user)); } - private void currentChannelChanged(ValueChangedEvent e) + private void currentChannelChanged(ValueChangedEvent channel) { - bool isSelectorChannel = e.NewValue is ChannelListing.ChannelListingChannel; + bool isSelectorChannel = channel.NewValue is ChannelListing.ChannelListingChannel; if (!isSelectorChannel) - JoinChannel(e.NewValue); + JoinChannel(channel.NewValue); + + Logger.Log($"Current channel changed to {channel.NewValue}"); } /// @@ -447,9 +449,17 @@ namespace osu.Game.Online.Chat return channel; case ChannelType.PM: + Logger.Log($"Attempting to join PM channel {channel}"); + var createRequest = new CreateChannelRequest(channel); + createRequest.Failure += e => + { + Logger.Log($"Failed to join PM channel {channel} ({e.Message})"); + }; createRequest.Success += resChannel => { + Logger.Log($"Joined PM channel {channel} ({resChannel.ChannelID})"); + if (resChannel.ChannelID.HasValue) { channel.Id = resChannel.ChannelID.Value; @@ -463,9 +473,19 @@ namespace osu.Game.Online.Chat break; default: + Logger.Log($"Attempting to join public channel {channel}"); + var req = new JoinChannelRequest(channel); - req.Success += () => joinChannel(channel, fetchInitialMessages); - req.Failure += _ => LeaveChannel(channel); + req.Success += () => + { + Logger.Log($"Joined public channel {channel}"); + joinChannel(channel, fetchInitialMessages); + }; + req.Failure += e => + { + Logger.Log($"Failed to join public channel {channel} ({e.Message})"); + LeaveChannel(channel); + }; api.Queue(req); return channel; } From a2701a32055c752fb2ce6c8a937623b69da583b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 14:58:33 +0900 Subject: [PATCH 248/803] Increase leaniences on `TestSceneSpectatorPlayback.TestWithSendFailure` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not really sure how to improve this further, but should help with cases like this: ```csharp [runtime] 2022-06-28 05:32:06 [verbose]: 💨 Class: TestSceneSpectatorPlayback [runtime] 2022-06-28 05:32:06 [verbose]: 🔶 Test: TestWithSendFailure [runtime] 2022-06-28 05:32:06 [verbose]: 🔸 Step #1 Setup containers [runtime] 2022-06-28 05:32:06 [verbose]: Received 1 new frames (total 1 of 2) [runtime] 2022-06-28 05:32:06 [verbose]: 🔸 Step #2 received frames [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 7 of 8) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 13 of 19) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 19 of 29) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 25 of 44) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 31 of 45) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 37 of 59) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 43 of 67) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 49 of 125) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 55 of 126) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 61 of 127) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 67 of 128) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 73 of 129) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 79 of 130) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 85 of 131) [runtime] 2022-06-28 05:32:06 [verbose]: ✔️ 22 repetitions [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 91 of 132) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 97 of 133) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 103 of 134) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 109 of 135) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 115 of 136) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 121 of 137) [runtime] 2022-06-28 05:32:06 [verbose]: 🔸 Step #3 start failing sends [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 127 of 138) [runtime] 2022-06-28 05:32:06 [verbose]: 🔸 Step #4 wait for send attempts [runtime] 2022-06-28 05:32:06 [verbose]: 🔸 Step #5 frames did not increase [runtime] 2022-06-28 05:32:06 [verbose]: 💥 Failed [runtime] 2022-06-28 05:32:06 [verbose]: ⏳ Currently loading components (0) [runtime] 2022-06-28 05:32:06 [verbose]: 🧵 Task schedulers [runtime] 2022-06-28 05:32:06 [verbose]: LoadComponentsAsync (standard) concurrency:4 running:0 pending:0 [runtime] 2022-06-28 05:32:06 [verbose]: LoadComponentsAsync (long load) concurrency:4 running:0 pending:0 [runtime] 2022-06-28 05:32:06 [verbose]: 🎱 Thread pool [runtime] 2022-06-28 05:32:06 [verbose]: worker: min 1 max 32,767 available 32,766 [runtime] 2022-06-28 05:32:06 [verbose]: completion: min 1 max 1,000 available 1,000 [runtime] 2022-06-28 05:32:06 [verbose]: Host execution state changed to Stopping ``` https://teamcity.ppy.sh/buildConfiguration/Osu_Build/811?hideProblemsFromDependencies=false&hideTestsFromDependencies=false&expandBuildTestsSection=true --- .../Visual/Gameplay/TestSceneSpectatorPlayback.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 1e517efef2..5fad661e9b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -167,11 +167,16 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("start failing sends", () => { spectatorClient.ShouldFailSendingFrames = true; - framesReceivedSoFar = replay.Frames.Count; frameSendAttemptsSoFar = spectatorClient.FrameSendAttempts; }); - AddUntilStep("wait for send attempts", () => spectatorClient.FrameSendAttempts > frameSendAttemptsSoFar + 5); + AddUntilStep("wait for next send attempt", () => + { + framesReceivedSoFar = replay.Frames.Count; + return spectatorClient.FrameSendAttempts > frameSendAttemptsSoFar + 1; + }); + + AddUntilStep("wait for more send attempts", () => spectatorClient.FrameSendAttempts > frameSendAttemptsSoFar + 10); AddAssert("frames did not increase", () => framesReceivedSoFar == replay.Frames.Count); AddStep("stop failing sends", () => spectatorClient.ShouldFailSendingFrames = false); From 22b254e5c5ce9aee62f21f0863394f26e1212a2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 15:09:28 +0900 Subject: [PATCH 249/803] Handle task exception outside of schedule to avoid unobserved exceptions --- osu.Game/Online/Spectator/SpectatorClient.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 4a43aa6c66..08e1e78d86 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -294,17 +294,21 @@ namespace osu.Game.Online.Spectator lastSend = tcs.Task; - SendFramesInternal(bundle).ContinueWith(t => Schedule(() => + SendFramesInternal(bundle).ContinueWith(t => { + // Handle exception outside of `Schedule` to ensure it doesn't go unovserved. bool wasSuccessful = t.Exception == null; - // If the last bundle send wasn't successful, try again without dequeuing. - if (wasSuccessful) - pendingFrameBundles.Dequeue(); + return Schedule(() => + { + // If the last bundle send wasn't successful, try again without dequeuing. + if (wasSuccessful) + pendingFrameBundles.Dequeue(); - tcs.SetResult(wasSuccessful); - sendNextBundleIfRequired(); - })); + tcs.SetResult(wasSuccessful); + sendNextBundleIfRequired(); + }); + }); } } } From 35745c83b7fa468468c5b032bda97d5120c59f27 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 15:19:02 +0900 Subject: [PATCH 250/803] Replace dodgy `SetUpSteps` overriding with usage of `HasCustomSteps` --- .../TestSceneCatchPlayerLegacySkin.cs | 2 +- .../TestSceneSliderSnaking.cs | 29 ++++++------------- .../TestSceneStoryboardSamplePlayback.cs | 2 +- .../Gameplay/TestSceneStoryboardWithOutro.cs | 12 ++++---- osu.Game/Tests/Visual/PlayerTestScene.cs | 4 +-- 5 files changed, 19 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs index 731cb4e135..8dd6f82c57 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("change component scale", () => Player.ChildrenOfType().First().Scale = new Vector2(2f)); AddStep("update target", () => Player.ChildrenOfType().ForEach(LegacySkin.UpdateDrawableTarget)); AddStep("exit player", () => Player.Exit()); - CreateTest(null); + CreateTest(); } AddAssert("legacy HUD combo counter hidden", () => diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 5706955fc5..366793058d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -66,10 +66,7 @@ namespace osu.Game.Rulesets.Osu.Tests drawableSlider = null; }); - [SetUpSteps] - public override void SetUpSteps() - { - } + protected override bool HasCustomSteps => true; [TestCase(0)] [TestCase(1)] @@ -77,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Tests public void TestSnakingEnabled(int sliderIndex) { AddStep("enable autoplay", () => autoplay = true); - base.SetUpSteps(); + CreateTest(); AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); retrieveSlider(sliderIndex); @@ -101,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests public void TestSnakingDisabled(int sliderIndex) { AddStep("have autoplay", () => autoplay = true); - base.SetUpSteps(); + CreateTest(); AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); retrieveSlider(sliderIndex); @@ -121,8 +118,7 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep("enable autoplay", () => autoplay = true); setSnaking(true); - base.SetUpSteps(); - + CreateTest(); // 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 addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionAlmostSame); @@ -133,15 +129,14 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep("disable autoplay", () => autoplay = false); setSnaking(true); - base.SetUpSteps(); - + CreateTest(); addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionDecreased); } private void retrieveSlider(int index) { AddStep("retrieve slider at index", () => slider = (Slider)beatmap.HitObjects[index]); - addSeekStep(() => slider); + addSeekStep(() => slider.StartTime); AddUntilStep("retrieve drawable slider", () => (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null); } @@ -205,16 +200,10 @@ namespace osu.Game.Rulesets.Osu.Tests }); } - private void addSeekStep(Func slider) + private void addSeekStep(Func getTime) { - AddStep("seek to slider", () => Player.GameplayClockContainer.Seek(slider().StartTime)); - AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(slider().StartTime, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); - } - - private void addSeekStep(Func time) - { - 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 time", () => Player.GameplayClockContainer.Seek(getTime())); + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(getTime(), Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { HitObjects = createHitObjects() }; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs index 079d459beb..f0e184d727 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs @@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void createPlayerTest() { - CreateTest(null); + CreateTest(); AddAssert("storyboard loaded", () => Player.Beatmap.Value.Storyboard != null); waitUntilStoryboardSamplesPlay(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 0ba8583b56..e2b2ad85a3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestStoryboardSkipOutro() { AddStep("set storyboard duration to long", () => currentStoryboardDuration = 200000); - CreateTest(null); + CreateTest(); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space)); AddUntilStep("player is no longer current screen", () => !Player.IsCurrentScreen()); @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestStoryboardNoSkipOutro() { - CreateTest(null); + CreateTest(); AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration); AddUntilStep("wait for score shown", () => Player.IsScoreShown); } @@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestStoryboardExitDuringOutroStillExits() { - CreateTest(null); + CreateTest(); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("exit via pause", () => Player.ExitViaPause()); AddAssert("player exited", () => !Player.IsCurrentScreen() && Player.GetChildScreen() == null); @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Gameplay [TestCase(true)] public void TestStoryboardToggle(bool enabledAtBeginning) { - CreateTest(null); + CreateTest(); AddStep($"{(enabledAtBeginning ? "enable" : "disable")} storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, enabledAtBeginning)); AddStep("toggle storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, !enabledAtBeginning)); AddUntilStep("wait for score shown", () => Player.IsScoreShown); @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Gameplay { SkipOverlay.FadeContainer fadeContainer() => Player.ChildrenOfType().First(); - CreateTest(null); + CreateTest(); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddUntilStep("skip overlay content becomes visible", () => fadeContainer().State == Visibility.Visible); @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestPerformExitNoOutro() { - CreateTest(null); + CreateTest(); AddStep("disable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, false)); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("exit via pause", () => Player.ExitViaPause()); diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 521fd8f21a..e1714299b6 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -39,10 +39,10 @@ namespace osu.Game.Tests.Visual base.SetUpSteps(); if (!HasCustomSteps) - CreateTest(null); + CreateTest(); } - protected void CreateTest(Action action) + protected void CreateTest([CanBeNull] Action action = null) { if (action != null && !HasCustomSteps) throw new InvalidOperationException($"Cannot add custom test steps without {nameof(HasCustomSteps)} being set."); From 6bfd351dec8e4a1a38a8ab60e36580534b3fa6ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 15:23:29 +0900 Subject: [PATCH 251/803] Add logging of `GameplayClockContainer` seeks --- osu.Game/Screens/Play/GameplayClockContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 6403943a53..9396b3311f 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Framework.Timing; namespace osu.Game.Screens.Play @@ -101,6 +102,8 @@ namespace osu.Game.Screens.Play /// The destination time to seek to. public virtual void Seek(double time) { + Logger.Log($"{nameof(GameplayClockContainer)} seeking to {time}"); + AdjustableSource.Seek(time); // Manually process to make sure the gameplay clock is correctly updated after a seek. From e10ac45fd73b7345898c700591edabfe7d84ee3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 16:54:53 +0900 Subject: [PATCH 252/803] Remove probably redundant `realmLock` As far as I can tell all accesses are safe due to update thread guarantees. The only weird one may be async writes during a `BlockAllOperations`, but the `Compact` loop should handle this quite amicably. --- osu.Game/Database/RealmAccess.cs | 147 +++++++++++++------------------ 1 file changed, 62 insertions(+), 85 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 7a9561fc5e..715066f32d 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -98,8 +98,6 @@ namespace osu.Game.Database private static readonly GlobalStatistic total_writes_async = GlobalStatistics.Get(@"Realm", @"Writes (Async)"); - private readonly object realmLock = new object(); - private Realm? updateRealm; /// @@ -122,24 +120,21 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"Use {nameof(getRealmInstance)} when performing realm operations from a non-update thread"); - lock (realmLock) + if (updateRealm == null) { - if (updateRealm == null) - { - updateRealm = getRealmInstance(); - hasInitialisedOnce = true; + updateRealm = getRealmInstance(); + hasInitialisedOnce = true; - Logger.Log(@$"Opened realm ""{updateRealm.Config.DatabasePath}"" at version {updateRealm.Config.SchemaVersion}"); + Logger.Log(@$"Opened realm ""{updateRealm.Config.DatabasePath}"" at version {updateRealm.Config.SchemaVersion}"); - // Resubscribe any subscriptions - foreach (var action in customSubscriptionsResetMap.Keys) - registerSubscription(action); - } - - Debug.Assert(updateRealm != null); - - return updateRealm; + // Resubscribe any subscriptions + foreach (var action in customSubscriptionsResetMap.Keys) + registerSubscription(action); } + + Debug.Assert(updateRealm != null); + + return updateRealm; } internal static bool CurrentThreadSubscriptionsAllowed => current_thread_subscriptions_allowed.Value; @@ -404,30 +399,27 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(WriteAsync)} must be called from the update thread."); - lock (realmLock) + // CountdownEvent will fail if already at zero. + if (!pendingAsyncWrites.TryAddCount()) + pendingAsyncWrites.Reset(1); + + // Regardless of calling Realm.GetInstance or Realm.GetInstanceAsync, there is a blocking overhead on retrieval. + // Adding a forced Task.Run resolves this. + var writeTask = Task.Run(async () => { - // CountdownEvent will fail if already at zero. - if (!pendingAsyncWrites.TryAddCount()) - pendingAsyncWrites.Reset(1); + total_writes_async.Value++; - // Regardless of calling Realm.GetInstance or Realm.GetInstanceAsync, there is a blocking overhead on retrieval. - // Adding a forced Task.Run resolves this. - var writeTask = Task.Run(async () => - { - total_writes_async.Value++; + // Not attempting to use Realm.GetInstanceAsync as there's seemingly no benefit to us (for now) and it adds complexity due to locking + // concerns in getRealmInstance(). On a quick check, it looks to be more suited to cases where realm is connecting to an online sync + // server, which we don't use. May want to report upstream or revisit in the future. + using (var realm = getRealmInstance()) + // ReSharper disable once AccessToDisposedClosure (WriteAsync should be marked as [InstantHandle]). + await realm.WriteAsync(() => action(realm)); - // Not attempting to use Realm.GetInstanceAsync as there's seemingly no benefit to us (for now) and it adds complexity due to locking - // concerns in getRealmInstance(). On a quick check, it looks to be more suited to cases where realm is connecting to an online sync - // server, which we don't use. May want to report upstream or revisit in the future. - using (var realm = getRealmInstance()) - // ReSharper disable once AccessToDisposedClosure (WriteAsync should be marked as [InstantHandle]). - await realm.WriteAsync(() => action(realm)); + pendingAsyncWrites.Signal(); + }); - pendingAsyncWrites.Signal(); - }); - - return writeTask; - } + return writeTask; } /// @@ -452,14 +444,11 @@ namespace osu.Game.Database public IDisposable RegisterForNotifications(Func> query, NotificationCallbackDelegate callback) where T : RealmObjectBase { - lock (realmLock) - { - Func action = realm => query(realm).QueryAsyncWithNotifications(callback); + Func action = realm => query(realm).QueryAsyncWithNotifications(callback); - // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. - notificationsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); - return RegisterCustomSubscription(action); - } + // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. + notificationsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); + return RegisterCustomSubscription(action); } /// @@ -550,15 +539,12 @@ namespace osu.Game.Database void unsubscribe() { - lock (realmLock) + if (customSubscriptionsResetMap.TryGetValue(action, out var unsubscriptionAction)) { - if (customSubscriptionsResetMap.TryGetValue(action, out var unsubscriptionAction)) - { - unsubscriptionAction?.Dispose(); - customSubscriptionsResetMap.Remove(action); - notificationsResetMap.Remove(action); - total_subscriptions.Value--; - } + unsubscriptionAction?.Dispose(); + customSubscriptionsResetMap.Remove(action); + notificationsResetMap.Remove(action); + total_subscriptions.Value--; } } }); @@ -568,19 +554,16 @@ namespace osu.Game.Database { Debug.Assert(ThreadSafety.IsUpdateThread); - lock (realmLock) - { - // Retrieve realm instance outside of flag update to ensure that the instance is retrieved, - // as attempting to access it inside the subscription if it's not constructed would lead to - // cyclic invocations of the subscription callback. - var realm = Realm; + // Retrieve realm instance outside of flag update to ensure that the instance is retrieved, + // as attempting to access it inside the subscription if it's not constructed would lead to + // cyclic invocations of the subscription callback. + var realm = Realm; - Debug.Assert(!customSubscriptionsResetMap.TryGetValue(action, out var found) || found == null); + Debug.Assert(!customSubscriptionsResetMap.TryGetValue(action, out var found) || found == null); - current_thread_subscriptions_allowed.Value = true; - customSubscriptionsResetMap[action] = action(realm); - current_thread_subscriptions_allowed.Value = false; - } + current_thread_subscriptions_allowed.Value = true; + customSubscriptionsResetMap[action] = action(realm); + current_thread_subscriptions_allowed.Value = false; } private Realm getRealmInstance() @@ -831,31 +814,28 @@ namespace osu.Game.Database { realmRetrievalLock.Wait(); - lock (realmLock) + if (hasInitialisedOnce) { - if (hasInitialisedOnce) + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + + syncContext = SynchronizationContext.Current; + + // Before disposing the update context, clean up all subscriptions. + // Note that in the case of realm notification subscriptions, this is not really required (they will be cleaned up by disposal). + // In the case of custom subscriptions, we want them to fire before the update realm is disposed in case they do any follow-up work. + foreach (var action in customSubscriptionsResetMap.ToArray()) { - if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); - - syncContext = SynchronizationContext.Current; - - // Before disposing the update context, clean up all subscriptions. - // Note that in the case of realm notification subscriptions, this is not really required (they will be cleaned up by disposal). - // In the case of custom subscriptions, we want them to fire before the update realm is disposed in case they do any follow-up work. - foreach (var action in customSubscriptionsResetMap.ToArray()) - { - action.Value?.Dispose(); - customSubscriptionsResetMap[action.Key] = null; - } - - updateRealm?.Dispose(); - updateRealm = null; + action.Value?.Dispose(); + customSubscriptionsResetMap[action.Key] = null; } - Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); + updateRealm?.Dispose(); + updateRealm = null; } + Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); + const int sleep_length = 200; int timeout = 5000; @@ -933,10 +913,7 @@ namespace osu.Game.Database if (!pendingAsyncWrites.Wait(10000)) Logger.Log("Realm took too long waiting on pending async writes", level: LogLevel.Error); - lock (realmLock) - { - updateRealm?.Dispose(); - } + updateRealm?.Dispose(); if (!isDisposed) { From d64959ad0c8a7dccf241741801f5854586229edb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 17:07:49 +0900 Subject: [PATCH 253/803] Add test coverage of async writes during a blocking operation --- osu.Game.Tests/Database/GeneralUsageTests.cs | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index db6e97dab2..5b6f7a0a53 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -49,6 +49,27 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestAsyncWriteWhileBlocking() + { + RunTestWithRealm((realm, _) => + { + Task writeTask; + + using (realm.BlockAllOperations()) + { + writeTask = realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); + Thread.Sleep(100); + Assert.That(writeTask.IsCompleted, Is.False); + } + + writeTask.WaitSafely(); + + realm.Run(r => r.Refresh()); + Assert.That(realm.Run(r => r.All().Count()), Is.EqualTo(1)); + }); + } + [Test] public void TestAsyncWrite() { From 21d31ee218ab60d886c20155e9b817f5dc44257e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 18:04:08 +0900 Subject: [PATCH 254/803] Ensure joined channels are actually joined to avoid unexpected tab order --- .../Visual/Online/TestSceneChatOverlay.cs | 71 +++++++++++-------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index ff2817c439..c57606d44b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -40,7 +40,6 @@ namespace osu.Game.Tests.Visual.Online private ChannelManager channelManager; private APIUser testUser; - private Channel testPMChannel; private Channel[] testChannels; private Channel testChannel1 => testChannels[0]; @@ -53,7 +52,6 @@ namespace osu.Game.Tests.Visual.Online public void SetUp() => Schedule(() => { testUser = new APIUser { Username = "test user", Id = 5071479 }; - testPMChannel = new Channel(testUser); testChannels = Enumerable.Range(1, 10).Select(createPublicChannel).ToArray(); Child = new DependencyProvidingContainer @@ -181,7 +179,7 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Show overlay", () => chatOverlay.Show()); AddAssert("Listing is visible", () => listingIsVisible); - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + joinTestChannel(0); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); waitForChannel1Visible(); } @@ -203,12 +201,11 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestChannelCloseButton() { + var testPMChannel = new Channel(testUser); + AddStep("Show overlay", () => chatOverlay.Show()); - AddStep("Join PM and public channels", () => - { - channelManager.JoinChannel(testChannel1); - channelManager.JoinChannel(testPMChannel); - }); + joinTestChannel(0); + joinChannel(testPMChannel); AddStep("Select PM channel", () => clickDrawable(getChannelListItem(testPMChannel))); AddStep("Click close button", () => { @@ -229,7 +226,7 @@ namespace osu.Game.Tests.Visual.Online public void TestChatCommand() { AddStep("Show overlay", () => chatOverlay.Show()); - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + joinTestChannel(0); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddStep("Open chat with user", () => channelManager.PostCommand($"chat {testUser.Username}")); AddAssert("PM channel is selected", () => @@ -248,14 +245,16 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestMultiplayerChannelIsNotShown() { - Channel multiplayerChannel = null; + Channel multiplayerChannel; AddStep("Show overlay", () => chatOverlay.Show()); - AddStep("Join multiplayer channel", () => channelManager.JoinChannel(multiplayerChannel = new Channel(new APIUser()) + + joinChannel(multiplayerChannel = new Channel(new APIUser()) { Name = "#mp_1", Type = ChannelType.Multiplayer, - })); + }); + AddAssert("Channel is joined", () => channelManager.JoinedChannels.Contains(multiplayerChannel)); AddUntilStep("Channel not present in listing", () => !chatOverlay.ChildrenOfType() .Where(item => item.IsPresent) @@ -269,7 +268,7 @@ namespace osu.Game.Tests.Visual.Online Message message = null; AddStep("Show overlay", () => chatOverlay.Show()); - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + joinTestChannel(0); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddStep("Send message in channel 1", () => { @@ -291,8 +290,8 @@ namespace osu.Game.Tests.Visual.Online Message message = null; AddStep("Show overlay", () => chatOverlay.Show()); - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); - AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2)); + joinTestChannel(0); + joinTestChannel(1); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddStep("Send message in channel 2", () => { @@ -314,8 +313,8 @@ namespace osu.Game.Tests.Visual.Online Message message = null; AddStep("Show overlay", () => chatOverlay.Show()); - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); - AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2)); + joinTestChannel(0); + joinTestChannel(1); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddStep("Send message in channel 2", () => { @@ -337,7 +336,7 @@ namespace osu.Game.Tests.Visual.Online { Message message = null; - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + joinTestChannel(0); AddStep("Send message in channel 1", () => { testChannel1.AddNewMessages(message = new Message @@ -357,7 +356,7 @@ namespace osu.Game.Tests.Visual.Online { Message message = null; - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + joinTestChannel(0); AddStep("Send message in channel 1", () => { testChannel1.AddNewMessages(message = new Message @@ -378,7 +377,7 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Show overlay", () => chatOverlay.Show()); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + joinTestChannel(0); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); waitForChannel1Visible(); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); @@ -404,11 +403,11 @@ namespace osu.Game.Tests.Visual.Online chatOverlay.Show(); chatOverlay.SlowLoading = true; }); - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + joinTestChannel(0); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddUntilStep("Channel 1 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel1).LoadState == LoadState.Loading); - AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2)); + joinTestChannel(1); AddStep("Select channel 2", () => clickDrawable(getChannelListItem(testChannel2))); AddUntilStep("Channel 2 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel2).LoadState == LoadState.Loading); @@ -461,15 +460,13 @@ namespace osu.Game.Tests.Visual.Online Channel pmChannel1 = createPrivateChannel(); Channel pmChannel2 = createPrivateChannel(); - AddStep("Show overlay with channels", () => - { - channelManager.JoinChannel(testChannel1); - channelManager.JoinChannel(testChannel2); - channelManager.JoinChannel(pmChannel1); - channelManager.JoinChannel(pmChannel2); - channelManager.JoinChannel(announceChannel); - chatOverlay.Show(); - }); + joinTestChannel(0); + joinTestChannel(1); + joinChannel(pmChannel1); + joinChannel(pmChannel2); + joinChannel(announceChannel); + + AddStep("Show overlay", () => chatOverlay.Show()); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); waitForChannel1Visible(); @@ -490,6 +487,18 @@ namespace osu.Game.Tests.Visual.Online waitForChannel1Visible(); } + private void joinTestChannel(int i) + { + AddStep($"Join test channel {i}", () => channelManager.JoinChannel(testChannels[i])); + AddUntilStep("wait for join completed", () => testChannels[i].Joined.Value); + } + + private void joinChannel(Channel channel) + { + AddStep($"Join channel {channel}", () => channelManager.JoinChannel(channel)); + AddUntilStep("wait for join completed", () => channel.Joined.Value); + } + private void waitForChannel1Visible() => AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel?.Channel == testChannel1); From ed1b809f54b22116ee6f04738f85d11a22ff8457 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 18:11:30 +0900 Subject: [PATCH 255/803] Add missing request handling to actually join PM channels --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index c57606d44b..d2d9b9a9e5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -78,6 +78,14 @@ namespace osu.Game.Tests.Visual.Online { switch (req) { + case CreateChannelRequest createRequest: + createRequest.TriggerSuccess(new APIChatChannel + { + ChannelID = ((int)createRequest.Channel.Id), + RecentMessages = new List() + }); + return true; + case GetUpdatesRequest getUpdates: getUpdates.TriggerFailure(new WebException()); return true; From 975ba83838a63f97e7942f3958ad9d3dd50d80d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 18:19:43 +0900 Subject: [PATCH 256/803] 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 8f4a102cce..f884ce0eff 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 b9033f1bb9..dbfdf0fde8 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 a1e4cf3ba0..11c177dbae 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From c282e193169431e8c7bbe1bf26ad77b6c197396b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 16:29:19 +0900 Subject: [PATCH 257/803] Update language initialisation in line with framework `LocalisationManager` changes --- osu.Game/OsuGame.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ef42522f9d..bd0a2680ae 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -24,6 +24,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; @@ -686,27 +687,29 @@ namespace osu.Game { base.LoadComplete(); - foreach (var language in Enum.GetValues(typeof(Language)).OfType()) + var languages = Enum.GetValues(typeof(Language)).OfType(); + + var mappings = languages.Select(language => { #if DEBUG if (language == Language.debug) - { - Localisation.AddLanguage(Language.debug.ToString(), new DebugLocalisationStore()); - continue; - } + return new LocaleMapping("debug", new DebugLocalisationStore()); #endif string cultureCode = language.ToCultureCode(); try { - Localisation.AddLanguage(cultureCode, new ResourceManagerLocalisationStore(cultureCode)); + return new LocaleMapping(new ResourceManagerLocalisationStore(cultureCode)); } catch (Exception ex) { Logger.Error(ex, $"Could not load localisations for language \"{cultureCode}\""); + return null; } - } + }).Where(m => m != null); + + Localisation.AddLocaleMappings(mappings); // The next time this is updated is in UpdateAfterChildren, which occurs too late and results // in the cursor being shown for a few frames during the intro. From 7c9c499e19d033ef0b352df7930668c67ddd3017 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 19:21:41 +0900 Subject: [PATCH 258/803] Update resources (translation updates) --- 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 f884ce0eff..2ef3bb2dd1 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 dbfdf0fde8..ab690b22a6 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 11c177dbae..a84fc40a45 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From 946178ca412762ff871d8ea7f4d0579b8eebaf76 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Tue, 28 Jun 2022 20:03:21 +0900 Subject: [PATCH 259/803] Remove useless `LocalisableDescription` --- osu.Game/Scoring/ScoreRank.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Scoring/ScoreRank.cs b/osu.Game/Scoring/ScoreRank.cs index dc90e417cd..a4bb99add1 100644 --- a/osu.Game/Scoring/ScoreRank.cs +++ b/osu.Game/Scoring/ScoreRank.cs @@ -11,8 +11,6 @@ namespace osu.Game.Scoring { public enum ScoreRank { - // TODO: reconsider changing later on - [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.RankD))] [Description(@"F")] F = -1, From 2269f1046ed34d4dad8542013a322290ebda5458 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Tue, 28 Jun 2022 20:59:03 +0800 Subject: [PATCH 260/803] Remove the nullable disable annotations. --- osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs | 2 -- osu.Game/Migrations/20171019041408_InitialCreate.cs | 2 -- .../Migrations/20171025071459_AddMissingIndexRules.Designer.cs | 2 -- osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs | 2 -- ...0171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs | 2 -- .../20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs | 2 -- .../20171209034410_AddRulesetInfoShortName.Designer.cs | 2 -- osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs | 2 -- osu.Game/Migrations/20180125143340_Settings.Designer.cs | 2 -- osu.Game/Migrations/20180125143340_Settings.cs | 2 -- osu.Game/Migrations/20180131154205_AddMuteBinding.cs | 2 -- osu.Game/Migrations/20180219060912_AddSkins.Designer.cs | 2 -- osu.Game/Migrations/20180219060912_AddSkins.cs | 2 -- .../20180529055154_RemoveUniqueHashConstraints.Designer.cs | 2 -- .../Migrations/20180529055154_RemoveUniqueHashConstraints.cs | 2 -- .../20180621044111_UpdateTaikoDefaultBindings.Designer.cs | 2 -- .../Migrations/20180621044111_UpdateTaikoDefaultBindings.cs | 2 -- .../Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs | 2 -- osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs | 2 -- osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs | 2 -- osu.Game/Migrations/20180913080842_AddRankStatus.cs | 2 -- osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs | 2 -- osu.Game/Migrations/20181007180454_StandardizePaths.cs | 2 -- osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs | 2 -- osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs | 2 -- .../Migrations/20181130113755_AddScoreInfoTables.Designer.cs | 2 -- osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs | 2 -- osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs | 2 -- osu.Game/Migrations/20190225062029_AddUserIDColumn.cs | 2 -- osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs | 2 -- osu.Game/Migrations/20190525060824_SkinSettings.cs | 2 -- .../20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs | 2 -- .../Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs | 2 -- .../20190708070844_AddBPMAndLengthColumns.Designer.cs | 2 -- osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs | 2 -- osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs | 2 -- osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs | 2 -- .../Migrations/20200302094919_RefreshVolumeBindings.Designer.cs | 2 -- osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs | 2 -- .../Migrations/20201019224408_AddEpilepsyWarning.Designer.cs | 2 -- osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs | 2 -- .../20210412045700_RefreshVolumeBindingsAgain.Designer.cs | 2 -- .../Migrations/20210412045700_RefreshVolumeBindingsAgain.cs | 2 -- .../20210511060743_AddSkinInstantiationInfo.Designer.cs | 2 -- osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs | 2 -- .../20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs | 2 -- .../Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs | 2 -- .../Migrations/20210824185035_AddCountdownSettings.Designer.cs | 2 -- osu.Game/Migrations/20210824185035_AddCountdownSettings.cs | 2 -- .../20210912144011_AddSamplesMatchPlaybackRate.Designer.cs | 2 -- .../Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs | 2 -- osu.Game/Migrations/20211020081609_ResetSkinHashes.cs | 2 -- osu.Game/Migrations/OsuDbContextModelSnapshot.cs | 2 -- 53 files changed, 106 deletions(-) diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs index 7104245d9e..c751530bf4 100644 --- a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs +++ b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs @@ -1,7 +1,5 @@ // using Microsoft.EntityFrameworkCore; - -#nullable disable using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.cs b/osu.Game/Migrations/20171019041408_InitialCreate.cs index f1c7c94638..08ab64fd08 100644 --- a/osu.Game/Migrations/20171019041408_InitialCreate.cs +++ b/osu.Game/Migrations/20171019041408_InitialCreate.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs index b464c15ce2..4cd234f2ef 100644 --- a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs +++ b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs @@ -1,7 +1,5 @@ // using Microsoft.EntityFrameworkCore; - -#nullable disable using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs index 1d0164a1d9..4ec3952941 100644 --- a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs +++ b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs index 53e8b887d8..006acf12cd 100644 --- a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs +++ b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs @@ -1,7 +1,5 @@ // using Microsoft.EntityFrameworkCore; - -#nullable disable using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs index 765a6a5b58..6aba12f86f 100644 --- a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs +++ b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs index 4da20141bc..fc2496bc24 100644 --- a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs +++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs @@ -1,7 +1,5 @@ // using Microsoft.EntityFrameworkCore; - -#nullable disable using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs index 3379efa68e..5688455f79 100644 --- a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs +++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20180125143340_Settings.Designer.cs b/osu.Game/Migrations/20180125143340_Settings.Designer.cs index 68054d6404..4bb599eec1 100644 --- a/osu.Game/Migrations/20180125143340_Settings.Designer.cs +++ b/osu.Game/Migrations/20180125143340_Settings.Designer.cs @@ -1,7 +1,5 @@ // using Microsoft.EntityFrameworkCore; - -#nullable disable using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20180125143340_Settings.cs b/osu.Game/Migrations/20180125143340_Settings.cs index 8f7d0a6ed3..1feb37531f 100644 --- a/osu.Game/Migrations/20180125143340_Settings.cs +++ b/osu.Game/Migrations/20180125143340_Settings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs index 3e97e78e61..8646d1d76b 100644 --- a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs +++ b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Infrastructure; using osu.Game.Database; diff --git a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs index 7d95a702b8..cdc4ef2e66 100644 --- a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs +++ b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs @@ -1,7 +1,5 @@ // using Microsoft.EntityFrameworkCore; - -#nullable disable using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20180219060912_AddSkins.cs b/osu.Game/Migrations/20180219060912_AddSkins.cs index df9d267a14..319748bed6 100644 --- a/osu.Game/Migrations/20180219060912_AddSkins.cs +++ b/osu.Game/Migrations/20180219060912_AddSkins.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs index 7e490c7833..f28408bfb3 100644 --- a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs +++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs @@ -1,7 +1,5 @@ // using Microsoft.EntityFrameworkCore; - -#nullable disable using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs index 62c341b0c6..91eabe8868 100644 --- a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs +++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs index a103cdad20..aaa11e88b6 100644 --- a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs +++ b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs index b08fa7a899..d888ccd5a2 100644 --- a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs +++ b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs index 5b1734554a..7eeacd56d7 100644 --- a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs index 4ba0a4d0e6..fdea636ac6 100644 --- a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs index ceef5b6948..5ab43da046 100644 --- a/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs +++ b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20180913080842_AddRankStatus.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.cs index ec82925b03..bb147dff84 100644 --- a/osu.Game/Migrations/20180913080842_AddRankStatus.cs +++ b/osu.Game/Migrations/20180913080842_AddRankStatus.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs index d1185ef186..b387a45ecf 100644 --- a/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.cs index f9323c0b9b..30f27043a0 100644 --- a/osu.Game/Migrations/20181007180454_StandardizePaths.cs +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs index 0797090c7b..120674671a 100644 --- a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs index 43689f2a3c..ee825a1e9c 100644 --- a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs index 3b70ad8d45..eee53182ce 100644 --- a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs index f52edacc7f..58980132f3 100644 --- a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs index 51b63bd9eb..8e1e3a59f3 100644 --- a/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs +++ b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs index 00d807d5c4..f2eef600dc 100644 --- a/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs +++ b/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs index 9d6515e5c6..348c42adb9 100644 --- a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs +++ b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.cs b/osu.Game/Migrations/20190525060824_SkinSettings.cs index 463d4fe1ad..7779b55bb7 100644 --- a/osu.Game/Migrations/20190525060824_SkinSettings.cs +++ b/osu.Game/Migrations/20190525060824_SkinSettings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs index 1bc2e76ae5..9477369aa0 100644 --- a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs +++ b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs index 61925a4cb4..0620a0624f 100644 --- a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs +++ b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs index c51b8739bc..c5fcc16f84 100644 --- a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs +++ b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs index 19a2464157..f8ce354aa1 100644 --- a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs +++ b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs index 95583e7587..826233a2b0 100644 --- a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs +++ b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs index c1208e0bf1..af82b4db20 100644 --- a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs +++ b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs index 3c7de0602b..22316b0380 100644 --- a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs +++ b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs index 73cfb1725b..3d2ddbf6fc 100644 --- a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs +++ b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs index 24acc3195f..1c05de832e 100644 --- a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs +++ b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs index eaca8785f9..58a35a7bf3 100644 --- a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs +++ b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs index 704af354c9..2c100d39b9 100644 --- a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs +++ b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs index 7772c0e86b..4d3941dd20 100644 --- a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs +++ b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs index 097bd6a244..b808c648da 100644 --- a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs +++ b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs index 1cc9ab6120..887635fa85 100644 --- a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs +++ b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs index ca9502422b..89bab3a0fa 100644 --- a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs +++ b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs index 82c5b27769..7b579e27b9 100644 --- a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs +++ b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs b/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs index 2a4e370649..afeb42130d 100644 --- a/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs +++ b/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs b/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs index dd6fa23387..d1b09e2c1d 100644 --- a/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs +++ b/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs index 9e09f2c69e..6e53d7fae0 100644 --- a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs +++ b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs index 0598a41f09..f6fc1f4420 100644 --- a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs +++ b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs b/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs index 0f7a2a5702..6d53c019ec 100644 --- a/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs +++ b/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using osu.Game.Database; diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 7e38889fa6..036c26cb0a 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; From de7f2a0bba6953b486689427198b0b1fd2804a30 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Tue, 28 Jun 2022 20:56:02 +0800 Subject: [PATCH 261/803] Remove the nullable disable annotation in the localisation. --- osu.Game/Localisation/AudioSettingsStrings.cs | 2 -- osu.Game/Localisation/BeatmapOffsetControlStrings.cs | 2 -- osu.Game/Localisation/BindingSettingsStrings.cs | 2 -- osu.Game/Localisation/ButtonSystemStrings.cs | 2 -- osu.Game/Localisation/ChatStrings.cs | 2 -- osu.Game/Localisation/CommonStrings.cs | 2 -- osu.Game/Localisation/DebugLocalisationStore.cs | 2 -- osu.Game/Localisation/DebugSettingsStrings.cs | 2 -- osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs | 2 -- .../FirstRunOverlayImportFromStableScreenStrings.cs | 2 -- osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs | 2 -- osu.Game/Localisation/FirstRunSetupOverlayStrings.cs | 2 -- osu.Game/Localisation/GameplaySettingsStrings.cs | 2 -- osu.Game/Localisation/GeneralSettingsStrings.cs | 2 -- osu.Game/Localisation/GlobalActionKeyBindingStrings.cs | 2 -- osu.Game/Localisation/GraphicsSettingsStrings.cs | 2 -- osu.Game/Localisation/InputSettingsStrings.cs | 2 -- osu.Game/Localisation/JoystickSettingsStrings.cs | 2 -- osu.Game/Localisation/Language.cs | 2 -- osu.Game/Localisation/LayoutSettingsStrings.cs | 2 -- osu.Game/Localisation/LeaderboardStrings.cs | 2 -- osu.Game/Localisation/MaintenanceSettingsStrings.cs | 2 -- osu.Game/Localisation/ModSelectOverlayStrings.cs | 2 -- osu.Game/Localisation/MouseSettingsStrings.cs | 2 -- osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs | 2 -- osu.Game/Localisation/NamedOverlayComponentStrings.cs | 2 -- osu.Game/Localisation/NotificationsStrings.cs | 2 -- osu.Game/Localisation/NowPlayingStrings.cs | 2 -- osu.Game/Localisation/OnlineSettingsStrings.cs | 2 -- osu.Game/Localisation/RulesetSettingsStrings.cs | 2 -- osu.Game/Localisation/SettingsStrings.cs | 2 -- osu.Game/Localisation/SkinSettingsStrings.cs | 2 -- osu.Game/Localisation/TabletSettingsStrings.cs | 2 -- osu.Game/Localisation/ToastStrings.cs | 2 -- osu.Game/Localisation/UserInterfaceStrings.cs | 2 -- 35 files changed, 70 deletions(-) diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs index 0dc95da2f4..0f0f560df9 100644 --- a/osu.Game/Localisation/AudioSettingsStrings.cs +++ b/osu.Game/Localisation/AudioSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs index 417cf335e0..632a1ad0ea 100644 --- a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs +++ b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/BindingSettingsStrings.cs b/osu.Game/Localisation/BindingSettingsStrings.cs index 39b5ac0d21..ad4a650a1f 100644 --- a/osu.Game/Localisation/BindingSettingsStrings.cs +++ b/osu.Game/Localisation/BindingSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/ButtonSystemStrings.cs b/osu.Game/Localisation/ButtonSystemStrings.cs index c71a99711b..ba4abf63a6 100644 --- a/osu.Game/Localisation/ButtonSystemStrings.cs +++ b/osu.Game/Localisation/ButtonSystemStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/ChatStrings.cs b/osu.Game/Localisation/ChatStrings.cs index b07ed18f7b..7bd284a94e 100644 --- a/osu.Game/Localisation/ChatStrings.cs +++ b/osu.Game/Localisation/ChatStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs index 39dc7cf518..1ee562e122 100644 --- a/osu.Game/Localisation/CommonStrings.cs +++ b/osu.Game/Localisation/CommonStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/DebugLocalisationStore.cs b/osu.Game/Localisation/DebugLocalisationStore.cs index 83ae4581a8..2b114b1bd8 100644 --- a/osu.Game/Localisation/DebugLocalisationStore.cs +++ b/osu.Game/Localisation/DebugLocalisationStore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Globalization; diff --git a/osu.Game/Localisation/DebugSettingsStrings.cs b/osu.Game/Localisation/DebugSettingsStrings.cs index e6de4ddee9..74b2c8d892 100644 --- a/osu.Game/Localisation/DebugSettingsStrings.cs +++ b/osu.Game/Localisation/DebugSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs b/osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs index 4bcbdcff7b..952ca22678 100644 --- a/osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs +++ b/osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs index 38f5860cc5..deac7d8628 100644 --- a/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs +++ b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs b/osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs index 331a8c6764..3a7fe4bb12 100644 --- a/osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs +++ b/osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs b/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs index 0c73d7a85b..91b427e2ca 100644 --- a/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs +++ b/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs index 1719b7d6e8..8a0f773551 100644 --- a/osu.Game/Localisation/GameplaySettingsStrings.cs +++ b/osu.Game/Localisation/GameplaySettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/GeneralSettingsStrings.cs b/osu.Game/Localisation/GeneralSettingsStrings.cs index 8cf26a930d..2aa91f5245 100644 --- a/osu.Game/Localisation/GeneralSettingsStrings.cs +++ b/osu.Game/Localisation/GeneralSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index d45fba6f17..82d03dbb5b 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/GraphicsSettingsStrings.cs b/osu.Game/Localisation/GraphicsSettingsStrings.cs index a73d67067e..38355d9041 100644 --- a/osu.Game/Localisation/GraphicsSettingsStrings.cs +++ b/osu.Game/Localisation/GraphicsSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/InputSettingsStrings.cs b/osu.Game/Localisation/InputSettingsStrings.cs index a16dcd998a..e46b4cecf3 100644 --- a/osu.Game/Localisation/InputSettingsStrings.cs +++ b/osu.Game/Localisation/InputSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/JoystickSettingsStrings.cs b/osu.Game/Localisation/JoystickSettingsStrings.cs index efda1afd48..976ec1adde 100644 --- a/osu.Game/Localisation/JoystickSettingsStrings.cs +++ b/osu.Game/Localisation/JoystickSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs index f9094b9540..c13a1a10cb 100644 --- a/osu.Game/Localisation/Language.cs +++ b/osu.Game/Localisation/Language.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.ComponentModel; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/LayoutSettingsStrings.cs b/osu.Game/Localisation/LayoutSettingsStrings.cs index 1a0b015050..b4326b8e39 100644 --- a/osu.Game/Localisation/LayoutSettingsStrings.cs +++ b/osu.Game/Localisation/LayoutSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/LeaderboardStrings.cs b/osu.Game/Localisation/LeaderboardStrings.cs index 14bc5b7af4..8e53f8e88c 100644 --- a/osu.Game/Localisation/LeaderboardStrings.cs +++ b/osu.Game/Localisation/LeaderboardStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/MaintenanceSettingsStrings.cs b/osu.Game/Localisation/MaintenanceSettingsStrings.cs index 4cb514feb0..7a04bcd1ca 100644 --- a/osu.Game/Localisation/MaintenanceSettingsStrings.cs +++ b/osu.Game/Localisation/MaintenanceSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/ModSelectOverlayStrings.cs b/osu.Game/Localisation/ModSelectOverlayStrings.cs index 3d99075922..e9af7147e3 100644 --- a/osu.Game/Localisation/ModSelectOverlayStrings.cs +++ b/osu.Game/Localisation/ModSelectOverlayStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/MouseSettingsStrings.cs b/osu.Game/Localisation/MouseSettingsStrings.cs index 563d6f7637..fd7225ad2e 100644 --- a/osu.Game/Localisation/MouseSettingsStrings.cs +++ b/osu.Game/Localisation/MouseSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs b/osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs index 956d5195e7..92cedce3e0 100644 --- a/osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs +++ b/osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/NamedOverlayComponentStrings.cs b/osu.Game/Localisation/NamedOverlayComponentStrings.cs index f6a50460dc..475bea2a4a 100644 --- a/osu.Game/Localisation/NamedOverlayComponentStrings.cs +++ b/osu.Game/Localisation/NamedOverlayComponentStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index c60e5d3415..382e0d81f4 100644 --- a/osu.Game/Localisation/NotificationsStrings.cs +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/NowPlayingStrings.cs b/osu.Game/Localisation/NowPlayingStrings.cs index 75d9b28ea6..f334637338 100644 --- a/osu.Game/Localisation/NowPlayingStrings.cs +++ b/osu.Game/Localisation/NowPlayingStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/OnlineSettingsStrings.cs b/osu.Game/Localisation/OnlineSettingsStrings.cs index ebb21afeb7..6862f4ac2c 100644 --- a/osu.Game/Localisation/OnlineSettingsStrings.cs +++ b/osu.Game/Localisation/OnlineSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/RulesetSettingsStrings.cs b/osu.Game/Localisation/RulesetSettingsStrings.cs index 08901a641e..a356c9e20b 100644 --- a/osu.Game/Localisation/RulesetSettingsStrings.cs +++ b/osu.Game/Localisation/RulesetSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/SettingsStrings.cs b/osu.Game/Localisation/SettingsStrings.cs index 5dcab39e10..aa2e2740eb 100644 --- a/osu.Game/Localisation/SettingsStrings.cs +++ b/osu.Game/Localisation/SettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/SkinSettingsStrings.cs b/osu.Game/Localisation/SkinSettingsStrings.cs index 42b72fdbb8..81035c5a5e 100644 --- a/osu.Game/Localisation/SkinSettingsStrings.cs +++ b/osu.Game/Localisation/SkinSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/TabletSettingsStrings.cs b/osu.Game/Localisation/TabletSettingsStrings.cs index 8d2bc21652..d62d348df9 100644 --- a/osu.Game/Localisation/TabletSettingsStrings.cs +++ b/osu.Game/Localisation/TabletSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 00a0031293..52e75425bf 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index a007f760d8..a090b8c14c 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation From aacded0ecf2aaecaac5ea48e7fff00a9052064ca Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 28 Jun 2022 07:33:05 -0700 Subject: [PATCH 262/803] Fix difficulty adjust settings having more padding on sliders --- osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index a42d4a049a..599e81f2b8 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods @@ -103,9 +104,9 @@ namespace osu.Game.Rulesets.Mods { InternalChildren = new Drawable[] { - new SettingsSlider + new OsuSliderBar { - ShowsDefaultIndicator = false, + RelativeSizeAxes = Axes.X, Current = currentNumber, KeyboardStep = 0.1f, } From f07107aba9e3812e8ae94a09fac8a00374da5e42 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 28 Jun 2022 08:38:15 -0700 Subject: [PATCH 263/803] Fix failing diff adjust slider tests --- .../UserInterface/TestSceneModDifficultyAdjustSettings.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs index 72e503dc33..9641e6b1bf 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; @@ -246,7 +247,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep($"Set {name} slider to {value}", () => this.ChildrenOfType().First(c => c.LabelText == name) - .ChildrenOfType>().First().Current.Value = value); + .ChildrenOfType>().First().Current.Value = value); } private void checkBindableAtValue(string name, float? expectedValue) @@ -260,7 +261,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddAssert($"Slider {name} at {expectedValue}", () => this.ChildrenOfType().First(c => c.LabelText == name) - .ChildrenOfType>().First().Current.Value == expectedValue); + .ChildrenOfType>().First().Current.Value == expectedValue); } private void setBeatmapWithDifficultyParameters(float value) From d07c2a64b934303314fec10956ec437a464cc6a2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 28 Jun 2022 08:39:09 -0700 Subject: [PATCH 264/803] Remove unused using --- .../Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs index 9641e6b1bf..181f46a996 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs @@ -14,7 +14,6 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osuTK; From 978a80dd6f2b6bbc615ca520eac176da6e58ae88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 01:52:50 +0900 Subject: [PATCH 265/803] Change closure elimination to hint Pretty noisy as a suggestion, and can fire incorrectly quite a lot (aka `Schedule()`). --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index a55b3b5074..0794095854 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -98,6 +98,7 @@ WARNING HINT DO_NOT_SHOW + HINT HINT HINT ERROR From 36e71d39f2bbdd119148659e5682683f3bf07943 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 28 Jun 2022 09:54:18 -0700 Subject: [PATCH 266/803] Fix one more mod settings test --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 006707d064..4de70f6f9e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -230,7 +230,7 @@ namespace osu.Game.Tests.Visual.UserInterface createScreen(); AddStep("select diff adjust", () => SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }); - AddStep("set setting", () => modSelectOverlay.ChildrenOfType>().First().Current.Value = 8); + AddStep("set setting", () => modSelectOverlay.ChildrenOfType>().First().Current.Value = 8); AddAssert("ensure setting is propagated", () => SelectedMods.Value.OfType().Single().CircleSize.Value == 8); From ecdb30d215f57df0cbb608acafe676b25b5b25a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 02:28:21 +0900 Subject: [PATCH 267/803] Fix one more case of collection modification during enumeration https://sentry.ppy.sh/share/issue/a61c27b2a63a4a6aa80e75873f9d87ca/ --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 089a783177..cb34a92702 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -132,7 +132,7 @@ namespace osu.Game.Database Logger.Log(@$"Opened realm ""{updateRealm.Config.DatabasePath}"" at version {updateRealm.Config.SchemaVersion}"); // Resubscribe any subscriptions - foreach (var action in customSubscriptionsResetMap.Keys) + foreach (var action in customSubscriptionsResetMap.Keys.ToArray()) registerSubscription(action); } From 3a00131606d11382b1ca264be4803cc7fb300ae3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 13:51:48 +0900 Subject: [PATCH 268/803] Bump fastlane --- Gemfile.lock | 60 ++++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ddab497657..cae682ec2b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,20 +8,20 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.570.0) - aws-sdk-core (3.130.0) + aws-partitions (1.601.0) + aws-sdk-core (3.131.2) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) - jmespath (~> 1.0) - aws-sdk-kms (1.55.0) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.57.0) aws-sdk-core (~> 3, >= 3.127.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.113.0) + aws-sdk-s3 (1.114.0) aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) - aws-sigv4 (1.4.0) + aws-sigv4 (1.5.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) claide (1.1.0) @@ -36,7 +36,7 @@ GEM unf (>= 0.0.5, < 1.0.0) dotenv (2.7.6) emoji_regex (3.2.3) - excon (0.92.1) + excon (0.92.3) faraday (1.10.0) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -56,8 +56,8 @@ GEM faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) faraday-httpclient (1.0.1) - faraday-multipart (1.0.3) - multipart-post (>= 1.2, < 3) + faraday-multipart (1.0.4) + multipart-post (~> 2) faraday-net_http (1.0.1) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) @@ -66,7 +66,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.6) - fastlane (2.205.1) + fastlane (2.206.2) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -110,9 +110,9 @@ GEM souyuz (= 0.11.1) fastlane-plugin-xamarin (0.6.3) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.16.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-core (0.4.2) + google-apis-androidpublisher_v3 (0.23.0) + google-apis-core (>= 0.6, < 2.a) + google-apis-core (0.6.0) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -121,19 +121,19 @@ GEM retriable (>= 2.0, < 4.a) rexml webrick - google-apis-iamcredentials_v1 (0.10.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-playcustomapp_v1 (0.7.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-storage_v1 (0.11.0) - google-apis-core (>= 0.4, < 2.a) + google-apis-iamcredentials_v1 (0.12.0) + google-apis-core (>= 0.6, < 2.a) + google-apis-playcustomapp_v1 (0.9.0) + google-apis-core (>= 0.6, < 2.a) + google-apis-storage_v1 (0.16.0) + google-apis-core (>= 0.6, < 2.a) google-cloud-core (1.6.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) google-cloud-errors (1.2.0) - google-cloud-storage (1.36.1) + google-cloud-storage (1.36.2) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) @@ -141,7 +141,7 @@ GEM google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.1.2) + googleauth (1.2.0) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -149,12 +149,12 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.4) + http-cookie (1.0.5) domain_name (~> 0.5) httpclient (2.8.3) jmespath (1.6.1) - json (2.6.1) - jwt (2.3.0) + json (2.6.2) + jwt (2.4.1) memoist (0.16.2) mini_magick (4.11.0) mini_mime (1.1.2) @@ -169,10 +169,10 @@ GEM optparse (0.1.1) os (1.1.4) plist (3.6.0) - public_suffix (4.0.6) + public_suffix (4.0.7) racc (1.6.0) rake (13.0.6) - representable (3.1.1) + representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) @@ -182,9 +182,9 @@ GEM ruby2_keywords (0.0.5) rubyzip (2.3.2) security (0.1.3) - signet (0.16.1) + signet (0.17.0) addressable (~> 2.8) - faraday (>= 0.17.5, < 3.0) + faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) simctl (1.6.8) @@ -205,11 +205,11 @@ GEM uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.8.1) + unf_ext (0.0.8.2) unicode-display_width (1.8.0) webrick (1.7.0) word_wrap (1.0.0) - xcodeproj (1.21.0) + xcodeproj (1.22.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) From 34b9118fb3bf83f7cb7055e322ef147267aeedb7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 29 Jun 2022 16:08:59 +0900 Subject: [PATCH 269/803] Cleanup by using const value --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 9 +-------- .../Difficulty/Skills/OsuStrainSkill.cs | 8 +++++++- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index f64c218c65..84ef109598 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -43,13 +43,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return currentStrain; } - public override double DifficultyValue() - { - double difficulty = GetCurrentStrainPeaks().Sum(); - - // 1.06 comes from the default DifficultyMultiplier field in OsuStrainSkill, - // and it's added here to keep values the same after Flashlight was converted from OsuStrainSkill. - return difficulty * 1.06; - } + public override double DifficultyValue() => GetCurrentStrainPeaks().Sum() * OsuStrainSkill.DEFAULT_DIFFICULTY_MULTIPLIER; } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 94b5727e3f..d6683ade05 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -14,6 +14,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { public abstract class OsuStrainSkill : StrainSkill { + /// + /// The default multiplier applied by to the final difficulty value after all other calculations. + /// May be overridden via . + /// + public const double DEFAULT_DIFFICULTY_MULTIPLIER = 1.06; + /// /// The number of sections with the highest strains, which the peak strain reductions will apply to. /// This is done in order to decrease their impact on the overall difficulty of the map for this skill. @@ -28,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// /// The final multiplier to be applied to after all other calculations. /// - protected virtual double DifficultyMultiplier => 1.06; + protected virtual double DifficultyMultiplier => DEFAULT_DIFFICULTY_MULTIPLIER; protected OsuStrainSkill(Mod[] mods) : base(mods) From 0211fe7ae8d1e472158c2544d14b2ba6dff70daf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 29 Jun 2022 16:29:14 +0900 Subject: [PATCH 270/803] Fix exception + possible div-by-0 --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 6218d1a54a..c3b7834009 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double relevantCountGreat = Math.Max(0, countGreat - relevantTotalDiff); double relevantCountOk = Math.Max(0, countOk - Math.Max(0, relevantTotalDiff - countGreat)); double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk)); - double relevantAccuracy = (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (attributes.SpeedNoteCount * 6.0); + double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (attributes.SpeedNoteCount * 6.0); // Scale the speed value with accuracy and OD. speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 38cfc1ece5..3e0f2a5638 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -56,6 +56,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public double RelevantNoteCount() { + if (objectStrains.Count == 0) + return 0; + double maxStrain = objectStrains.Max(); return objectStrains.Aggregate((total, next) => total + (1.0 / (1.0 + Math.Exp(-(next / maxStrain * 12.0 - 6.0))))); } From e6ccca804527f8032cf20dd0e8a84eb409b27735 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 29 Jun 2022 16:29:17 +0900 Subject: [PATCH 271/803] Fix inspection --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index fab3fa8425..75d9469da3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; - double speedNotes = (skills[2] as Speed).RelevantNoteCount(); + double speedNotes = ((Speed)skills[2]).RelevantNoteCount(); double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; From ad95f037de2ead8f5da46f4f1201df5bb8cdde3a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 29 Jun 2022 16:42:53 +0900 Subject: [PATCH 272/803] Prevent another case of potential div-by-0 --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 3e0f2a5638..76d93f536c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -60,6 +60,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return 0; double maxStrain = objectStrains.Max(); + + if (maxStrain == 0) + return 0; + return objectStrains.Aggregate((total, next) => total + (1.0 / (1.0 + Math.Exp(-(next / maxStrain * 12.0 - 6.0))))); } } From 6d91c0f375369c182312bfde644cae26443339a9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 29 Jun 2022 16:57:10 +0900 Subject: [PATCH 273/803] Resolve inspection issue --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 76d93f536c..a156726f94 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double DifficultyMultiplier => 1.04; private readonly double greatWindow; - private List objectStrains = new List(); + private readonly List objectStrains = new List(); public Speed(Mod[] mods, double hitWindowGreat) : base(mods) From 0e0e9968aca4027b8730a97942fdd71995dc4fd6 Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Wed, 29 Jun 2022 01:23:35 -0700 Subject: [PATCH 274/803] Split ball and followcircle into default/legacy files --- .../TestSceneSliderApplication.cs | 5 +- .../Objects/Drawables/DrawableSlider.cs | 19 ++-- .../Drawables/DrawableSliderBall.cs} | 100 +++--------------- .../Skinning/Default/DefaultFollowCircle.cs | 33 ++++++ .../Skinning/Default/DefaultSliderBall.cs | 60 +++++++++++ .../Skinning/Legacy/LegacyFollowCircle.cs | 22 ++++ .../Legacy/OsuLegacySkinTransformer.cs | 10 +- 7 files changed, 147 insertions(+), 102 deletions(-) rename osu.Game.Rulesets.Osu/{Skinning/Default/SliderBall.cs => Objects/Drawables/DrawableSliderBall.cs} (67%) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs index 2981f1164d..08a62fe3ae 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs @@ -14,7 +14,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osu.Game.Tests.Visual; using osuTK; @@ -93,10 +92,10 @@ namespace osu.Game.Rulesets.Osu.Tests }); AddStep("set accent white", () => dho.AccentColour.Value = Color4.White); - AddAssert("ball is white", () => dho.ChildrenOfType().Single().AccentColour == Color4.White); + AddAssert("ball is white", () => dho.ChildrenOfType().Single().AccentColour == Color4.White); AddStep("set accent red", () => dho.AccentColour.Value = Color4.Red); - AddAssert("ball is red", () => dho.ChildrenOfType().Single().AccentColour == Color4.Red); + AddAssert("ball is red", () => dho.ChildrenOfType().Single().AccentColour == Color4.Red); } private Slider prepareObject(Slider slider) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 9e3b762690..91bb7f95f6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -29,7 +29,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public DrawableSliderHead HeadCircle => headContainer.Child; public DrawableSliderTail TailCircle => tailContainer.Child; - public SliderBall Ball { get; private set; } + [Cached] + public DrawableSliderBall Ball { get; private set; } + public SkinnableDrawable Body { get; private set; } /// @@ -60,6 +62,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public DrawableSlider([CanBeNull] Slider s = null) : base(s) { + Ball = new DrawableSliderBall + { + GetInitialHitAction = () => HeadCircle.HitAction, + BypassAutoSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0 + }; } [BackgroundDependencyLoader] @@ -73,13 +82,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables repeatContainer = new Container { RelativeSizeAxes = Axes.Both }, headContainer = new Container { RelativeSizeAxes = Axes.Both }, OverlayElementContainer = new Container { RelativeSizeAxes = Axes.Both, }, - Ball = new SliderBall(this) - { - GetInitialHitAction = () => HeadCircle.HitAction, - BypassAutoSizeAxes = Axes.Both, - AlwaysPresent = true, - Alpha = 0 - }, + Ball, slidingSample = new PausableSkinnableSound { Looping = true } }; diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs similarity index 67% rename from osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs index 389e9343e7..7bde60b39d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs @@ -7,24 +7,21 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; -namespace osu.Game.Rulesets.Osu.Skinning.Default +namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour + public class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour { public Func GetInitialHitAction; @@ -34,14 +31,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default set => ball.Colour = value; } - private readonly Drawable followCircle; - private readonly Drawable fullSizeFollowCircle; - private readonly DrawableSlider drawableSlider; - private readonly Drawable ball; + private Drawable followCircle; + private Drawable followCircleReceptor; + private DrawableSlider drawableSlider; + private Drawable ball; - public SliderBall(DrawableSlider drawableSlider) + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableSlider) { - this.drawableSlider = drawableSlider; + this.drawableSlider = (DrawableSlider)drawableSlider; Origin = Anchor.Centre; @@ -49,15 +47,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default Children = new[] { - followCircle = new FollowCircleContainer + followCircle = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()) { Origin = Anchor.Centre, Anchor = Anchor.Centre, RelativeSizeAxes = Axes.Both, Alpha = 0, - Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()), }, - fullSizeFollowCircle = new CircularContainer + followCircleReceptor = new CircularContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -106,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default tracking = value; - fullSizeFollowCircle.Scale = new Vector2(tracking ? 2.4f : 1f); + followCircleReceptor.Scale = new Vector2(tracking ? 2.4f : 1f); followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint); followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint); @@ -167,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default // in valid time range Time.Current >= drawableSlider.HitObject.StartTime && Time.Current < drawableSlider.HitObject.EndTime && // in valid position range - lastScreenSpaceMousePosition.HasValue && fullSizeFollowCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && + lastScreenSpaceMousePosition.HasValue && followCircleReceptor.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && // valid action (actions?.Any(isValidTrackingAction) ?? false); @@ -205,74 +202,5 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI); lastPosition = Position; } - - private class FollowCircleContainer : CircularContainer - { - public override bool HandlePositionalInput => true; - } - - public class DefaultFollowCircle : CompositeDrawable - { - public DefaultFollowCircle() - { - RelativeSizeAxes = Axes.Both; - - InternalChild = new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = 5, - BorderColour = Color4.Orange, - Blending = BlendingParameters.Additive, - Child = new Box - { - Colour = Color4.Orange, - RelativeSizeAxes = Axes.Both, - Alpha = 0.2f, - } - }; - } - } - - public class DefaultSliderBall : CompositeDrawable - { - private Box box; - - [BackgroundDependencyLoader] - private void load(DrawableHitObject drawableObject, ISkinSource skin) - { - var slider = (DrawableSlider)drawableObject; - - RelativeSizeAxes = Axes.Both; - - float radius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS; - - InternalChild = new CircularContainer - { - Masking = true, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(radius / OsuHitObject.OBJECT_RADIUS), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Blending = BlendingParameters.Additive, - BorderThickness = 10, - BorderColour = Color4.White, - Alpha = 1, - Child = box = new Box - { - Blending = BlendingParameters.Additive, - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - AlwaysPresent = true, - Alpha = 0 - } - }; - - slider.Tracking.BindValueChanged(trackingChanged, true); - } - - private void trackingChanged(ValueChangedEvent tracking) => - box.FadeTo(tracking.NewValue ? 0.3f : 0.05f, 200, Easing.OutQuint); - } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs new file mode 100644 index 0000000000..8211448705 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -0,0 +1,33 @@ +// 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.Framework.Graphics.Shapes; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public class DefaultFollowCircle : CompositeDrawable + { + public DefaultFollowCircle() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = 5, + BorderColour = Color4.Orange, + Blending = BlendingParameters.Additive, + Child = new Box + { + Colour = Color4.Orange, + RelativeSizeAxes = Axes.Both, + Alpha = 0.2f, + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs new file mode 100644 index 0000000000..47308375e6 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.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. + +#nullable disable + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public class DefaultSliderBall : CompositeDrawable + { + private Box box; + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableObject, ISkinSource skin) + { + var slider = (DrawableSlider)drawableObject; + + RelativeSizeAxes = Axes.Both; + + float radius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS; + + InternalChild = new CircularContainer + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(radius / OsuHitObject.OBJECT_RADIUS), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, + BorderThickness = 10, + BorderColour = Color4.White, + Alpha = 1, + Child = box = new Box + { + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + AlwaysPresent = true, + Alpha = 0 + } + }; + + slider.Tracking.BindValueChanged(trackingChanged, true); + } + + private void trackingChanged(ValueChangedEvent tracking) => + box.FadeTo(tracking.NewValue ? 0.3f : 0.05f, 200, Easing.OutQuint); + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs new file mode 100644 index 0000000000..b8a559ce07 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.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.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Rulesets.Osu.Skinning.Legacy +{ + public class LegacyFollowCircle : CompositeDrawable + { + public LegacyFollowCircle(Drawable animationContent) + { + // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x + animationContent.Scale *= 0.5f; + animationContent.Anchor = Anchor.Centre; + animationContent.Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + InternalChild = animationContent; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index e7d28a9bd7..885a2c12fb 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -41,11 +41,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return this.GetAnimation(component.LookupName, false, false); case OsuSkinComponents.SliderFollowCircle: - var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true); - if (followCircle != null) - // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x - followCircle.Scale *= 0.5f; - return followCircle; + var followCircleContent = this.GetAnimation("sliderfollowcircle", true, true, true); + if (followCircleContent != null) + return new LegacyFollowCircle(followCircleContent); + + return null; case OsuSkinComponents.SliderBall: var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: ""); From b092e6937a6d9fbec870b2804470935b0392466b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 18:51:07 +0900 Subject: [PATCH 275/803] Guard against ruleset icon creation failures to avoid whole game death --- osu.Game/Rulesets/RulesetSelector.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetSelector.cs b/osu.Game/Rulesets/RulesetSelector.cs index 35244eb86e..1909e741f0 100644 --- a/osu.Game/Rulesets/RulesetSelector.cs +++ b/osu.Game/Rulesets/RulesetSelector.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Allocation; +using osu.Framework.Logging; namespace osu.Game.Rulesets { @@ -19,7 +20,16 @@ namespace osu.Game.Rulesets private void load() { foreach (var r in Rulesets.AvailableRulesets) - AddItem(r); + { + try + { + AddItem(r); + } + catch + { + Logger.Log($"Could not create ruleset icon for {r.Name}. Please check for an update.", level: LogLevel.Error); + } + } } } } From 6c64cea057121dbb49458eb21e69c7ae9ddfbb72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 18:56:15 +0900 Subject: [PATCH 276/803] Catch a second location --- osu.Game/Overlays/Settings/SettingsFooter.cs | 24 +++++++++++++------- osu.Game/Rulesets/RulesetSelector.cs | 6 ++--- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs index 2f182d537f..b9ac1aa53b 100644 --- a/osu.Game/Overlays/Settings/SettingsFooter.cs +++ b/osu.Game/Overlays/Settings/SettingsFooter.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -32,16 +33,23 @@ namespace osu.Game.Overlays.Settings foreach (var ruleset in rulesets.AvailableRulesets) { - var icon = new ConstrainedIconContainer + try { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Icon = ruleset.CreateInstance().CreateIcon(), - Colour = Color4.Gray, - Size = new Vector2(20), - }; + var icon = new ConstrainedIconContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Icon = ruleset.CreateInstance().CreateIcon(), + Colour = Color4.Gray, + Size = new Vector2(20), + }; - modes.Add(icon); + modes.Add(icon); + } + catch + { + Logger.Log($"Could not create ruleset icon for {ruleset.Name}. Please check for an update from the developer.", level: LogLevel.Error); + } } Children = new Drawable[] diff --git a/osu.Game/Rulesets/RulesetSelector.cs b/osu.Game/Rulesets/RulesetSelector.cs index 1909e741f0..701e60eec9 100644 --- a/osu.Game/Rulesets/RulesetSelector.cs +++ b/osu.Game/Rulesets/RulesetSelector.cs @@ -19,15 +19,15 @@ namespace osu.Game.Rulesets [BackgroundDependencyLoader] private void load() { - foreach (var r in Rulesets.AvailableRulesets) + foreach (var ruleset in Rulesets.AvailableRulesets) { try { - AddItem(r); + AddItem(ruleset); } catch { - Logger.Log($"Could not create ruleset icon for {r.Name}. Please check for an update.", level: LogLevel.Error); + Logger.Log($"Could not create ruleset icon for {ruleset.Name}. Please check for an update from the developer.", level: LogLevel.Error); } } } From e3eba58cb644773817e57b2f94ae9e9781bbd913 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 29 Jun 2022 13:37:17 +0300 Subject: [PATCH 277/803] Catch in triangles intro --- osu.Game/Screens/Menu/IntroTriangles.cs | 34 ++++++++++++++----------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index d59a07a350..ad098ae8df 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using System.Collections.Generic; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -14,6 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; +using osu.Framework.Logging; using osu.Framework.Utils; using osu.Framework.Timing; using osu.Game.Graphics; @@ -340,24 +340,28 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - var modes = new List(); - - foreach (var ruleset in rulesets.AvailableRulesets) - { - var icon = new ConstrainedIconContainer - { - Icon = ruleset.CreateInstance().CreateIcon(), - Size = new Vector2(30), - }; - - modes.Add(icon); - } - AutoSizeAxes = Axes.Both; - Children = modes; Anchor = Anchor.Centre; Origin = Anchor.Centre; + + foreach (var ruleset in rulesets.AvailableRulesets) + { + try + { + var icon = new ConstrainedIconContainer + { + Icon = ruleset.CreateInstance().CreateIcon(), + Size = new Vector2(30), + }; + + Add(icon); + } + catch + { + Logger.Log($"Could not create ruleset icon for {ruleset.Name}. Please check for an update from the developer.", level: LogLevel.Error); + } + } } } From 87e3b4418113831cb612109a4813bfac115b8e86 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 29 Jun 2022 13:37:02 +0300 Subject: [PATCH 278/803] Ensure icon is loaded inside try-catch --- osu.Game/Overlays/Settings/SettingsFooter.cs | 56 ++++++++++---------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs index b9ac1aa53b..db0dc8fd5e 100644 --- a/osu.Game/Overlays/Settings/SettingsFooter.cs +++ b/osu.Game/Overlays/Settings/SettingsFooter.cs @@ -3,7 +3,6 @@ #nullable disable -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Development; using osu.Framework.Graphics; @@ -29,7 +28,33 @@ namespace osu.Game.Overlays.Settings Direction = FillDirection.Vertical; Padding = new MarginPadding { Top = 20, Bottom = 30, Horizontal = SettingsPanel.CONTENT_MARGINS }; - var modes = new List(); + FillFlowContainer modes; + + Children = new Drawable[] + { + modes = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(5), + Padding = new MarginPadding { Bottom = 10 }, + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = game.Name, + Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold), + }, + new BuildDisplay(game.Version, DebugUtils.IsDebugBuild) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } + }; foreach (var ruleset in rulesets.AvailableRulesets) { @@ -51,33 +76,6 @@ namespace osu.Game.Overlays.Settings Logger.Log($"Could not create ruleset icon for {ruleset.Name}. Please check for an update from the developer.", level: LogLevel.Error); } } - - Children = new Drawable[] - { - new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Direction = FillDirection.Full, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = modes, - Spacing = new Vector2(5), - Padding = new MarginPadding { Bottom = 10 }, - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = game.Name, - Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold), - }, - new BuildDisplay(game.Version, DebugUtils.IsDebugBuild) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - } - }; } private class BuildDisplay : OsuAnimatedButton From 32af4e41eaa3805ddc2ffb94909f289877aa1996 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 20:56:01 +0900 Subject: [PATCH 279/803] Add back thread safety and locking as required --- osu.Game/Database/RealmAccess.cs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 715066f32d..728cd5c7c3 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -446,8 +446,12 @@ namespace osu.Game.Database { Func action = realm => query(realm).QueryAsyncWithNotifications(callback); - // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. - notificationsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); + lock (notificationsResetMap) + { + // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. + notificationsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); + } + return RegisterCustomSubscription(action); } @@ -543,7 +547,12 @@ namespace osu.Game.Database { unsubscriptionAction?.Dispose(); customSubscriptionsResetMap.Remove(action); - notificationsResetMap.Remove(action); + + lock (notificationsResetMap) + { + notificationsResetMap.Remove(action); + } + total_subscriptions.Value--; } } @@ -805,6 +814,9 @@ namespace osu.Game.Database /// An which should be disposed to end the blocking section. public IDisposable BlockAllOperations() { + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + if (isDisposed) throw new ObjectDisposedException(nameof(RealmAccess)); @@ -816,9 +828,6 @@ namespace osu.Game.Database if (hasInitialisedOnce) { - if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); - syncContext = SynchronizationContext.Current; // Before disposing the update context, clean up all subscriptions. From 18d465eff7dc30e4d9c5ad5ee6a0d277e2012dc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 21:02:23 +0900 Subject: [PATCH 280/803] Guard against `NaN` star difficulty results --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 2 +- osu.Game/Beatmaps/StarDifficulty.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index ef0fa36b16..611b47c120 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -207,7 +207,7 @@ namespace osu.Game.Beatmaps if (cancellationToken.IsCancellationRequested) return; - var starDifficulty = task.GetResultSafely(); + StarDifficulty? starDifficulty = task.GetResultSafely(); if (starDifficulty != null) bindable.Value = starDifficulty.Value; diff --git a/osu.Game/Beatmaps/StarDifficulty.cs b/osu.Game/Beatmaps/StarDifficulty.cs index e042f1c698..6aac275a6a 100644 --- a/osu.Game/Beatmaps/StarDifficulty.cs +++ b/osu.Game/Beatmaps/StarDifficulty.cs @@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps /// public StarDifficulty([NotNull] DifficultyAttributes attributes) { - Stars = attributes.StarRating; + Stars = double.IsFinite(attributes.StarRating) ? attributes.StarRating : 0; MaxCombo = attributes.MaxCombo; Attributes = attributes; // Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...) @@ -46,7 +46,7 @@ namespace osu.Game.Beatmaps /// public StarDifficulty(double starDifficulty, int maxCombo) { - Stars = starDifficulty; + Stars = double.IsFinite(starDifficulty) ? starDifficulty : 0; MaxCombo = maxCombo; Attributes = null; } From 7cb4e32c17ffb61c91c7b5d9e84c1f0729839e33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 22:45:19 +0900 Subject: [PATCH 281/803] Add one more lock to appease CI --- osu.Game/Database/RealmAccess.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 59a235dbc2..8cf9bb4a47 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -880,8 +880,11 @@ namespace osu.Game.Database try { - foreach (var action in notificationsResetMap.Values) - action(); + lock (notificationsResetMap) + { + foreach (var action in notificationsResetMap.Values) + action(); + } } finally { From fd0d8b1ce3d5062b349484436d035d35a13a0e88 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Wed, 29 Jun 2022 22:50:47 +0900 Subject: [PATCH 282/803] Add button state, fix async issues, watch replay method Most borrowed from `ReplayDownloadButton` --- osu.Game/Screens/Play/FailOverlay.cs | 8 +- osu.Game/Screens/Play/Player.cs | 7 +- .../Screens/Play/SaveFailedScoreButton.cs | 93 +++++++++++++++---- 3 files changed, 86 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index 881791271b..75b06f859d 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -4,6 +4,8 @@ #nullable disable using System; +using System.Threading.Tasks; +using osu.Game.Scoring; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osuTK; @@ -18,7 +20,7 @@ namespace osu.Game.Screens.Play { public class FailOverlay : GameplayMenuOverlay { - public Action SaveReplay; + public Func> SaveReplay; public override string Header => "failed"; public override string Description => "you're dead, try again?"; @@ -52,10 +54,8 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Horizontal, Children = new Drawable[] { - new SaveFailedScoreButton() + new SaveFailedScoreButton(SaveReplay) { - OnSave = SaveReplay, - RelativeSizeAxes = Axes.Y, Width = 300 }, } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8223ed1e3d..f20cb74db6 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1043,7 +1043,7 @@ namespace osu.Game.Screens.Play return base.OnExiting(e); } - private async void saveFailedReplay() + private async Task saveFailedReplay() { Score.ScoreInfo.Passed = false; Score.ScoreInfo.Rank = ScoreRank.F; @@ -1051,12 +1051,15 @@ namespace osu.Game.Screens.Play try { - await ImportScore(scoreCopy).ConfigureAwait(false); + await ImportScore(scoreCopy); } catch (Exception ex) { Logger.Error(ex, @"Score import failed!"); + return null; } + + return scoreCopy.ScoreInfo; } /// diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index 3efd8fa4a3..b5727487dc 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -1,11 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable disable + using System; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Threading; +using osu.Game.Scoring; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online; @@ -15,14 +20,25 @@ namespace osu.Game.Screens.Play { public class SaveFailedScoreButton : CompositeDrawable { - public Action? OnSave; + public Func> SaveReplay; + private Task saveReplayAsync; + private ScoreInfo score; - protected readonly Bindable State = new Bindable(); + private ScheduledDelegate saveScoreDelegate; + + protected readonly Bindable State = new Bindable(); private DownloadButton button; private ShakeContainer shakeContainer; - public SaveFailedScoreButton() + public SaveFailedScoreButton(Func> sr) + { + Size = new Vector2(50, 30); + SaveReplay = sr; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuGame game) { InternalChild = shakeContainer = new ShakeContainer { @@ -32,17 +48,16 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, } }; - Size = new Vector2(50, 30); - } - [BackgroundDependencyLoader] - private void load() - { button.Action = () => { switch (State.Value) { - case DownloadState.LocallyAvailable: + case ImportState.Imported: + game?.PresentScore(score, ScorePresentType.Gameplay); + break; + + case ImportState.Importing: shakeContainer.Shake(); break; @@ -52,23 +67,61 @@ namespace osu.Game.Screens.Play } }; State.BindValueChanged(updateTooltip, true); + State.BindValueChanged(state => + { + switch (state.NewValue) + { + case ImportState.Imported: + button.State.Value = DownloadState.LocallyAvailable; + break; + + case ImportState.Importing: + button.State.Value = DownloadState.Importing; + break; + + case ImportState.Failed: + button.State.Value = DownloadState.NotDownloaded; + break; + } + }, true); } private void saveScore() { - if (State.Value != DownloadState.LocallyAvailable) - OnSave?.Invoke(); + State.Value = ImportState.Importing; + saveReplayAsync = Task.Run(SaveReplay); - State.Value = DownloadState.LocallyAvailable; - button.State.Value = DownloadState.LocallyAvailable; + saveScoreDelegate = new ScheduledDelegate(() => + { + if (saveReplayAsync?.IsCompleted != true) + // If the asynchronous preparation has not completed, keep repeating this delegate. + return; + + saveScoreDelegate?.Cancel(); + + score = saveReplayAsync.GetAwaiter().GetResult(); + + State.Value = score != null ? ImportState.Imported : ImportState.Failed; + }, Time.Current, 50); + + Scheduler.Add(saveScoreDelegate); } - private void updateTooltip(ValueChangedEvent state) + private void updateTooltip(ValueChangedEvent state) { switch (state.NewValue) { - case DownloadState.LocallyAvailable: - button.TooltipText = @"Score saved"; + case ImportState.Imported: + button.TooltipText = @"Watch replay"; + break; + + case ImportState.Importing: + button.TooltipText = @"Importing score"; + break; + + case ImportState.Failed: + button.State.Value = DownloadState.NotDownloaded; + button.TooltipText = @"Import failed, click button to re-import"; break; default: @@ -76,5 +129,13 @@ namespace osu.Game.Screens.Play break; } } + + public enum ImportState + { + NotImported, + Failed, + Importing, + Imported + } } } From 582175c3a429e36e4d58798693518c0fffc3508d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 22:57:52 +0900 Subject: [PATCH 283/803] 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 2ef3bb2dd1..584fe0a3ef 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 ab690b22a6..ff223f5107 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 a84fc40a45..b8a4aca02e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 13108155d3e4ef0ca2e19d520e9ddfcf2c8a4b95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 03:43:56 +0900 Subject: [PATCH 284/803] Fix crash on visual esting `TestSceneChatOverlay` due to null user --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index d2d9b9a9e5..247ea52648 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -39,7 +39,8 @@ namespace osu.Game.Tests.Visual.Online private TestChatOverlay chatOverlay; private ChannelManager channelManager; - private APIUser testUser; + private readonly APIUser testUser = new APIUser { Username = "test user", Id = 5071479 }; + private Channel[] testChannels; private Channel testChannel1 => testChannels[0]; @@ -51,7 +52,6 @@ namespace osu.Game.Tests.Visual.Online [SetUp] public void SetUp() => Schedule(() => { - testUser = new APIUser { Username = "test user", Id = 5071479 }; testChannels = Enumerable.Range(1, 10).Select(createPublicChannel).ToArray(); Child = new DependencyProvidingContainer From 46cbbf5c6eb240b416ee24445ed8aa5beeead54a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 04:45:02 +0900 Subject: [PATCH 285/803] Fix flaky song select placeholder test by changing ruleset post-display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Was failing occasionally due to the beatmap present operation causing the test's ruleset change to undo. ```csharp TearDown : System.TimeoutException : "wait for placeholder visible" timed out --TearDown at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0() at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered) at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition) --- End of stack trace from previous location --- at osu.Framework.Testing.TestSceneTestRunner.TestRunner.RunTestBlocking(TestScene test) at osu.Game.Tests.Visual.OsuTestScene.OsuTestSceneTestRunner.RunTestBlocking(TestScene test) in /opt/buildagent/work/ecd860037212ac52/osu.Game/Tests/Visual/OsuTestScene.cs:line 503 at osu.Framework.Testing.TestScene.RunTestsFromNUnit() ------- Stdout: ------- [runtime] 2022-06-29 10:36:45 [verbose]: 💨 Class: TestScenePlaySongSelect [runtime] 2022-06-29 10:36:45 [verbose]: 🔶 Test: TestPlaceholderConvertSetting [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #1 exit all screens [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #2 reset defaults [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #3 delete all beatmaps [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #4 change ruleset to 2 [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #5 import test map for ruleset 0 [database] 2022-06-29 10:36:45 [verbose]: [efc1a] Beginning import from unknown... [database] 2022-06-29 10:36:45 [verbose]: [efc1a] Import successfully completed! [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #6 wait for imported to arrive in carousel [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #7 change convert setting [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #8 create song select [runtime] 2022-06-29 10:36:45 [verbose]: ScreenTestScene screen changed → TestScenePlaySongSelect+TestSongSelect [runtime] 2022-06-29 10:36:45 [verbose]: 📺 ScreenTestScene(OsuScreenStack)#338(depth:1) loading TestScenePlaySongSelect+TestSongSelect#263 [runtime] 2022-06-29 10:36:45 [verbose]: decoupled ruleset transferred ("" -> "osu!catch") [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #9 wait for present [runtime] 2022-06-29 10:36:45 [verbose]: 📺 ScreenTestScene(OsuScreenStack)#338(depth:1) entered TestScenePlaySongSelect+TestSongSelect#263 [runtime] 2022-06-29 10:36:45 [verbose]: 📺 BackgroundScreenStack#328(depth:1) loading BackgroundScreenBeatmap#338 [runtime] 2022-06-29 10:36:45 [verbose]: 📺 BackgroundScreenStack#328(depth:1) entered BackgroundScreenBeatmap#338 [runtime] 2022-06-29 10:36:45 [verbose]: Song select updating selection with beatmap:null ruleset:fruits [runtime] 2022-06-29 10:36:45 [verbose]: Song select changing beatmap from "please load a beatmap! - no beatmaps available!" to "null" [runtime] 2022-06-29 10:36:45 [verbose]: Song select working beatmap updated to Some Artist 0 - Some Song (set id 6224) ece7b702-895c-4f15-892f-05e4ff5e9a24 (Some Guy 3) [Normal 6224000 (length 0:50, bpm 106.7)] [runtime] 2022-06-29 10:36:45 [verbose]: Song select updating selection with beatmap:null ruleset:osu [runtime] 2022-06-29 10:36:45 [verbose]: decoupled ruleset transferred ("osu!catch" -> "osu!") [runtime] 2022-06-29 10:36:45 [verbose]: Song select updating selection with beatmap:74232aa2-2a1f-4920-b643-e85976838251 ruleset:osu [runtime] 2022-06-29 10:36:45 [verbose]: Song select decided to ensurePlayingSelected [runtime] 2022-06-29 10:36:45 [verbose]: Game-wide working beatmap updated to Some Artist 0 - Some Song (set id 6224) ece7b702-895c-4f15-892f-05e4ff5e9a24 (Some Guy 3) [Normal 6224000 (length 0:50, bpm 106.7)] [runtime] 2022-06-29 10:36:45 [debug]: Focus changed from nothing to SeekLimitedSearchTextBox. [network] 2022-06-29 10:36:45 [verbose]: Failing request osu.Game.Online.API.Requests.GetBeatmapRequest (System.InvalidOperationException: DummyAPIAccess cannot process this request.) [runtime] 2022-06-29 10:36:45 [verbose]: ✔️ 16 repetitions [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #10 wait for carousel loaded [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #11 wait for placeholder visible [runtime] 2022-06-29 10:36:55 [verbose]: 💥 Failed (on attempt 1,459) [runtime] 2022-06-29 10:36:55 [verbose]: ⏳ Currently loading components (0) [runtime] 2022-06-29 10:36:55 [verbose]: 🧵 Task schedulers [runtime] 2022-06-29 10:36:55 [verbose]: LoadComponentsAsync (standard) concurrency:4 running:0 pending:0 [runtime] 2022-06-29 10:36:55 [verbose]: LoadComponentsAsync (long load) concurrency:4 running:0 pending:0 [runtime] 2022-06-29 10:36:55 [verbose]: 🎱 Thread pool [runtime] 2022-06-29 10:36:55 [verbose]: worker: min 1 max 32,767 available 32,766 [runtime] 2022-06-29 10:36:55 [verbose]: completion: min 1 max 1,000 available 1,000 [runtime] 2022-06-29 10:36:55 [debug]: Focus on "SeekLimitedSearchTextBox" no longer valid as a result of unfocusIfNoLongerValid. [runtime] 2022-06-29 10:36:55 [debug]: Focus changed from SeekLimitedSearchTextBox to nothing. ``` --- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 6d881555da..6896b442e0 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -100,12 +100,13 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestPlaceholderConvertSetting() { - changeRuleset(2); addRulesetImportStep(0); AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, false)); createSongSelect(); + changeRuleset(2); + AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); AddStep("click link in placeholder", () => getPlaceholder().ChildrenOfType().First().TriggerClick()); From 8171deec494d8b1a2f9794453bb54f8d88821ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 Jun 2022 22:38:06 +0200 Subject: [PATCH 286/803] Attempt to fix editor navigation test failures again First of all, stop relying that `InputManager` will successfully press the gameplay test button, because it won't if it's obscured by the notification overlay. Closing the overlay in a loop doesn't work because there's a chance that a notification is posted, then all overlays are closed, then another notification is posted, and so the button still isn't clickable. Instead, use the `TestGameplay()` method directly. Secondly, the notifications will still block `EditorPlayerLoader` from transitioning to `EditorPlayer`. Here we have no other choice than to aggressively dismiss notifications every spin of the relevant until step and hope we eventually progress to `EditorPlayer`. --- .../Editing/TestSceneEditorNavigation.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs index a21ef6f897..327d581e37 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs @@ -6,18 +6,14 @@ using NUnit.Framework; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; -using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Select; using osu.Game.Tests.Resources; -using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { @@ -36,20 +32,21 @@ namespace osu.Game.Tests.Visual.Editing () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.IsLoaded); - AddUntilStep("wait for completion notification", () => Game.Notifications.ChildrenOfType().Count() == 1); - AddStep("dismiss notifications", () => Game.Notifications.Hide()); AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); - AddStep("test gameplay", () => + AddStep("test gameplay", () => ((Editor)Game.ScreenStack.CurrentScreen).TestGameplay()); + + AddUntilStep("wait for player", () => { - var testGameplayButton = this.ChildrenOfType().Single(); - InputManager.MoveMouseTo(testGameplayButton); - InputManager.Click(MouseButton.Left); + // notifications may fire at almost any inopportune time and cause annoying test failures. + // relentlessly attempt to dismiss any and all interfering overlays, which includes notifications. + // this is theoretically not foolproof, but it's the best that can be done here. + Game.CloseAllOverlays(); + return Game.ScreenStack.CurrentScreen is EditorPlayer editorPlayer && editorPlayer.IsLoaded; }); - AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen is EditorPlayer editorPlayer && editorPlayer.IsLoaded); AddAssert("current ruleset is osu!", () => Game.Ruleset.Value.Equals(new OsuRuleset().RulesetInfo)); AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield())); From c5d31f50cd4053adedaa4787edc0fcb1222bbc6e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 30 Jun 2022 11:21:15 +0900 Subject: [PATCH 287/803] Attempt to fix flaky TestSceneAutoplay test --- .../Visual/Gameplay/TestSceneAutoplay.cs | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 019bfe322e..47c8dc0f8d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -6,10 +6,10 @@ using System.ComponentModel; using System.Linq; using osu.Framework.Testing; -using osu.Game.Beatmaps.Timing; using osu.Game.Graphics.Containers; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.Break; @@ -31,19 +31,20 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void AddCheckSteps() { + // It doesn't matter which ruleset is used - this beatmap is only used for reference. + var beatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0); AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2)); - seekToBreak(0); + + seekTo(beatmap.Beatmap.Breaks[0].StartTime); AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting); AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType().Single().AccuracyDisplay.Current.Value == 1); + AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000)); AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); - seekToBreak(0); - seekToBreak(1); - - AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); - + seekTo(beatmap.Beatmap.HitObjects[^1].GetEndTime()); AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true); AddAssert("score has combo", () => getResultsScreen().Score.Combo > 100); @@ -58,12 +59,18 @@ namespace osu.Game.Tests.Visual.Gameplay ResultsScreen getResultsScreen() => Stack.CurrentScreen as ResultsScreen; } - private void seekToBreak(int breakIndex) + private void seekTo(double time) { - AddStep($"seek to break {breakIndex}", () => Player.GameplayClockContainer.Seek(destBreak().StartTime)); - AddUntilStep("wait for seek to complete", () => Player.DrawableRuleset.FrameStableClock.CurrentTime >= destBreak().StartTime); + AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time)); - BreakPeriod destBreak() => Beatmap.Value.Beatmap.Breaks.ElementAt(breakIndex); + // Prevent test timeouts by seeking in 10 second increments. + for (double t = 0; t < time; t += 10000) + { + double expectedTime = t; + AddUntilStep($"current time >= {t}", () => Player.DrawableRuleset.FrameStableClock.CurrentTime >= expectedTime); + } + + AddUntilStep("wait for seek to complete", () => Player.DrawableRuleset.FrameStableClock.CurrentTime >= time); } } } From edc4ace17e4f703b6ee0b362e943388d25262748 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 30 Jun 2022 11:52:29 +0900 Subject: [PATCH 288/803] Attach DT with NC, SD with PF, and Cinema with Autoplay --- osu.Game/Rulesets/Ruleset.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index e098472999..c1ec6c30ef 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets break; case ModPerfect: - value |= LegacyMods.Perfect; + value |= LegacyMods.Perfect | LegacyMods.SuddenDeath; break; case ModSuddenDeath: @@ -151,7 +151,7 @@ namespace osu.Game.Rulesets break; case ModNightcore: - value |= LegacyMods.Nightcore; + value |= LegacyMods.Nightcore | LegacyMods.DoubleTime; break; case ModDoubleTime: @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets break; case ModCinema: - value |= LegacyMods.Cinema; + value |= LegacyMods.Cinema | LegacyMods.Autoplay; break; case ModAutoplay: From 6c2d02fefdd5498553ca65b7546f717546e06c0d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 30 Jun 2022 12:37:03 +0900 Subject: [PATCH 289/803] Fix tests --- .../CatchLegacyModConversionTest.cs | 15 +++++++++------ .../ManiaLegacyModConversionTest.cs | 9 ++++++--- .../OsuLegacyModConversionTest.cs | 9 ++++++--- .../TaikoLegacyModConversionTest.cs | 15 +++++++++------ 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs index c65c9df9f9..b9d6f28228 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs @@ -24,21 +24,24 @@ namespace osu.Game.Rulesets.Catch.Tests new object[] { LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime) } }, new object[] { LegacyMods.Relax, new[] { typeof(CatchModRelax) } }, new object[] { LegacyMods.HalfTime, new[] { typeof(CatchModHalfTime) } }, - new object[] { LegacyMods.Nightcore, new[] { typeof(CatchModNightcore) } }, new object[] { LegacyMods.Flashlight, new[] { typeof(CatchModFlashlight) } }, new object[] { LegacyMods.Autoplay, new[] { typeof(CatchModAutoplay) } }, - new object[] { LegacyMods.Perfect, new[] { typeof(CatchModPerfect) } }, - new object[] { LegacyMods.Cinema, new[] { typeof(CatchModCinema) } }, new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) } } }; + [TestCaseSource(nameof(catch_mod_mapping))] + [TestCase(LegacyMods.Cinema, new[] { typeof(CatchModCinema) })] + [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(CatchModCinema) })] + [TestCase(LegacyMods.Nightcore, new[] { typeof(CatchModNightcore) })] + [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModNightcore) })] + [TestCase(LegacyMods.Perfect, new[] { typeof(CatchModPerfect) })] + [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(CatchModPerfect) })] + public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); + [TestCaseSource(nameof(catch_mod_mapping))] [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(CatchModCinema) })] [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModNightcore) })] [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(CatchModPerfect) })] - public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); - - [TestCaseSource(nameof(catch_mod_mapping))] public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods); protected override Ruleset CreateRuleset() => new CatchRuleset(); diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs index accae29ffe..9dee861e66 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs @@ -23,10 +23,8 @@ namespace osu.Game.Rulesets.Mania.Tests new object[] { LegacyMods.SuddenDeath, new[] { typeof(ManiaModSuddenDeath) } }, new object[] { LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime) } }, new object[] { LegacyMods.HalfTime, new[] { typeof(ManiaModHalfTime) } }, - new object[] { LegacyMods.Nightcore, new[] { typeof(ManiaModNightcore) } }, new object[] { LegacyMods.Flashlight, new[] { typeof(ManiaModFlashlight) } }, new object[] { LegacyMods.Autoplay, new[] { typeof(ManiaModAutoplay) } }, - new object[] { LegacyMods.Perfect, new[] { typeof(ManiaModPerfect) } }, new object[] { LegacyMods.Key4, new[] { typeof(ManiaModKey4) } }, new object[] { LegacyMods.Key5, new[] { typeof(ManiaModKey5) } }, new object[] { LegacyMods.Key6, new[] { typeof(ManiaModKey6) } }, @@ -34,7 +32,6 @@ namespace osu.Game.Rulesets.Mania.Tests new object[] { LegacyMods.Key8, new[] { typeof(ManiaModKey8) } }, new object[] { LegacyMods.FadeIn, new[] { typeof(ManiaModFadeIn) } }, new object[] { LegacyMods.Random, new[] { typeof(ManiaModRandom) } }, - new object[] { LegacyMods.Cinema, new[] { typeof(ManiaModCinema) } }, new object[] { LegacyMods.Key9, new[] { typeof(ManiaModKey9) } }, new object[] { LegacyMods.KeyCoop, new[] { typeof(ManiaModDualStages) } }, new object[] { LegacyMods.Key1, new[] { typeof(ManiaModKey1) } }, @@ -45,12 +42,18 @@ namespace osu.Game.Rulesets.Mania.Tests }; [TestCaseSource(nameof(mania_mod_mapping))] + [TestCase(LegacyMods.Cinema, new[] { typeof(ManiaModCinema) })] [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(ManiaModCinema) })] + [TestCase(LegacyMods.Nightcore, new[] { typeof(ManiaModNightcore) })] [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(ManiaModNightcore) })] + [TestCase(LegacyMods.Perfect, new[] { typeof(ManiaModPerfect) })] [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(ManiaModPerfect) })] public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); [TestCaseSource(nameof(mania_mod_mapping))] + [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(ManiaModCinema) })] + [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(ManiaModNightcore) })] + [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(ManiaModPerfect) })] public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods); protected override Ruleset CreateRuleset() => new ManiaRuleset(); diff --git a/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs index db9872b152..01d83b55e6 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs @@ -25,24 +25,27 @@ namespace osu.Game.Rulesets.Osu.Tests new object[] { LegacyMods.DoubleTime, new[] { typeof(OsuModDoubleTime) } }, new object[] { LegacyMods.Relax, new[] { typeof(OsuModRelax) } }, new object[] { LegacyMods.HalfTime, new[] { typeof(OsuModHalfTime) } }, - new object[] { LegacyMods.Nightcore, new[] { typeof(OsuModNightcore) } }, new object[] { LegacyMods.Flashlight, new[] { typeof(OsuModFlashlight) } }, new object[] { LegacyMods.Autoplay, new[] { typeof(OsuModAutoplay) } }, new object[] { LegacyMods.SpunOut, new[] { typeof(OsuModSpunOut) } }, new object[] { LegacyMods.Autopilot, new[] { typeof(OsuModAutopilot) } }, - new object[] { LegacyMods.Perfect, new[] { typeof(OsuModPerfect) } }, - new object[] { LegacyMods.Cinema, new[] { typeof(OsuModCinema) } }, new object[] { LegacyMods.Target, new[] { typeof(OsuModTarget) } }, new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(OsuModHardRock), typeof(OsuModDoubleTime) } } }; [TestCaseSource(nameof(osu_mod_mapping))] + [TestCase(LegacyMods.Cinema, new[] { typeof(OsuModCinema) })] [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(OsuModCinema) })] + [TestCase(LegacyMods.Nightcore, new[] { typeof(OsuModNightcore) })] [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(OsuModNightcore) })] + [TestCase(LegacyMods.Perfect, new[] { typeof(OsuModPerfect) })] [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(OsuModPerfect) })] public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); [TestCaseSource(nameof(osu_mod_mapping))] + [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(OsuModCinema) })] + [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(OsuModNightcore) })] + [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(OsuModPerfect) })] public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods); protected override Ruleset CreateRuleset() => new OsuRuleset(); diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs index 3553cb27dc..c86f8cb8d2 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs @@ -24,22 +24,25 @@ namespace osu.Game.Rulesets.Taiko.Tests new object[] { LegacyMods.DoubleTime, new[] { typeof(TaikoModDoubleTime) } }, new object[] { LegacyMods.Relax, new[] { typeof(TaikoModRelax) } }, new object[] { LegacyMods.HalfTime, new[] { typeof(TaikoModHalfTime) } }, - new object[] { LegacyMods.Nightcore, new[] { typeof(TaikoModNightcore) } }, new object[] { LegacyMods.Flashlight, new[] { typeof(TaikoModFlashlight) } }, new object[] { LegacyMods.Autoplay, new[] { typeof(TaikoModAutoplay) } }, - new object[] { LegacyMods.Perfect, new[] { typeof(TaikoModPerfect) } }, new object[] { LegacyMods.Random, new[] { typeof(TaikoModRandom) } }, - new object[] { LegacyMods.Cinema, new[] { typeof(TaikoModCinema) } }, new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(TaikoModHardRock), typeof(TaikoModDoubleTime) } } }; + [TestCaseSource(nameof(taiko_mod_mapping))] + [TestCase(LegacyMods.Cinema, new[] { typeof(TaikoModCinema) })] + [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(TaikoModCinema) })] + [TestCase(LegacyMods.Nightcore, new[] { typeof(TaikoModNightcore) })] + [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(TaikoModNightcore) })] + [TestCase(LegacyMods.Perfect, new[] { typeof(TaikoModPerfect) })] + [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(TaikoModPerfect) })] + public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); + [TestCaseSource(nameof(taiko_mod_mapping))] [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(TaikoModCinema) })] [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(TaikoModNightcore) })] [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(TaikoModPerfect) })] - public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); - - [TestCaseSource(nameof(taiko_mod_mapping))] public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods); protected override Ruleset CreateRuleset() => new TaikoRuleset(); From c6520de749daeaaf6b7e1e8962d7c46d25491aa4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 30 Jun 2022 14:24:49 +0900 Subject: [PATCH 290/803] Ensure PlaylistItem beatmap is not null --- osu.Game/Online/Rooms/PlaylistItem.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index a0711d9ded..2213311c67 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -72,7 +72,7 @@ namespace osu.Game.Online.Rooms /// In many cases, this will *not* contain any usable information apart from OnlineID. /// [JsonIgnore] - public IBeatmapInfo Beatmap { get; set; } = null!; + public IBeatmapInfo Beatmap { get; private set; } [JsonIgnore] public IBindable Valid => valid; @@ -81,6 +81,7 @@ namespace osu.Game.Online.Rooms [JsonConstructor] private PlaylistItem() + : this(new APIBeatmap()) { } From 0abb400f6400e8ceca40bbf34950aa1671a06eb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 15:14:25 +0900 Subject: [PATCH 291/803] Add reproduction of transaction not being exited correctly --- osu.Game.Tests/Database/RealmLiveTests.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 00a667521d..fd1f564f59 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -91,6 +91,25 @@ namespace osu.Game.Tests.Database Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); } + [Test] + public void TestTransactionRolledBackOnException() + { + RunTestWithRealm((realm, _) => + { + var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); + + realm.Run(r => r.Write(_ => r.Add(beatmap))); + + var liveBeatmap = beatmap.ToLive(realm); + + Assert.Throws(() => liveBeatmap.PerformWrite(l => throw new InvalidOperationException())); + Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); + + liveBeatmap.PerformWrite(l => l.Hidden = true); + Assert.IsTrue(liveBeatmap.PerformRead(l => l.Hidden)); + }); + } + [Test] public void TestScopedReadWithoutContext() { From 78d86fd3ffef5f61b06ea557ab777a0990174762 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 15:11:22 +0900 Subject: [PATCH 292/803] Fix `PerformWrite` not rolling back transaction on exception --- osu.Game/Database/RealmLive.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 72de747807..9c871a3929 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -104,9 +104,12 @@ namespace osu.Game.Database PerformRead(t => { - var transaction = t.Realm.BeginWrite(); - perform(t); - transaction.Commit(); + using (var transaction = t.Realm.BeginWrite()) + { + perform(t); + transaction.Commit(); + } + RealmLiveStatistics.WRITES.Value++; }); } From 356c0501ec25596ed62dc0c8ae0c4af3cf56878f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 15:40:44 +0900 Subject: [PATCH 293/803] Guard again potential nulls in `RealmNamedFileUsage` Hopefully help in figuring out https://sentry.ppy.sh/organizations/ppy/issues/3679/?project=2&query=user%3A%22id%3A10078484%22 --- osu.Game/Models/RealmNamedFileUsage.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Models/RealmNamedFileUsage.cs b/osu.Game/Models/RealmNamedFileUsage.cs index 0f6f439d73..c4310c4edb 100644 --- a/osu.Game/Models/RealmNamedFileUsage.cs +++ b/osu.Game/Models/RealmNamedFileUsage.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 JetBrains.Annotations; using osu.Framework.Testing; using osu.Game.Database; @@ -19,8 +20,8 @@ namespace osu.Game.Models public RealmNamedFileUsage(RealmFile file, string filename) { - File = file; - Filename = filename; + File = file ?? throw new ArgumentNullException(nameof(file)); + Filename = filename ?? throw new ArgumentNullException(nameof(filename)); } [UsedImplicitly] From 10934450cc2e35ccd02a70278ec073c433057881 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 16:02:46 +0900 Subject: [PATCH 294/803] Fix directory cleanup occurring too early during realm tests --- .../NonVisual/CustomDataDirectoryTest.cs | 47 +++++++------------ 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index a803974d30..216bd0fd3c 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -44,8 +44,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestCustomDirectory() { - string customPath = prepareCustomPath(); - + using (prepareCustomPath(out string customPath)) using (var host = new CustomTestHeadlessGameHost()) { using (var storageConfig = new StorageConfigManager(host.InitialStorage)) @@ -63,7 +62,6 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); - cleanupPath(customPath); } } } @@ -71,8 +69,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestSubDirectoryLookup() { - string customPath = prepareCustomPath(); - + using (prepareCustomPath(out string customPath)) using (var host = new CustomTestHeadlessGameHost()) { using (var storageConfig = new StorageConfigManager(host.InitialStorage)) @@ -97,7 +94,6 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); - cleanupPath(customPath); } } } @@ -105,8 +101,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestMigration() { - string customPath = prepareCustomPath(); - + using (prepareCustomPath(out string customPath)) using (var host = new CustomTestHeadlessGameHost()) { try @@ -173,7 +168,6 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); - cleanupPath(customPath); } } } @@ -181,9 +175,8 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestMigrationBetweenTwoTargets() { - string customPath = prepareCustomPath(); - string customPath2 = prepareCustomPath(); - + using (prepareCustomPath(out string customPath)) + using (prepareCustomPath(out string customPath2)) using (var host = new CustomTestHeadlessGameHost()) { try @@ -205,8 +198,6 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); - cleanupPath(customPath); - cleanupPath(customPath2); } } } @@ -214,8 +205,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestMigrationToSameTargetFails() { - string customPath = prepareCustomPath(); - + using (prepareCustomPath(out string customPath)) using (var host = new CustomTestHeadlessGameHost()) { try @@ -228,7 +218,6 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); - cleanupPath(customPath); } } } @@ -236,9 +225,8 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestMigrationFailsOnExistingData() { - string customPath = prepareCustomPath(); - string customPath2 = prepareCustomPath(); - + using (prepareCustomPath(out string customPath)) + using (prepareCustomPath(out string customPath2)) using (var host = new CustomTestHeadlessGameHost()) { try @@ -267,8 +255,6 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); - cleanupPath(customPath); - cleanupPath(customPath2); } } } @@ -276,8 +262,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestMigrationToNestedTargetFails() { - string customPath = prepareCustomPath(); - + using (prepareCustomPath(out string customPath)) using (var host = new CustomTestHeadlessGameHost()) { try @@ -298,7 +283,6 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); - cleanupPath(customPath); } } } @@ -306,8 +290,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestMigrationToSeeminglyNestedTarget() { - string customPath = prepareCustomPath(); - + using (prepareCustomPath(out string customPath)) using (var host = new CustomTestHeadlessGameHost()) { try @@ -328,7 +311,6 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); - cleanupPath(customPath); } } } @@ -343,14 +325,17 @@ namespace osu.Game.Tests.NonVisual return path; } - private static string prepareCustomPath() => Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, $"custom-path-{Guid.NewGuid()}"); + private static IDisposable prepareCustomPath(out string path) + { + path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, $"custom-path-{Guid.NewGuid()}"); + return new InvokeOnDisposal(path, cleanupPath); + } private static void cleanupPath(string path) { try { - if (Directory.Exists(path)) - Directory.Delete(path, true); + if (Directory.Exists(path)) Directory.Delete(path, true); } catch { From 82134ad1a668bb5a35b7d3feae7404090bce4bf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 16:46:28 +0900 Subject: [PATCH 295/803] Allow `null` parameter to `GetWorkingBeatmap` for now --- osu.Game/Beatmaps/BeatmapManager.cs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 26f538f65b..5f83bbb0dd 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -436,20 +436,23 @@ namespace osu.Game.Beatmaps /// The beatmap to lookup. /// Whether to force a refetch from the database to ensure is up-to-date. /// A instance correlating to the provided . - public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, bool refetch = false) + public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo? beatmapInfo, bool refetch = false) { - // Detached sets don't come with files. - // If we seem to be missing files, now is a good time to re-fetch. - if (refetch || beatmapInfo.IsManaged || beatmapInfo.BeatmapSet?.Files.Count == 0) + if (beatmapInfo != null) { - workingBeatmapCache.Invalidate(beatmapInfo); + // Detached sets don't come with files. + // If we seem to be missing files, now is a good time to re-fetch. + if (refetch || beatmapInfo.IsManaged || beatmapInfo.BeatmapSet?.Files.Count == 0) + { + workingBeatmapCache.Invalidate(beatmapInfo); - Guid id = beatmapInfo.ID; - beatmapInfo = Realm.Run(r => r.Find(id)?.Detach()) ?? beatmapInfo; + Guid id = beatmapInfo.ID; + beatmapInfo = Realm.Run(r => r.Find(id)?.Detach()) ?? beatmapInfo; + } + + Debug.Assert(beatmapInfo.IsManaged != true); } - Debug.Assert(beatmapInfo.IsManaged != true); - return workingBeatmapCache.GetWorkingBeatmap(beatmapInfo); } From e34c2f0acac243ecc4a53cc256b02468b032f97e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 16:47:26 +0900 Subject: [PATCH 296/803] Remove unnecessary `nullable-enable` --- osu.Game/Beatmaps/BeatmapUpdater.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index caef094701..62a6fc7293 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable enable - using System.Diagnostics; using System.Linq; using System.Threading.Tasks; From ef42c6ecdf95f659faf31980c2c27e7ebc3a1a94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 16:51:31 +0900 Subject: [PATCH 297/803] Add missing xmldoc --- osu.Game/Beatmaps/IWorkingBeatmapCache.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/IWorkingBeatmapCache.cs b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs index 36c53fce99..e28f802e78 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs @@ -28,6 +28,9 @@ namespace osu.Game.Beatmaps /// The beatmap info to invalidate any cached entries for. void Invalidate(BeatmapInfo beatmapInfo); + /// + /// Fired whenever a is invalidated. + /// event Action OnInvalidated; } } From 0698471627b384e63af9cfd4e47a7f28d08abd6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 17:03:19 +0900 Subject: [PATCH 298/803] Move `BeatmapOnlineLookupQueue` to inside `BeatmapUpdater` --- osu.Game/Beatmaps/BeatmapManager.cs | 6 ++---- osu.Game/Beatmaps/BeatmapUpdater.cs | 21 ++++++++++++++++++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 5f83bbb0dd..e16a87eb50 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -41,7 +41,6 @@ namespace osu.Game.Beatmaps private readonly BeatmapImporter beatmapImporter; private readonly WorkingBeatmapCache workingBeatmapCache; - private readonly BeatmapOnlineLookupQueue? onlineBeatmapLookupQueue; private readonly BeatmapUpdater? beatmapUpdater; public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, @@ -56,8 +55,7 @@ namespace osu.Game.Beatmaps if (difficultyCache == null) throw new ArgumentNullException(nameof(difficultyCache), "Difficulty cache must be provided if online lookups are required."); - onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue(api, storage); - beatmapUpdater = new BeatmapUpdater(this, onlineBeatmapLookupQueue, difficultyCache); + beatmapUpdater = new BeatmapUpdater(this, difficultyCache, api, storage); } var userResources = new RealmFileStore(realm, storage).Store; @@ -474,7 +472,7 @@ namespace osu.Game.Beatmaps public void Dispose() { - onlineBeatmapLookupQueue?.Dispose(); + beatmapUpdater?.Dispose(); } #endregion diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 62a6fc7293..d800b09a2b 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -1,10 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Platform; using osu.Game.Database; +using osu.Game.Online.API; using osu.Game.Rulesets.Objects; using Realms; @@ -13,17 +17,18 @@ namespace osu.Game.Beatmaps /// /// Handles all processing required to ensure a local beatmap is in a consistent state with any changes. /// - public class BeatmapUpdater + public class BeatmapUpdater : IDisposable { private readonly IWorkingBeatmapCache workingBeatmapCache; private readonly BeatmapOnlineLookupQueue onlineLookupQueue; private readonly BeatmapDifficultyCache difficultyCache; - public BeatmapUpdater(IWorkingBeatmapCache workingBeatmapCache, BeatmapOnlineLookupQueue onlineLookupQueue, BeatmapDifficultyCache difficultyCache) + public BeatmapUpdater(IWorkingBeatmapCache workingBeatmapCache, BeatmapDifficultyCache difficultyCache, IAPIProvider api, Storage storage) { this.workingBeatmapCache = workingBeatmapCache; - this.onlineLookupQueue = onlineLookupQueue; this.difficultyCache = difficultyCache; + + onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); } /// @@ -81,5 +86,15 @@ namespace osu.Game.Beatmaps return endTime - startTime; } + + #region Implementation of IDisposable + + public void Dispose() + { + if (onlineLookupQueue.IsNotNull()) + onlineLookupQueue.Dispose(); + } + + #endregion } } From aab4dcefbd060b514c79765ceee8e46ad321515e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 17:13:26 +0900 Subject: [PATCH 299/803] Remove unnecessary invalidation handling flow --- osu.Game/Beatmaps/IWorkingBeatmapCache.cs | 9 --------- osu.Game/Screens/Select/SongSelect.cs | 18 ------------------ 2 files changed, 27 deletions(-) diff --git a/osu.Game/Beatmaps/IWorkingBeatmapCache.cs b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs index e28f802e78..3eb33f10d6 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs @@ -1,10 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using System; - namespace osu.Game.Beatmaps { public interface IWorkingBeatmapCache @@ -27,10 +23,5 @@ namespace osu.Game.Beatmaps /// /// The beatmap info to invalidate any cached entries for. void Invalidate(BeatmapInfo beatmapInfo); - - /// - /// Fired whenever a is invalidated. - /// - event Action OnInvalidated; } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 73bcabba31..7abcbfca42 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -296,24 +296,8 @@ namespace osu.Game.Screens.Select base.LoadComplete(); modSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(ModSelect); - - beatmaps.OnInvalidated += workingBeatmapInvalidated; } - private void workingBeatmapInvalidated(WorkingBeatmap working) => Scheduler.AddOnce(w => - { - // The global beatmap may have already been updated (ie. by the editor). - // Only perform the actual switch if we still need to. - if (w == Beatmap.Value) - { - // Not sure if this refresh is required. - var beatmapInfo = beatmaps.QueryBeatmap(b => b.ID == w.BeatmapInfo.ID); - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); - } - - updateComponentFromBeatmap(Beatmap.Value); - }, working); - /// /// Creates the buttons to be displayed in the footer. /// @@ -719,8 +703,6 @@ namespace osu.Game.Screens.Select music.TrackChanged -= ensureTrackLooping; modSelectOverlayRegistration?.Dispose(); - - beatmaps.OnInvalidated -= workingBeatmapInvalidated; } /// From 2a7321086548a6f99a35c4517357ff77d3ab996c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 17:17:06 +0900 Subject: [PATCH 300/803] Add xmldoc and update parameter naming for `MemoryCachingComponent.Invalidate` flow --- osu.Game/Database/MemoryCachingComponent.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/MemoryCachingComponent.cs b/osu.Game/Database/MemoryCachingComponent.cs index 215050460b..104943bbae 100644 --- a/osu.Game/Database/MemoryCachingComponent.cs +++ b/osu.Game/Database/MemoryCachingComponent.cs @@ -40,11 +40,15 @@ namespace osu.Game.Database return computed; } - protected void Invalidate(Func invalidationFunction) + /// + /// Invalidate all entries matching a provided predicate. + /// + /// The predicate to decide which keys should be invalidated. + protected void Invalidate(Func matchKeyPredicate) { foreach (var kvp in cache) { - if (invalidationFunction(kvp.Key)) + if (matchKeyPredicate(kvp.Key)) cache.TryRemove(kvp.Key, out _); } } From 7ab83359359602f44440ef56ff8f2968ec1a29fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 17:42:44 +0900 Subject: [PATCH 301/803] Fix various multiplayer test failures due to not waiting for ongoing operation As seen at https://teamcity.ppy.sh/buildConfiguration/Osu_Build/1138?hideProblemsFromDependencies=false&hideTestsFromDependencies=false&expandBuildChangesSection=true&expandBuildTestsSection=true. --- .../Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs | 2 ++ osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs | 2 ++ .../Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs | 1 + 3 files changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 86da9dc33d..190b2128f9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.Play; @@ -146,6 +147,7 @@ namespace osu.Game.Tests.Visual.Multiplayer if (mods != null) AddStep($"set mods to {string.Join(",", mods.Select(m => m.Acronym))}", () => songSelect.AsNonNull().Mods.Value = mods); + AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); AddStep("select other beatmap", () => songSelect.AsNonNull().FinaliseSelection(beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index fe584fe3da..e436f418ab 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -104,6 +104,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); BeatmapInfo otherBeatmap = null; + AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); @@ -119,6 +120,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); + AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 285258d7c0..ba79a96692 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -110,6 +110,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap)); AddStep("set mods", () => SelectedMods.Value = new[] { new TaikoModDoubleTime() }); + AddUntilStep("wait for ongoing operation to complete", () => !OnlinePlayDependencies.OngoingOperationTracker.InProgress.Value); AddStep("confirm selection", () => songSelect.FinaliseSelection()); AddUntilStep("song select exited", () => !songSelect.IsCurrentScreen()); From 84ecd524c851f6fe0679a7b3b164832a017626fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 17:58:43 +0900 Subject: [PATCH 302/803] Move wait steps upwards --- .../Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 190b2128f9..32fd7724e0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -140,6 +140,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for song select", () => (songSelect = CurrentSubScreen as Screens.Select.SongSelect) != null); AddUntilStep("wait for loaded", () => songSelect.AsNonNull().BeatmapSetsLoaded); + AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); if (ruleset != null) AddStep($"set {ruleset.Name} ruleset", () => songSelect.AsNonNull().Ruleset.Value = ruleset); @@ -147,7 +148,6 @@ namespace osu.Game.Tests.Visual.Multiplayer if (mods != null) AddStep($"set mods to {string.Join(",", mods.Select(m => m.Acronym))}", () => songSelect.AsNonNull().Mods.Value = mods); - AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); AddStep("select other beatmap", () => songSelect.AsNonNull().FinaliseSelection(beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index ba79a96692..ab4f9c37b2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -107,10 +107,12 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); AddStep("select beatmap", () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == new TaikoRuleset().LegacyID))); + AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap)); + AddUntilStep("wait for ongoing operation to complete", () => !OnlinePlayDependencies.OngoingOperationTracker.InProgress.Value); + AddStep("set mods", () => SelectedMods.Value = new[] { new TaikoModDoubleTime() }); - AddUntilStep("wait for ongoing operation to complete", () => !OnlinePlayDependencies.OngoingOperationTracker.InProgress.Value); AddStep("confirm selection", () => songSelect.FinaliseSelection()); AddUntilStep("song select exited", () => !songSelect.IsCurrentScreen()); From 33209ecd254f40f3d4dcc64be9fa7b470ac872a9 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 30 Jun 2022 19:51:58 +0900 Subject: [PATCH 303/803] remove useless value change --- osu.Game/Screens/Play/SaveFailedScoreButton.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index b5727487dc..9a30e23ddd 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -66,7 +66,6 @@ namespace osu.Game.Screens.Play break; } }; - State.BindValueChanged(updateTooltip, true); State.BindValueChanged(state => { switch (state.NewValue) @@ -84,6 +83,7 @@ namespace osu.Game.Screens.Play break; } }, true); + State.BindValueChanged(updateTooltip, true); } private void saveScore() @@ -120,7 +120,6 @@ namespace osu.Game.Screens.Play break; case ImportState.Failed: - button.State.Value = DownloadState.NotDownloaded; button.TooltipText = @"Import failed, click button to re-import"; break; From e50e0f733975cbc43a2626a1c8aa407a57634a8e Mon Sep 17 00:00:00 2001 From: andy840119 Date: Thu, 30 Jun 2022 23:05:09 +0800 Subject: [PATCH 304/803] Remove the nullable disable annotate. --- osu.Game/Utils/BatteryInfo.cs | 2 -- osu.Game/Utils/ColourUtils.cs | 2 -- osu.Game/Utils/FormatUtils.cs | 2 -- osu.Game/Utils/HumanizerUtils.cs | 2 -- osu.Game/Utils/IDeepCloneable.cs | 2 -- osu.Game/Utils/LegacyRandom.cs | 2 -- osu.Game/Utils/LegacyUtils.cs | 2 -- osu.Game/Utils/NamingUtils.cs | 2 -- osu.Game/Utils/PeriodTracker.cs | 2 -- osu.Game/Utils/StatelessRNG.cs | 2 -- osu.Game/Utils/ZipUtils.cs | 2 -- 11 files changed, 22 deletions(-) diff --git a/osu.Game/Utils/BatteryInfo.cs b/osu.Game/Utils/BatteryInfo.cs index be12671b84..dd9b695e1f 100644 --- a/osu.Game/Utils/BatteryInfo.cs +++ b/osu.Game/Utils/BatteryInfo.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Utils { /// diff --git a/osu.Game/Utils/ColourUtils.cs b/osu.Game/Utils/ColourUtils.cs index 7e665fd9a7..515963971d 100644 --- a/osu.Game/Utils/ColourUtils.cs +++ b/osu.Game/Utils/ColourUtils.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Framework.Utils; using osuTK.Graphics; diff --git a/osu.Game/Utils/FormatUtils.cs b/osu.Game/Utils/FormatUtils.cs index 07a1879267..799dc75ca9 100644 --- a/osu.Game/Utils/FormatUtils.cs +++ b/osu.Game/Utils/FormatUtils.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using Humanizer; using osu.Framework.Extensions.LocalisationExtensions; diff --git a/osu.Game/Utils/HumanizerUtils.cs b/osu.Game/Utils/HumanizerUtils.cs index 27d3317b80..5b7c3630d9 100644 --- a/osu.Game/Utils/HumanizerUtils.cs +++ b/osu.Game/Utils/HumanizerUtils.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Globalization; using Humanizer; diff --git a/osu.Game/Utils/IDeepCloneable.cs b/osu.Game/Utils/IDeepCloneable.cs index a0a2548f6c..6877f346c4 100644 --- a/osu.Game/Utils/IDeepCloneable.cs +++ b/osu.Game/Utils/IDeepCloneable.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Utils { /// A generic interface for a deeply cloneable type. diff --git a/osu.Game/Utils/LegacyRandom.cs b/osu.Game/Utils/LegacyRandom.cs index ace8f8f65c..cf731aa91f 100644 --- a/osu.Game/Utils/LegacyRandom.cs +++ b/osu.Game/Utils/LegacyRandom.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Utils; diff --git a/osu.Game/Utils/LegacyUtils.cs b/osu.Game/Utils/LegacyUtils.cs index 400d3b3865..64306adf50 100644 --- a/osu.Game/Utils/LegacyUtils.cs +++ b/osu.Game/Utils/LegacyUtils.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; diff --git a/osu.Game/Utils/NamingUtils.cs b/osu.Game/Utils/NamingUtils.cs index 6b1be8885d..482e3d0954 100644 --- a/osu.Game/Utils/NamingUtils.cs +++ b/osu.Game/Utils/NamingUtils.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Text.RegularExpressions; diff --git a/osu.Game/Utils/PeriodTracker.cs b/osu.Game/Utils/PeriodTracker.cs index d8251f49ca..ba77702247 100644 --- a/osu.Game/Utils/PeriodTracker.cs +++ b/osu.Game/Utils/PeriodTracker.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; diff --git a/osu.Game/Utils/StatelessRNG.cs b/osu.Game/Utils/StatelessRNG.cs index 548aaa887f..3db632fc42 100644 --- a/osu.Game/Utils/StatelessRNG.cs +++ b/osu.Game/Utils/StatelessRNG.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; namespace osu.Game.Utils diff --git a/osu.Game/Utils/ZipUtils.cs b/osu.Game/Utils/ZipUtils.cs index d6ad3e132e..eb2d2d3b80 100644 --- a/osu.Game/Utils/ZipUtils.cs +++ b/osu.Game/Utils/ZipUtils.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.IO; using SharpCompress.Archives.Zip; From d94f700ab10d6ef1d835a725a83640ca1a569519 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Thu, 30 Jun 2022 23:06:49 +0800 Subject: [PATCH 305/803] Remove the nullable disable annotate in the test project. --- osu.Game.Tests/Utils/NamingUtilsTest.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Utils/NamingUtilsTest.cs b/osu.Game.Tests/Utils/NamingUtilsTest.cs index 2195933197..62e688db90 100644 --- a/osu.Game.Tests/Utils/NamingUtilsTest.cs +++ b/osu.Game.Tests/Utils/NamingUtilsTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Game.Utils; From 26de34da844ee45a50db4d2e31a60abf0afd8703 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Thu, 30 Jun 2022 23:20:40 +0800 Subject: [PATCH 306/803] Remove the nullable disable annotation. --- osu.Game/Extensions/CollectionExtensions.cs | 2 -- osu.Game/Extensions/DrawableExtensions.cs | 2 -- osu.Game/Extensions/LanguageExtensions.cs | 2 -- osu.Game/Extensions/TimeDisplayExtensions.cs | 2 -- osu.Game/Extensions/TypeExtensions.cs | 2 -- osu.Game/Extensions/WebRequestExtensions.cs | 2 -- 6 files changed, 12 deletions(-) diff --git a/osu.Game/Extensions/CollectionExtensions.cs b/osu.Game/Extensions/CollectionExtensions.cs index c573d169f1..473dc4b8f4 100644 --- a/osu.Game/Extensions/CollectionExtensions.cs +++ b/osu.Game/Extensions/CollectionExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; namespace osu.Game.Extensions diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index c2c00e342b..d1aba2bfe3 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Humanizer; using osu.Framework.Bindables; using osu.Framework.Graphics; diff --git a/osu.Game/Extensions/LanguageExtensions.cs b/osu.Game/Extensions/LanguageExtensions.cs index 3b1e9c7719..b67e7fb6fc 100644 --- a/osu.Game/Extensions/LanguageExtensions.cs +++ b/osu.Game/Extensions/LanguageExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Globalization; using osu.Game.Localisation; diff --git a/osu.Game/Extensions/TimeDisplayExtensions.cs b/osu.Game/Extensions/TimeDisplayExtensions.cs index 94871233ed..98633958ee 100644 --- a/osu.Game/Extensions/TimeDisplayExtensions.cs +++ b/osu.Game/Extensions/TimeDisplayExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using Humanizer; using osu.Framework.Extensions.LocalisationExtensions; diff --git a/osu.Game/Extensions/TypeExtensions.cs b/osu.Game/Extensions/TypeExtensions.cs index 6f160b0479..2e93c81758 100644 --- a/osu.Game/Extensions/TypeExtensions.cs +++ b/osu.Game/Extensions/TypeExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; diff --git a/osu.Game/Extensions/WebRequestExtensions.cs b/osu.Game/Extensions/WebRequestExtensions.cs index 79115c6023..50837a648d 100644 --- a/osu.Game/Extensions/WebRequestExtensions.cs +++ b/osu.Game/Extensions/WebRequestExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Globalization; using Newtonsoft.Json.Linq; using osu.Framework.IO.Network; From a5b1f1a688fe3d9ad0bd2e87b89f45e3c403ad6a Mon Sep 17 00:00:00 2001 From: andy840119 Date: Thu, 30 Jun 2022 23:23:14 +0800 Subject: [PATCH 307/803] mark the string as nullable. --- osu.Game/Extensions/TypeExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Extensions/TypeExtensions.cs b/osu.Game/Extensions/TypeExtensions.cs index 2e93c81758..072b18b0ba 100644 --- a/osu.Game/Extensions/TypeExtensions.cs +++ b/osu.Game/Extensions/TypeExtensions.cs @@ -21,7 +21,7 @@ namespace osu.Game.Extensions /// internal static string GetInvariantInstantiationInfo(this Type type) { - string assemblyQualifiedName = type.AssemblyQualifiedName; + string? assemblyQualifiedName = type.AssemblyQualifiedName; if (assemblyQualifiedName == null) throw new ArgumentException($"{type}'s assembly-qualified name is null. Ensure that it is a concrete type and not a generic type parameter.", nameof(type)); From 48047f2e58f103de50213d4be12c4abd33c47abf Mon Sep 17 00:00:00 2001 From: andy840119 Date: Thu, 30 Jun 2022 23:29:49 +0800 Subject: [PATCH 308/803] Move the null check in the outside. AddCursor() should not accept the null value. --- osu.Game/Extensions/WebRequestExtensions.cs | 2 +- osu.Game/Online/API/Requests/GetNewsRequest.cs | 10 +++++----- .../Online/API/Requests/SearchBeatmapSetsRequest.cs | 3 ++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Extensions/WebRequestExtensions.cs b/osu.Game/Extensions/WebRequestExtensions.cs index 50837a648d..a80b79f259 100644 --- a/osu.Game/Extensions/WebRequestExtensions.cs +++ b/osu.Game/Extensions/WebRequestExtensions.cs @@ -16,7 +16,7 @@ namespace osu.Game.Extensions /// public static void AddCursor(this WebRequest webRequest, Cursor cursor) { - cursor?.Properties.ForEach(x => + cursor.Properties.ForEach(x => { webRequest.AddParameter("cursor[" + x.Key + "]", (x.Value as JValue)?.ToString(CultureInfo.InvariantCulture) ?? x.Value.ToString()); }); diff --git a/osu.Game/Online/API/Requests/GetNewsRequest.cs b/osu.Game/Online/API/Requests/GetNewsRequest.cs index e1c9eefe30..64bed344bb 100644 --- a/osu.Game/Online/API/Requests/GetNewsRequest.cs +++ b/osu.Game/Online/API/Requests/GetNewsRequest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.IO.Network; using osu.Game.Extensions; @@ -11,9 +9,9 @@ namespace osu.Game.Online.API.Requests public class GetNewsRequest : APIRequest { private readonly int? year; - private readonly Cursor cursor; + private readonly Cursor? cursor; - public GetNewsRequest(int? year = null, Cursor cursor = null) + public GetNewsRequest(int? year = null, Cursor? cursor = null) { this.year = year; this.cursor = cursor; @@ -22,7 +20,9 @@ namespace osu.Game.Online.API.Requests protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); - req.AddCursor(cursor); + + if (cursor != null) + req.AddCursor(cursor); if (year.HasValue) req.AddParameter("year", year.Value.ToString()); diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 73f6fce4f9..082f9bb371 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -112,7 +112,8 @@ namespace osu.Game.Online.API.Requests req.AddParameter("nsfw", ExplicitContent == SearchExplicit.Show ? "true" : "false"); - req.AddCursor(cursor); + if (cursor != null) + req.AddCursor(cursor); return req; } From 3785027284ba1701af5b001d4dadd73bd96145d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 14:35:47 +0900 Subject: [PATCH 309/803] Update `OsuSliderBar` colours to not use transparency --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 3356153e17..493c8638cc 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -111,7 +111,6 @@ namespace osu.Game.Graphics.UserInterface RelativeSizeAxes = Axes.None, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Alpha = 0.5f, }, }, }, @@ -137,7 +136,7 @@ namespace osu.Game.Graphics.UserInterface { sample = audio.Samples.Get(@"UI/notch-tick"); AccentColour = colourProvider?.Highlight1 ?? colours.Pink; - BackgroundColour = colourProvider?.Background5 ?? colours.Pink.Opacity(0.5f); + BackgroundColour = colourProvider?.Background5 ?? colours.PinkDarker.Darken(1); } protected override void Update() From d54f7fc7289124ca119ac71df2b6cde2f621c392 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 19:54:08 +0900 Subject: [PATCH 310/803] Move slider range control to song select --- .../Graphics/UserInterface/OsuSliderBar.cs | 16 ++-- .../UserInterface/SongSelectSettings.cs | 36 -------- .../Select/DifficultyRangeFilterControl.cs | 90 +++++++++++++++++++ osu.Game/Screens/Select/FilterControl.cs | 5 ++ 4 files changed, 103 insertions(+), 44 deletions(-) create mode 100644 osu.Game/Screens/Select/DifficultyRangeFilterControl.cs diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 493c8638cc..1e472ed85b 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -38,8 +38,8 @@ namespace osu.Game.Graphics.UserInterface private T lastSampleValue; protected readonly Nub Nub; - private readonly Box leftBox; - private readonly Box rightBox; + protected readonly Box LeftBox; + protected readonly Box RightBox; private readonly Container nubContainer; public virtual LocalisableString TooltipText { get; private set; } @@ -57,7 +57,7 @@ namespace osu.Game.Graphics.UserInterface set { accentColour = value; - leftBox.Colour = value; + LeftBox.Colour = value; } } @@ -69,7 +69,7 @@ namespace osu.Game.Graphics.UserInterface set { backgroundColour = value; - rightBox.Colour = value; + RightBox.Colour = value; } } @@ -96,7 +96,7 @@ namespace osu.Game.Graphics.UserInterface CornerRadius = 5f, Children = new Drawable[] { - leftBox = new Box + LeftBox = new Box { Height = 5, EdgeSmoothness = new Vector2(0, 0.5f), @@ -104,7 +104,7 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - rightBox = new Box + RightBox = new Box { Height = 5, EdgeSmoothness = new Vector2(0, 0.5f), @@ -225,9 +225,9 @@ namespace osu.Game.Graphics.UserInterface protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - leftBox.Scale = new Vector2(Math.Clamp( + LeftBox.Scale = new Vector2(Math.Clamp( RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); - rightBox.Scale = new Vector2(Math.Clamp( + RightBox.Scale = new Vector2(Math.Clamp( DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, DrawWidth), 1); } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index 507e116723..708bee6fbd 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -3,13 +3,10 @@ #nullable disable -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; using osu.Game.Localisation; using osu.Game.Overlays.Mods.Input; @@ -17,20 +14,11 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface { public class SongSelectSettings : SettingsSubsection { - private Bindable minStars; - private Bindable maxStars; - protected override LocalisableString Header => UserInterfaceStrings.SongSelectHeader; [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - minStars = config.GetBindable(OsuSetting.DisplayStarsMinimum); - maxStars = config.GetBindable(OsuSetting.DisplayStarsMaximum); - - minStars.ValueChanged += min => maxStars.Value = Math.Max(min.NewValue, maxStars.Value); - maxStars.ValueChanged += max => minStars.Value = Math.Min(max.NewValue, minStars.Value); - Children = new Drawable[] { new SettingsCheckbox @@ -44,20 +32,6 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface LabelText = UserInterfaceStrings.ShowConvertedBeatmaps, Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), }, - new SettingsSlider - { - LabelText = UserInterfaceStrings.StarsMinimum, - Current = config.GetBindable(OsuSetting.DisplayStarsMinimum), - KeyboardStep = 0.1f, - Keywords = new[] { "minimum", "maximum", "star", "difficulty" } - }, - new SettingsSlider - { - LabelText = UserInterfaceStrings.StarsMaximum, - Current = config.GetBindable(OsuSetting.DisplayStarsMaximum), - KeyboardStep = 0.1f, - Keywords = new[] { "minimum", "maximum", "star", "difficulty" } - }, new SettingsEnumDropdown { LabelText = UserInterfaceStrings.RandomSelectionAlgorithm, @@ -71,15 +45,5 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface } }; } - - private class MaximumStarsSlider : StarsSlider - { - public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText; - } - - private class StarsSlider : OsuSliderBar - { - public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); - } } } diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs new file mode 100644 index 0000000000..6148d22838 --- /dev/null +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs @@ -0,0 +1,90 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; +using osuTK; + +namespace osu.Game.Screens.Select +{ + internal class DifficultyRangeFilterControl : CompositeDrawable + { + private Bindable minStars; + private Bindable maxStars; + + private StarsSlider lowerSlider; + private MaximumStarsSlider upperSlider; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + const float vertical_offset = 15; + + InternalChildren = new[] + { + new OsuSpriteText + { + Text = "Difficulty range", + Font = OsuFont.GetFont(size: 14), + }, + upperSlider = new MaximumStarsSlider + { + Current = config.GetBindable(OsuSetting.DisplayStarsMaximum), + KeyboardStep = 0.1f, + RelativeSizeAxes = Axes.X, + Y = vertical_offset, + }, + lowerSlider = new StarsSlider + { + Current = config.GetBindable(OsuSetting.DisplayStarsMinimum), + KeyboardStep = 0.1f, + RelativeSizeAxes = Axes.X, + Y = vertical_offset, + }, + lowerSlider.Nub.CreateProxy(), + upperSlider.Nub.CreateProxy(), + }; + + lowerSlider.LeftBox.Height = 6; + + minStars = config.GetBindable(OsuSetting.DisplayStarsMinimum); + maxStars = config.GetBindable(OsuSetting.DisplayStarsMaximum); + + lowerSlider.AccentColour = lowerSlider.BackgroundColour; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + minStars.ValueChanged += min => maxStars.Value = Math.Max(min.NewValue, maxStars.Value); + maxStars.ValueChanged += max => minStars.Value = Math.Min(max.NewValue, minStars.Value); + } + + private class MaximumStarsSlider : StarsSlider + { + public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText; + } + + private class StarsSlider : OsuSliderBar + { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Nub.ReceivePositionalInputAt(screenSpacePos); + + public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); + + public new Nub Nub => base.Nub; + public new Box LeftBox => base.LeftBox; + } + } +} diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index da764223a3..714de2487f 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -158,6 +158,11 @@ namespace osu.Game.Screens.Select Height = 20, Children = new Drawable[] { + new DifficultyRangeFilterControl + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + }, collectionDropdown = new CollectionFilterDropdown { Anchor = Anchor.TopRight, From 32139ac13fe90f00123d77d42b9d300afccd3746 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 14:22:15 +0900 Subject: [PATCH 311/803] Tidy up implementation and add basic visual test --- .../TestSceneDifficultyRangeFilterControl.cs | 26 ++++++++ .../Select/DifficultyRangeFilterControl.cs | 61 ++++++++++++++----- 2 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs new file mode 100644 index 0000000000..cc0909a835 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Screens.Select; +using osuTK; + +namespace osu.Game.Tests.Visual.SongSelect +{ + public class TestSceneDifficultyRangeFilterControl : OsuTestScene + { + [Test] + public void TestBasic() + { + Child = new DifficultyRangeFilterControl + { + Width = 200, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(3), + }; + } + } +} diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs index 6148d22838..156e373b6d 100644 --- a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics; @@ -15,13 +14,14 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Select { internal class DifficultyRangeFilterControl : CompositeDrawable { - private Bindable minStars; - private Bindable maxStars; + private Bindable lowerStars; + private Bindable upperStars; private StarsSlider lowerSlider; private MaximumStarsSlider upperSlider; @@ -45,46 +45,75 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.X, Y = vertical_offset, }, - lowerSlider = new StarsSlider + lowerSlider = new MinimumStarsSlider { Current = config.GetBindable(OsuSetting.DisplayStarsMinimum), KeyboardStep = 0.1f, RelativeSizeAxes = Axes.X, Y = vertical_offset, }, - lowerSlider.Nub.CreateProxy(), upperSlider.Nub.CreateProxy(), + lowerSlider.Nub.CreateProxy(), }; - lowerSlider.LeftBox.Height = 6; - - minStars = config.GetBindable(OsuSetting.DisplayStarsMinimum); - maxStars = config.GetBindable(OsuSetting.DisplayStarsMaximum); - - lowerSlider.AccentColour = lowerSlider.BackgroundColour; + lowerStars = config.GetBindable(OsuSetting.DisplayStarsMinimum); + upperStars = config.GetBindable(OsuSetting.DisplayStarsMaximum); } protected override void LoadComplete() { base.LoadComplete(); - minStars.ValueChanged += min => maxStars.Value = Math.Max(min.NewValue, maxStars.Value); - maxStars.ValueChanged += max => minStars.Value = Math.Min(max.NewValue, minStars.Value); + lowerStars.ValueChanged += min => upperStars.Value = Math.Max(min.NewValue + 0.1, upperStars.Value); + upperStars.ValueChanged += max => lowerStars.Value = Math.Min(max.NewValue - 0.1, lowerStars.Value); + } + + private class MinimumStarsSlider : StarsSlider + { + protected override void LoadComplete() + { + base.LoadComplete(); + + LeftBox.Height = 6; // hide any colour bleeding from overlap + + AccentColour = BackgroundColour; + BackgroundColour = Color4.Transparent; + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + base.ReceivePositionalInputAt(screenSpacePos) + && screenSpacePos.X <= Nub.ScreenSpaceDrawQuad.TopRight.X; + + public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText; } private class MaximumStarsSlider : StarsSlider { + protected override void LoadComplete() + { + base.LoadComplete(); + + RightBox.Height = 6; // just to match the left bar height really + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + base.ReceivePositionalInputAt(screenSpacePos) + && screenSpacePos.X >= Nub.ScreenSpaceDrawQuad.TopLeft.X; public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText; } private class StarsSlider : OsuSliderBar { - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Nub.ReceivePositionalInputAt(screenSpacePos); - public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); public new Nub Nub => base.Nub; - public new Box LeftBox => base.LeftBox; + + protected override void LoadComplete() + { + base.LoadComplete(); + Nub.Width = Nub.HEIGHT; + RangePadding = Nub.Width / 2; + } } } } From cb9947b1c92ac11b7735074044b6b5c2f9afdaa4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 14:41:32 +0900 Subject: [PATCH 312/803] Align song select components a bit better --- osu.Game/Screens/Select/DifficultyRangeFilterControl.cs | 2 +- osu.Game/Screens/Select/FilterControl.cs | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs index 156e373b6d..8e67a669e4 100644 --- a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - const float vertical_offset = 15; + const float vertical_offset = 13; InternalChildren = new[] { diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 714de2487f..a92b631100 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -155,20 +155,23 @@ namespace osu.Game.Screens.Select new Container { RelativeSizeAxes = Axes.X, - Height = 20, + Height = 40, Children = new Drawable[] { new DifficultyRangeFilterControl { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, RelativeSizeAxes = Axes.Both, - Width = 0.5f, + Width = 0.48f, }, collectionDropdown = new CollectionFilterDropdown { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, RelativeSizeAxes = Axes.X, - Width = 0.4f, + Y = 4, + Width = 0.5f, } } }, From 545df0a8e8d8d0ac75db6060266afbf998774597 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 15:17:26 +0900 Subject: [PATCH 313/803] Display difficulty on nub --- osu.Game/Graphics/UserInterface/Nub.cs | 2 +- .../Select/DifficultyRangeFilterControl.cs | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index 249fa2fbb2..7a3e54ddf1 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -19,7 +19,7 @@ using osu.Game.Overlays; namespace osu.Game.Graphics.UserInterface { - public class Nub : CompositeDrawable, IHasCurrentValue, IHasAccentColour + public class Nub : Container, IHasCurrentValue, IHasAccentColour { public const float HEIGHT = 15; diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs index 8e67a669e4..7115c59ebf 100644 --- a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs @@ -83,8 +83,6 @@ namespace osu.Game.Screens.Select public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && screenSpacePos.X <= Nub.ScreenSpaceDrawQuad.TopRight.X; - - public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText; } private class MaximumStarsSlider : StarsSlider @@ -99,12 +97,15 @@ namespace osu.Game.Screens.Select public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && screenSpacePos.X >= Nub.ScreenSpaceDrawQuad.TopLeft.X; - public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText; } private class StarsSlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); + private OsuSpriteText currentDisplay; + + public override LocalisableString TooltipText => Current.IsDefault + ? UserInterfaceStrings.NoLimit + : Current.Value.ToString(@"0.## stars"); public new Nub Nub => base.Nub; @@ -113,6 +114,20 @@ namespace osu.Game.Screens.Select base.LoadComplete(); Nub.Width = Nub.HEIGHT; RangePadding = Nub.Width / 2; + + Nub.Add(currentDisplay = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = -0.5f, + Colour = Color4.White, + Font = OsuFont.Torus.With(size: 10), + }); + + Current.BindValueChanged(current => + { + currentDisplay.Text = current.NewValue != Current.Default ? current.NewValue.ToString("N1") : "∞"; + }, true); } } } From f3af61213317ff8c735157bc0936fbf84d3ba9e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 15:17:53 +0900 Subject: [PATCH 314/803] Suggest removing difficulty filter if no matches found at song select --- .../Screens/Select/NoResultsPlaceholder.cs | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Select/NoResultsPlaceholder.cs b/osu.Game/Screens/Select/NoResultsPlaceholder.cs index e1a8e3e060..d018228e14 100644 --- a/osu.Game/Screens/Select/NoResultsPlaceholder.cs +++ b/osu.Game/Screens/Select/NoResultsPlaceholder.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Select Masking = true; CornerRadius = 10; - Width = 300; + Width = 400; AutoSizeAxes = Axes.Y; Anchor = Anchor.Centre; @@ -118,22 +118,35 @@ namespace osu.Game.Screens.Select textFlow.AddParagraph("No beatmaps match your filter criteria!"); textFlow.AddParagraph(string.Empty); - if (string.IsNullOrEmpty(filter?.SearchText)) + if (filter?.UserStarDifficulty.HasFilter == true) { - // TODO: Add realm queries to hint at which ruleset results are available in (and allow clicking to switch). - // TODO: Make this message more certain by ensuring the osu! beatmaps exist before suggesting. - if (filter?.Ruleset.OnlineID > 0 && !filter.AllowConvertedBeatmaps) + textFlow.AddParagraph("- Try "); + textFlow.AddLink("removing", () => { - textFlow.AddParagraph("Beatmaps may be available by "); - textFlow.AddLink("enabling automatic conversion", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); - textFlow.AddText("!"); - } + config.SetValue(OsuSetting.DisplayStarsMinimum, 0.0); + config.SetValue(OsuSetting.DisplayStarsMaximum, 10.1); + }); + + string lowerStar = filter.UserStarDifficulty.Min == null ? "∞" : $"{filter.UserStarDifficulty.Min:N1}"; + string upperStar = filter.UserStarDifficulty.Max == null ? "∞" : $"{filter.UserStarDifficulty.Max:N1}"; + + textFlow.AddText($" the {lowerStar}-{upperStar} star difficulty filter."); } - else + + // TODO: Add realm queries to hint at which ruleset results are available in (and allow clicking to switch). + // TODO: Make this message more certain by ensuring the osu! beatmaps exist before suggesting. + if (filter?.Ruleset.OnlineID > 0 && !filter.AllowConvertedBeatmaps) { - textFlow.AddParagraph("You can try "); + textFlow.AddParagraph("- Try"); + textFlow.AddLink(" enabling ", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); + textFlow.AddText("automatic conversion!"); + } + + if (!string.IsNullOrEmpty(filter?.SearchText)) + { + textFlow.AddParagraph("- Try "); textFlow.AddLink("searching online", LinkAction.SearchBeatmapSet, filter.SearchText); - textFlow.AddText(" for this query."); + textFlow.AddText($" for \"{filter.SearchText}\"."); } } From 3e0f4e7609106115905452202cdc6c538f07a3ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 15:28:55 +0900 Subject: [PATCH 315/803] Add test coverage of difficulty filter reset --- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 16 ++++++++++++++++ osu.Game/Screens/Select/NoResultsPlaceholder.cs | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 6896b442e0..159a3b1923 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -97,6 +97,22 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); } + [Test] + public void TestPlaceholderStarDifficulty() + { + addRulesetImportStep(0); + AddStep("change star filter", () => config.SetValue(OsuSetting.DisplayStarsMinimum, 10.0)); + + createSongSelect(); + + AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); + + AddStep("click link in placeholder", () => getPlaceholder().ChildrenOfType().First().TriggerClick()); + + AddUntilStep("star filter reset", () => config.Get(OsuSetting.DisplayStarsMinimum) == 0.0); + AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Hidden); + } + [Test] public void TestPlaceholderConvertSetting() { diff --git a/osu.Game/Screens/Select/NoResultsPlaceholder.cs b/osu.Game/Screens/Select/NoResultsPlaceholder.cs index d018228e14..5d5eafd2e6 100644 --- a/osu.Game/Screens/Select/NoResultsPlaceholder.cs +++ b/osu.Game/Screens/Select/NoResultsPlaceholder.cs @@ -135,7 +135,7 @@ namespace osu.Game.Screens.Select // TODO: Add realm queries to hint at which ruleset results are available in (and allow clicking to switch). // TODO: Make this message more certain by ensuring the osu! beatmaps exist before suggesting. - if (filter?.Ruleset.OnlineID > 0 && !filter.AllowConvertedBeatmaps) + if (filter?.Ruleset?.OnlineID > 0 && !filter.AllowConvertedBeatmaps) { textFlow.AddParagraph("- Try"); textFlow.AddLink(" enabling ", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); From 58e25a3a01369996401c1ed931667e19bf6d85b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 15:42:35 +0900 Subject: [PATCH 316/803] Fix potential crash in `BeatmapLeaderboard` during rapid refresh operations --- .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index f1f11518ee..43eaff56b3 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -111,7 +112,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { - subscribeToLocalScores(cancellationToken); + subscribeToLocalScores(fetchBeatmapInfo, cancellationToken); return null; } @@ -174,14 +175,13 @@ namespace osu.Game.Screens.Select.Leaderboards Action = () => ScoreSelected?.Invoke(model) }; - private void subscribeToLocalScores(CancellationToken cancellationToken) + private void subscribeToLocalScores(BeatmapInfo beatmapInfo, CancellationToken cancellationToken) { + Debug.Assert(beatmapInfo != null); + scoreSubscription?.Dispose(); scoreSubscription = null; - if (beatmapInfo == null) - return; - scoreSubscription = realm.RegisterForNotifications(r => r.All().Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $0" + $" AND {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $1" From 1777a6d24abffb933105e6fdd148368a4c841d50 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 16:06:18 +0900 Subject: [PATCH 317/803] Add attribute to retry flaky tests on normal CI runs --- osu.Game/Tests/FlakyTestAttribute.cs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 osu.Game/Tests/FlakyTestAttribute.cs diff --git a/osu.Game/Tests/FlakyTestAttribute.cs b/osu.Game/Tests/FlakyTestAttribute.cs new file mode 100644 index 0000000000..a5646f5d00 --- /dev/null +++ b/osu.Game/Tests/FlakyTestAttribute.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using System; +using NUnit.Framework; + +namespace osu.Game.Tests +{ + /// + /// An attribute to mark any flaky tests. + /// Will add a retry count unless environment variable `FAIL_FLAKY_TESTS` is set to `1`. + /// + public class FlakyTestAttribute : RetryAttribute + { + public FlakyTestAttribute() + : this(10) + { + } + + public FlakyTestAttribute(int tryCount) + : base(Environment.GetEnvironmentVariable("FAIL_FLAKY_TESTS") == "1" ? 0 : tryCount) + { + } + } +} From b597843579c3a10fbf043b91bac12b1db3ac7b47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 16:17:40 +0900 Subject: [PATCH 318/803] Mark and document remaining flaky tests --- .../TestSceneSliderSnaking.cs | 21 ++++++++++ .../TestSceneMasterGameplayClockContainer.cs | 10 +++++ .../Editing/TestSceneEditorBeatmapCreation.cs | 19 ++++++++++ .../Visual/Editing/TestSceneTimelineZoom.cs | 10 +++++ .../TestSceneCompletionCancellation.cs | 10 +++++ .../TestSceneMultiplayerMatchSubScreen.cs | 38 +++++++++++++++++++ 6 files changed, 108 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 366793058d..0118ed6513 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -24,6 +24,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Storyboards; +using osu.Game.Tests; using osuTK; namespace osu.Game.Rulesets.Osu.Tests @@ -71,6 +72,16 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(0)] [TestCase(1)] [TestCase(2)] + [FlakyTest] + /* + * Fail rate around 0.15% + * + * TearDown : System.TimeoutException : "wait for seek to finish" timed out + * --TearDown + * at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0() + * at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered) + * at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition) + */ public void TestSnakingEnabled(int sliderIndex) { AddStep("enable autoplay", () => autoplay = true); @@ -95,6 +106,16 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(0)] [TestCase(1)] [TestCase(2)] + [FlakyTest] + /* + * Fail rate around 0.15% + * + * TearDown : System.TimeoutException : "wait for seek to finish" timed out + * --TearDown + * at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0() + * at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered) + * at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition) + */ public void TestSnakingDisabled(int sliderIndex) { AddStep("have autoplay", () => autoplay = true); diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index ced397921c..ae431e77ae 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -78,6 +78,16 @@ namespace osu.Game.Tests.Gameplay } [Test] + [FlakyTest] + /* + * Fail rate around 0.15% + * + * TearDown : osu.Framework.Testing.Drawables.Steps.AssertButton+TracedException : gameplay clock time = 2500 + * --TearDown + * at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() + * at osu.Framework.Threading.Scheduler.Update() + * at osu.Framework.Graphics.Drawable.UpdateSubTree() + */ public void TestSeekPerformsInGameplayTime( [Values(1.0, 0.5, 2.0)] double clockRate, [Values(0.0, 200.0, -200.0)] double userOffset, diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index b711d55e15..2707682b4c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -82,6 +82,25 @@ namespace osu.Game.Tests.Visual.Editing } [Test] + [FlakyTest] + /* + * Fail rate around 1.2%. + * + * Failing with realm refetch occasionally being null. + * My only guess is that the WorkingBeatmap at SetupScreen is dummy instead of the true one. + * If it's something else, we have larger issues with realm, but I don't think that's the case. + * + * at osu.Framework.Logging.ThrowingTraceListener.Fail(String message1, String message2) + * at System.Diagnostics.TraceInternal.Fail(String message, String detailMessage) + * at System.Diagnostics.TraceInternal.TraceProvider.Fail(String message, String detailMessage) + * at System.Diagnostics.Debug.Fail(String message, String detailMessage) + * at osu.Game.Database.ModelManager`1.<>c__DisplayClass8_0.b__0(Realm realm) ModelManager.cs:line 50 + * at osu.Game.Database.RealmExtensions.Write(Realm realm, Action`1 function) RealmExtensions.cs:line 14 + * at osu.Game.Database.ModelManager`1.performFileOperation(TModel item, Action`1 operation) ModelManager.cs:line 47 + * at osu.Game.Database.ModelManager`1.AddFile(TModel item, Stream contents, String filename) ModelManager.cs:line 37 + * at osu.Game.Screens.Edit.Setup.ResourcesSection.ChangeAudioTrack(FileInfo source) ResourcesSection.cs:line 115 + * at osu.Game.Tests.Visual.Editing.TestSceneEditorBeatmapCreation.b__11_0() TestSceneEditorBeatmapCreation.cs:line 101 + */ public void TestAddAudioTrack() { AddAssert("switch track to real track", () => diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs index fd103ff70f..2cada1989e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs @@ -14,6 +14,16 @@ namespace osu.Game.Tests.Visual.Editing public override Drawable CreateTestComponent() => Empty(); [Test] + [FlakyTest] + /* + * Fail rate around 0.3% + * + * TearDown : osu.Framework.Testing.Drawables.Steps.AssertButton+TracedException : range halved + * --TearDown + * at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() + * at osu.Framework.Threading.Scheduler.Update() + * at osu.Framework.Graphics.Drawable.UpdateSubTree() + */ public void TestVisibleRangeUpdatesOnZoomChange() { double initialVisibleRange = 0; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs index 6aedc64370..13ceb05aff 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs @@ -61,6 +61,16 @@ namespace osu.Game.Tests.Visual.Gameplay /// Tests whether can still pause after cancelling completion by reverting back to true. /// [Test] + [FlakyTest] + /* + * Fail rate around 0.45% + * + * TearDown : System.TimeoutException : "completion set by processor" timed out + * --TearDown + * at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0() + * at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered) + * at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition) + */ public void TestCanPauseAfterCancellation() { complete(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index bb7587ac56..3a48f68f1d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -74,6 +74,25 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] + [FlakyTest] + /* + * Fail rate around 1.5% + * + * TearDown : System.AggregateException : One or more errors occurred. (Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')) + ----> System.ArgumentOutOfRangeException : Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index') + * --TearDown + * at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) + * at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) + * at osu.Framework.Extensions.TaskExtensions.WaitSafely(Task task) + * at osu.Framework.Testing.TestScene.checkForErrors() + * at osu.Framework.Testing.TestScene.RunTestsFromNUnit() + *--ArgumentOutOfRangeException + * at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller) + * at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller) + * at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller) + * at osu.Game.Online.Multiplayer.MultiplayerClient.<>c__DisplayClass106_0.b__0() in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Online\Multiplayer\MultiplayerClient .cs:line 702 + * at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() + */ public void TestCreatedRoom() { AddStep("add playlist item", () => @@ -90,6 +109,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] + [FlakyTest] public void TestTaikoOnlyMod() { AddStep("add playlist item", () => @@ -110,6 +130,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] + [FlakyTest] public void TestSettingValidity() { AddAssert("create button not enabled", () => !this.ChildrenOfType().Single().Enabled.Value); @@ -126,6 +147,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] + [FlakyTest] public void TestStartMatchWhileSpectating() { AddStep("set playlist", () => @@ -156,6 +178,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] + [FlakyTest] public void TestFreeModSelectionHasAllowedMods() { AddStep("add playlist item with allowed mod", () => @@ -183,6 +206,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] + [FlakyTest] public void TestModSelectKeyWithAllowedMods() { AddStep("add playlist item with allowed mod", () => @@ -204,6 +228,19 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] + [FlakyTest] + /* + * Fail rate around 0.3% + * + * Somehow there are two mod select overlays? + * + * TearDown : System.InvalidOperationException : Sequence contains more than one element + * --TearDown + * at System.Linq.ThrowHelper.ThrowMoreThanOneElementException() + * at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Boolean& found) + * at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source) + * at osu.Game.Tests.Visual.Multiplayer.TestSceneMultiplayerMatchSubScreen.b__14_3() in /opt/buildagent/work/ecd860037212ac52/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs:line 223 + */ public void TestModSelectKeyWithNoAllowedMods() { AddStep("add playlist item with no allowed mods", () => @@ -224,6 +261,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] + [FlakyTest] public void TestNextPlaylistItemSelectedAfterCompletion() { AddStep("add two playlist items", () => From c4f166084133f129d1f0195473836943db4dff5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 16:20:34 +0900 Subject: [PATCH 319/803] Rename ENVVAR in line with previous one (`OSU_TESTS_NO_TIMEOUT`) --- osu.Game/Tests/FlakyTestAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/FlakyTestAttribute.cs b/osu.Game/Tests/FlakyTestAttribute.cs index a5646f5d00..a1c16284c8 100644 --- a/osu.Game/Tests/FlakyTestAttribute.cs +++ b/osu.Game/Tests/FlakyTestAttribute.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests } public FlakyTestAttribute(int tryCount) - : base(Environment.GetEnvironmentVariable("FAIL_FLAKY_TESTS") == "1" ? 0 : tryCount) + : base(Environment.GetEnvironmentVariable("OSU_TESTS_FAIL_FLAKY") == "1" ? 0 : tryCount) { } } From 615c3234d8df6339635f6ae7dcbca6158b282e26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 16:25:21 +0900 Subject: [PATCH 320/803] Remove non-required NRT hint --- osu.Game/Tests/FlakyTestAttribute.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Tests/FlakyTestAttribute.cs b/osu.Game/Tests/FlakyTestAttribute.cs index a1c16284c8..299dbb89a2 100644 --- a/osu.Game/Tests/FlakyTestAttribute.cs +++ b/osu.Game/Tests/FlakyTestAttribute.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. -#nullable disable using System; using NUnit.Framework; From c22e77e481f62226507a7ef721e1f106b6f1bc4e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 16:40:52 +0900 Subject: [PATCH 321/803] Fix test sometimes referencing old ModSelect object --- .../TestSceneMultiplayerMatchSubScreen.cs | 9 ++++----- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 20 +++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index bb7587ac56..07a6352e10 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -176,8 +176,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("mod select contents loaded", () => this.ChildrenOfType().Any() && this.ChildrenOfType().All(col => col.IsLoaded && col.ItemsLoaded)); AddUntilStep("mod select contains only double time mod", - () => this.ChildrenOfType() - .SingleOrDefault()? + () => this.ChildrenOfType().Single().UserModsSelectOverlay .ChildrenOfType() .SingleOrDefault(panel => !panel.Filtered.Value)?.Mod is OsuModDoubleTime); } @@ -200,7 +199,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("press toggle mod select key", () => InputManager.Key(Key.F1)); - AddUntilStep("mod select shown", () => this.ChildrenOfType().Single().State.Value == Visibility.Visible); + AddUntilStep("mod select shown", () => this.ChildrenOfType().Single().UserModsSelectOverlay.State.Value == Visibility.Visible); } [Test] @@ -219,8 +218,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("press toggle mod select key", () => InputManager.Key(Key.F1)); - AddWaitStep("wait some", 3); - AddAssert("mod select not shown", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); + // AddWaitStep("wait some", 3); + AddAssert("mod select not shown", () => this.ChildrenOfType().Single().UserModsSelectOverlay.State.Value == Visibility.Hidden); } [Test] diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index e7d31fbdde..db636f6f95 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.OnlinePlay.Match public readonly Room Room; private readonly bool allowEdit; - private ModSelectOverlay userModsSelectOverlay; + public ModSelectOverlay UserModsSelectOverlay { get; private set; } [CanBeNull] private IDisposable userModsSelectOverlayRegistration; @@ -236,7 +236,7 @@ namespace osu.Game.Screens.OnlinePlay.Match } }; - LoadComponent(userModsSelectOverlay = new UserModSelectOverlay(OverlayColourScheme.Plum) + LoadComponent(UserModsSelectOverlay = new UserModSelectOverlay(OverlayColourScheme.Plum) { SelectedMods = { BindTarget = UserMods }, IsValidMod = _ => false @@ -269,7 +269,7 @@ namespace osu.Game.Screens.OnlinePlay.Match beatmapAvailabilityTracker.SelectedItem.BindTo(SelectedItem); beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap()); - userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(userModsSelectOverlay); + userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(UserModsSelectOverlay); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -289,9 +289,9 @@ namespace osu.Game.Screens.OnlinePlay.Match return base.OnBackButton(); } - if (userModsSelectOverlay.State.Value == Visibility.Visible) + if (UserModsSelectOverlay.State.Value == Visibility.Visible) { - userModsSelectOverlay.Hide(); + UserModsSelectOverlay.Hide(); return true; } @@ -304,7 +304,7 @@ namespace osu.Game.Screens.OnlinePlay.Match return base.OnBackButton(); } - protected void ShowUserModSelect() => userModsSelectOverlay.Show(); + protected void ShowUserModSelect() => UserModsSelectOverlay.Show(); public override void OnEntering(ScreenTransitionEvent e) { @@ -385,13 +385,13 @@ namespace osu.Game.Screens.OnlinePlay.Match if (!selected.AllowedMods.Any()) { UserModsSection?.Hide(); - userModsSelectOverlay.Hide(); - userModsSelectOverlay.IsValidMod = _ => false; + UserModsSelectOverlay.Hide(); + UserModsSelectOverlay.IsValidMod = _ => false; } else { UserModsSection?.Show(); - userModsSelectOverlay.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType()); + UserModsSelectOverlay.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType()); } } @@ -430,7 +430,7 @@ namespace osu.Game.Screens.OnlinePlay.Match private void onLeaving() { - userModsSelectOverlay.Hide(); + UserModsSelectOverlay.Hide(); endHandlingTrack(); } From 58c9bb031fbbaf958079c1568b4df5cc715bc0ff Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 16:51:15 +0900 Subject: [PATCH 322/803] Apply PR reviews/fixes --- .../Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs | 2 +- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 07a6352e10..f925622a80 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -218,7 +218,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("press toggle mod select key", () => InputManager.Key(Key.F1)); - // AddWaitStep("wait some", 3); + AddWaitStep("wait some", 3); AddAssert("mod select not shown", () => this.ChildrenOfType().Single().UserModsSelectOverlay.State.Value == Visibility.Hidden); } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index db636f6f95..bae25dc9f8 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.OnlinePlay.Match public readonly Room Room; private readonly bool allowEdit; - public ModSelectOverlay UserModsSelectOverlay { get; private set; } + internal ModSelectOverlay UserModsSelectOverlay { get; private set; } [CanBeNull] private IDisposable userModsSelectOverlayRegistration; From 8f8c2a8c9aa1e87a2468bed6408a33939e6d9b85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 16:55:47 +0900 Subject: [PATCH 323/803] Remove one flaky test description (fixed via #18966) --- .../TestSceneMultiplayerMatchSubScreen.cs | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 3a48f68f1d..75a011b6eb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -109,7 +109,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] + [FlakyTest] // See above public void TestTaikoOnlyMod() { AddStep("add playlist item", () => @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] + [FlakyTest] // See above public void TestSettingValidity() { AddAssert("create button not enabled", () => !this.ChildrenOfType().Single().Enabled.Value); @@ -147,7 +147,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] + [FlakyTest] // See above public void TestStartMatchWhileSpectating() { AddStep("set playlist", () => @@ -178,7 +178,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] + [FlakyTest] // See above public void TestFreeModSelectionHasAllowedMods() { AddStep("add playlist item with allowed mod", () => @@ -206,7 +206,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] + [FlakyTest] // See above public void TestModSelectKeyWithAllowedMods() { AddStep("add playlist item with allowed mod", () => @@ -228,19 +228,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] - /* - * Fail rate around 0.3% - * - * Somehow there are two mod select overlays? - * - * TearDown : System.InvalidOperationException : Sequence contains more than one element - * --TearDown - * at System.Linq.ThrowHelper.ThrowMoreThanOneElementException() - * at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Boolean& found) - * at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source) - * at osu.Game.Tests.Visual.Multiplayer.TestSceneMultiplayerMatchSubScreen.b__14_3() in /opt/buildagent/work/ecd860037212ac52/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs:line 223 - */ + [FlakyTest] // See above public void TestModSelectKeyWithNoAllowedMods() { AddStep("add playlist item with no allowed mods", () => @@ -261,7 +249,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] + [FlakyTest] // See above public void TestNextPlaylistItemSelectedAfterCompletion() { AddStep("add two playlist items", () => From 17ad6648d1e837a7a85f57792a9185377b37bd52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 17:07:00 +0900 Subject: [PATCH 324/803] Fix new test failing on headless runs --- .../TestSceneDifficultyRangeFilterControl.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs index cc0909a835..ffad15287b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs @@ -14,13 +14,16 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestBasic() { - Child = new DifficultyRangeFilterControl + AddStep("create control", () => { - Width = 200, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(3), - }; + Child = new DifficultyRangeFilterControl + { + Width = 200, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(3), + }; + }); } } } From 0e1f08eff870b325b837b03db6cce7f1679f3d20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 17:10:04 +0900 Subject: [PATCH 325/803] Fix regressing `BeatmapRecommendations` tests due to diffcalc running at --- .../TestSceneBeatmapRecommendations.cs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 117515977e..504ded5406 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -166,15 +167,22 @@ namespace osu.Game.Tests.Visual.SongSelect var beatmapSet = TestResources.CreateTestBeatmapSetInfo(rulesets.Length, rulesets); - for (int i = 0; i < rulesets.Length; i++) + var importedBeatmapSet = Game.BeatmapManager.Import(beatmapSet); + + Debug.Assert(importedBeatmapSet != null); + + importedBeatmapSet.PerformWrite(s => { - var beatmap = beatmapSet.Beatmaps[i]; + for (int i = 0; i < rulesets.Length; i++) + { + var beatmap = s.Beatmaps[i]; - beatmap.StarRating = i + 1; - beatmap.DifficultyName = $"SR{i + 1}"; - } + beatmap.StarRating = i + 1; + beatmap.DifficultyName = $"SR{i + 1}"; + } + }); - return Game.BeatmapManager.Import(beatmapSet)?.Value; + return importedBeatmapSet.Value; } private bool ensureAllBeatmapSetsImported(IEnumerable beatmapSets) => beatmapSets.All(set => set != null); From 0cfaef36058a374feb31d73a400e3bfc201777dc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 1 Jul 2022 11:21:52 +0300 Subject: [PATCH 326/803] Remove iOS build CI workaround --- .github/workflows/ci.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c728d89ed1..ef729a779f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,12 +126,6 @@ jobs: with: dotnet-version: "6.0.x" - # macOS agents recently have empty NuGet config files, resulting in restore failures, - # see https://github.com/actions/virtual-environments/issues/5768 - # Add the global nuget package source manually for now. - - name: Setup NuGet.Config - run: echo '' > ~/.config/NuGet/NuGet.Config - # Contrary to seemingly any other msbuild, msbuild running on macOS/Mono # cannot accept .sln(f) files as arguments. # Build just the main game for now. From e213c1a4ef6a489ad1154a353ab0beb4fc310da1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 17:26:40 +0900 Subject: [PATCH 327/803] Rename enum to explicitly mention it is a filter --- .../Multiplayer/TestSceneLoungeRoomsContainer.cs | 4 ++-- .../OnlinePlay/Lounge/Components/FilterCriteria.cs | 2 +- .../{RoomAccessType.cs => RoomPermissionsFilter.cs} | 2 +- .../OnlinePlay/Lounge/Components/RoomsContainer.cs | 12 ++++++------ .../Multiplayer/MultiplayerLoungeSubScreen.cs | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) rename osu.Game/Screens/OnlinePlay/Lounge/Components/{RoomAccessType.cs => RoomPermissionsFilter.cs} (87%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index d56716a0b5..82e7bf8969 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -170,11 +170,11 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("both rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2); - AddStep("filter public rooms", () => container.Filter.Value = new FilterCriteria { AccessType = RoomAccessType.Public }); + AddStep("filter public rooms", () => container.Filter.Value = new FilterCriteria { Permissions = RoomPermissionsFilter.Public }); AddUntilStep("private room hidden", () => container.Rooms.All(r => !r.Room.HasPassword.Value)); - AddStep("filter private rooms", () => container.Filter.Value = new FilterCriteria { AccessType = RoomAccessType.Private }); + AddStep("filter private rooms", () => container.Filter.Value = new FilterCriteria { Permissions = RoomPermissionsFilter.Private }); AddUntilStep("public room hidden", () => container.Rooms.All(r => r.Room.HasPassword.Value)); } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs index 864634209b..3a687ad351 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs @@ -13,6 +13,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components public RoomStatusFilter Status; public string Category; public RulesetInfo Ruleset; - public RoomAccessType AccessType; + public RoomPermissionsFilter Permissions; } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPermissionsFilter.cs similarity index 87% rename from osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs rename to osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPermissionsFilter.cs index 1141ed2937..faef2a9d57 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPermissionsFilter.cs @@ -3,7 +3,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public enum RoomAccessType + public enum RoomPermissionsFilter { All, Public, diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 24dd27c27b..6142fc78a8 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -87,27 +87,27 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components matchingFilter &= r.FilterTerms.Any(term => term.ToString().Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); } - matchingFilter &= matchesAccessType(r, criteria.AccessType); + matchingFilter &= matchPermissions(r, criteria.Permissions); r.MatchingFilter = matchingFilter; } }); - static bool matchesAccessType(DrawableLoungeRoom room, RoomAccessType accessType) + static bool matchPermissions(DrawableLoungeRoom room, RoomPermissionsFilter accessType) { switch (accessType) { - case RoomAccessType.All: + case RoomPermissionsFilter.All: return true; - case RoomAccessType.Public: + case RoomPermissionsFilter.Public: return !room.Room.HasPassword.Value; - case RoomAccessType.Private: + case RoomPermissionsFilter.Private: return room.Room.HasPassword.Value; default: - throw new ArgumentOutOfRangeException(nameof(accessType), accessType, $"Unsupported {nameof(RoomAccessType)} in filter"); + throw new ArgumentOutOfRangeException(nameof(accessType), accessType, $"Unsupported {nameof(RoomPermissionsFilter)} in filter"); } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index b482b1b680..37b977cff7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private MultiplayerClient client { get; set; } - private Dropdown roomAccessTypeDropdown; + private Dropdown roomAccessTypeDropdown; public override void OnResuming(ScreenTransitionEvent e) { @@ -48,7 +48,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override IEnumerable CreateFilterControls() { - roomAccessTypeDropdown = new SlimEnumDropdown + roomAccessTypeDropdown = new SlimEnumDropdown { RelativeSizeAxes = Axes.None, Width = 160, @@ -63,7 +63,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { var criteria = base.CreateFilterCriteria(); criteria.Category = @"realtime"; - criteria.AccessType = roomAccessTypeDropdown.Current.Value; + criteria.Permissions = roomAccessTypeDropdown.Current.Value; return criteria; } From 5880b824d0e91361de9d6cd2fea7b9fc18fe7f69 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 1 Jul 2022 11:30:51 +0300 Subject: [PATCH 328/803] Simplify description logic for overlay tab items --- osu.Game/Overlays/TabControlOverlayHeader.cs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs index 88ee565859..caec4aeed0 100644 --- a/osu.Game/Overlays/TabControlOverlayHeader.cs +++ b/osu.Game/Overlays/TabControlOverlayHeader.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -13,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -110,20 +108,7 @@ namespace osu.Game.Overlays public OverlayHeaderTabItem(T value) : base(value) { - if (!(Value is Enum enumValue)) - Text.Text = Value.ToString().ToLowerInvariant(); - else - { - case LocalisableString localisableString: - Text.Text = localisableString.ToLower(); - break; - - // If localisable == non-localisable, then we must have a basic string, so .ToLowerInvariant() is used. - Text.Text = localisableDescription.Equals(nonLocalisableDescription) - ? nonLocalisableDescription.ToLowerInvariant() - : localisableDescription; - } - + Text.Text = value.GetLocalisableDescription().ToLower(); Text.Font = OsuFont.GetFont(size: 14); Text.Margin = new MarginPadding { Vertical = 16.5f }; // 15px padding + 1.5px line-height difference compensation Bar.Margin = new MarginPadding { Bottom = bar_height }; From a17e18103f983649af80c9a15e1f14b3e6e52774 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 18:19:31 +0900 Subject: [PATCH 329/803] Improve description --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 807be997db..052c7128f6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Name => "Repel"; public override string Acronym => "RP"; public override ModType Type => ModType.Fun; - public override string Description => "Run away!"; + public override string Description => "Hit objects run away!"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised) }; From 1c2ffb3bc4414b6c8ca51d716b2d745fc53e6d5b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 18:03:26 +0900 Subject: [PATCH 330/803] Fix server-side objects being sent to client --- .../Multiplayer/TestMultiplayerClient.cs | 54 +++++++++++-------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 6b7495762a..ff83997936 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -6,9 +6,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using MessagePack; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; @@ -50,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// private Room? serverSideAPIRoom; - private MultiplayerPlaylistItem? currentItem => Room?.Playlist[currentIndex]; + private MultiplayerPlaylistItem? currentItem => serverSidePlaylist[currentIndex]; private int currentIndex; private long lastPlaylistItemId; @@ -79,7 +81,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void addUser(MultiplayerRoomUser user) { - ((IMultiplayerClient)this).UserJoined(user).WaitSafely(); + ((IMultiplayerClient)this).UserJoined(clone(user)).WaitSafely(); // We want the user to be immediately available for testing, so force a scheduler update to run the update-bound continuation. Scheduler.Update(); @@ -93,7 +95,7 @@ namespace osu.Game.Tests.Visual.Multiplayer .Select(team => (teamID: team.ID, userCount: Room.Users.Count(u => (u.MatchState as TeamVersusUserState)?.TeamID == team.ID))) .OrderBy(pair => pair.userCount) .First().teamID; - ((IMultiplayerClient)this).MatchUserStateChanged(user.UserID, new TeamVersusUserState { TeamID = bestTeam }).WaitSafely(); + ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), clone(new TeamVersusUserState { TeamID = bestTeam })).WaitSafely(); break; } } @@ -102,7 +104,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Debug.Assert(Room != null); - ((IMultiplayerClient)this).UserLeft(new MultiplayerRoomUser(user.Id)); + ((IMultiplayerClient)this).UserLeft(clone(new MultiplayerRoomUser(user.Id))); Schedule(() => { @@ -113,12 +115,12 @@ namespace osu.Game.Tests.Visual.Multiplayer public void ChangeRoomState(MultiplayerRoomState newState) { - ((IMultiplayerClient)this).RoomStateChanged(newState); + ((IMultiplayerClient)this).RoomStateChanged(clone(newState)); } public void ChangeUserState(int userId, MultiplayerUserState newState) { - ((IMultiplayerClient)this).UserStateChanged(userId, newState); + ((IMultiplayerClient)this).UserStateChanged(clone(userId), clone(newState)); updateRoomStateIfRequired(); } @@ -176,7 +178,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void ChangeUserBeatmapAvailability(int userId, BeatmapAvailability newBeatmapAvailability) { - ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(userId, newBeatmapAvailability); + ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(clone(userId), clone(newBeatmapAvailability)); } protected override async Task JoinRoom(long roomId, string? password = null) @@ -205,7 +207,7 @@ namespace osu.Game.Tests.Visual.Multiplayer QueueMode = serverSideAPIRoom.QueueMode.Value, AutoStartDuration = serverSideAPIRoom.AutoStartDuration.Value }, - Playlist = serverSidePlaylist.ToList(), + Playlist = serverSidePlaylist, Users = { localUser }, Host = localUser }; @@ -216,7 +218,7 @@ namespace osu.Game.Tests.Visual.Multiplayer RoomSetupAction?.Invoke(room); RoomSetupAction = null; - return room; + return clone(room); } protected override void OnRoomJoined() @@ -236,13 +238,13 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.CompletedTask; } - public override Task TransferHost(int userId) => ((IMultiplayerClient)this).HostChanged(userId); + public override Task TransferHost(int userId) => ((IMultiplayerClient)this).HostChanged(clone(userId)); public override Task KickUser(int userId) { Debug.Assert(Room != null); - return ((IMultiplayerClient)this).UserKicked(Room.Users.Single(u => u.UserID == userId)); + return ((IMultiplayerClient)this).UserKicked(clone(Room.Users.Single(u => u.UserID == userId))); } public override async Task ChangeSettings(MultiplayerRoomSettings settings) @@ -256,7 +258,7 @@ namespace osu.Game.Tests.Visual.Multiplayer await changeQueueMode(settings.QueueMode).ConfigureAwait(false); - await ((IMultiplayerClient)this).SettingsChanged(settings).ConfigureAwait(false); + await ((IMultiplayerClient)this).SettingsChanged(clone(settings)).ConfigureAwait(false); foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready)) ChangeUserState(user.UserID, MultiplayerUserState.Idle); @@ -285,7 +287,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void ChangeUserMods(int userId, IEnumerable newMods) { - ((IMultiplayerClient)this).UserModsChanged(userId, newMods.ToList()); + ((IMultiplayerClient)this).UserModsChanged(clone(userId), clone(newMods)); } public override Task ChangeUserMods(IEnumerable newMods) @@ -312,7 +314,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { userState.TeamID = targetTeam.ID; - await ((IMultiplayerClient)this).MatchUserStateChanged(LocalUser.UserID, userState).ConfigureAwait(false); + await ((IMultiplayerClient)this).MatchUserStateChanged(clone(LocalUser.UserID), clone(userState)).ConfigureAwait(false); } break; @@ -382,7 +384,7 @@ namespace osu.Game.Tests.Visual.Multiplayer serverSidePlaylist[serverSidePlaylist.IndexOf(existingItem)] = item; serverSideAPIRoom.Playlist[serverSideAPIRoom.Playlist.IndexOf(serverSideAPIRoom.Playlist.Single(i => i.ID == item.ID))] = new PlaylistItem(item); - await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); + await ((IMultiplayerClient)this).PlaylistItemChanged(clone(item)).ConfigureAwait(false); } public override Task EditPlaylistItem(MultiplayerPlaylistItem item) => EditUserPlaylistItem(api.LocalUser.Value.OnlineID, item); @@ -409,7 +411,7 @@ namespace osu.Game.Tests.Visual.Multiplayer serverSidePlaylist.Remove(item); serverSideAPIRoom.Playlist.RemoveAll(i => i.ID == item.ID); - await ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItemId).ConfigureAwait(false); + await ((IMultiplayerClient)this).PlaylistItemRemoved(clone(playlistItemId)).ConfigureAwait(false); await updateCurrentItem(Room).ConfigureAwait(false); updateRoomStateIfRequired(); @@ -427,14 +429,14 @@ namespace osu.Game.Tests.Visual.Multiplayer await ((IMultiplayerClient)this).MatchRoomStateChanged(null).ConfigureAwait(false); foreach (var user in Room.Users) - await ((IMultiplayerClient)this).MatchUserStateChanged(user.UserID, null).ConfigureAwait(false); + await ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), null).ConfigureAwait(false); break; case MatchType.TeamVersus: - await ((IMultiplayerClient)this).MatchRoomStateChanged(TeamVersusRoomState.CreateDefault()).ConfigureAwait(false); + await ((IMultiplayerClient)this).MatchRoomStateChanged(clone(TeamVersusRoomState.CreateDefault())).ConfigureAwait(false); foreach (var user in Room.Users) - await ((IMultiplayerClient)this).MatchUserStateChanged(user.UserID, new TeamVersusUserState()).ConfigureAwait(false); + await ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), clone(new TeamVersusUserState())).ConfigureAwait(false); break; } } @@ -463,7 +465,7 @@ namespace osu.Game.Tests.Visual.Multiplayer currentItem.Expired = true; currentItem.PlayedAt = DateTimeOffset.Now; - await ((IMultiplayerClient)this).PlaylistItemChanged(currentItem).ConfigureAwait(false); + await ((IMultiplayerClient)this).PlaylistItemChanged(clone(currentItem)).ConfigureAwait(false); await updatePlaylistOrder(Room).ConfigureAwait(false); // In host-only mode, a duplicate playlist item will be used for the next round. @@ -496,7 +498,7 @@ namespace osu.Game.Tests.Visual.Multiplayer serverSidePlaylist.Add(item); serverSideAPIRoom.Playlist.Add(new PlaylistItem(item)); - await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false); + await ((IMultiplayerClient)this).PlaylistItemAdded(clone(item)).ConfigureAwait(false); await updatePlaylistOrder(Room).ConfigureAwait(false); } @@ -514,7 +516,7 @@ namespace osu.Game.Tests.Visual.Multiplayer room.Settings.PlaylistItemId = nextItem.ID; if (notify && nextItem.ID != lastItem) - await ((IMultiplayerClient)this).SettingsChanged(room.Settings).ConfigureAwait(false); + await ((IMultiplayerClient)this).SettingsChanged(clone(room.Settings)).ConfigureAwait(false); } private async Task updatePlaylistOrder(MultiplayerRoom room) @@ -564,12 +566,18 @@ namespace osu.Game.Tests.Visual.Multiplayer item.PlaylistOrder = (ushort)i; - await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); + await ((IMultiplayerClient)this).PlaylistItemChanged(clone(item)).ConfigureAwait(false); } // Also ensure that the API room's playlist is correct. foreach (var item in serverSideAPIRoom.Playlist) item.PlaylistOrder = serverSidePlaylist.Single(i => i.ID == item.ID).PlaylistOrder; } + + private T clone(T incoming) + { + byte[]? serialized = MessagePackSerializer.Serialize(typeof(T), incoming, SignalRUnionWorkaroundResolver.OPTIONS); + return MessagePackSerializer.Deserialize(serialized, SignalRUnionWorkaroundResolver.OPTIONS); + } } } From 0be858b5bf7acbc3906f4c9172a16e4920e2791e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 18:04:29 +0900 Subject: [PATCH 331/803] Rename APIRoom -> ClientAPIRoom, remove unused asserts --- .../Visual/Multiplayer/QueueModeTestScene.cs | 2 +- .../TestSceneAllPlayersQueueMode.cs | 26 +++++++++---------- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 24 ++++++++--------- .../Multiplayer/TestSceneMultiplayer.cs | 12 ++++----- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../Multiplayer/TestSceneMultiplayerPlayer.cs | 2 +- .../TestSceneMultiplayerQueueList.cs | 8 +++--- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 4 +-- .../Multiplayer/TestMultiplayerClient.cs | 10 +------ 9 files changed, 41 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 074a92f5b0..53bed75723 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestCreatedWithCorrectMode() { - AddAssert("room created with correct mode", () => MultiplayerClient.APIRoom?.QueueMode.Value == Mode); + AddAssert("room created with correct mode", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == Mode); } protected void RunGameplay() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 32fd7724e0..b08102df8c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -30,19 +30,19 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFirstItemSelectedByDefault() { - AddAssert("first item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); + AddAssert("first item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] public void TestItemAddedToTheEndOfQueue() { addItem(() => OtherBeatmap); - AddAssert("playlist has 2 items", () => MultiplayerClient.APIRoom?.Playlist.Count == 2); + AddAssert("playlist has 2 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); addItem(() => InitialBeatmap); - AddAssert("playlist has 3 items", () => MultiplayerClient.APIRoom?.Playlist.Count == 3); + AddAssert("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3); - AddAssert("first item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); + AddAssert("first item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -50,9 +50,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { RunGameplay(); - AddAssert("playlist has only one item", () => MultiplayerClient.APIRoom?.Playlist.Count == 1); - AddAssert("playlist item is expired", () => MultiplayerClient.APIRoom?.Playlist[0].Expired == true); - AddAssert("last item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); + AddAssert("playlist has only one item", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 1); + AddAssert("playlist item is expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); + AddAssert("last item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -63,13 +63,13 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); - AddAssert("first item expired", () => MultiplayerClient.APIRoom?.Playlist[0].Expired == true); - AddAssert("next item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[1].ID); + AddAssert("first item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); + AddAssert("next item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID); RunGameplay(); - AddAssert("second item expired", () => MultiplayerClient.APIRoom?.Playlist[1].Expired == true); - AddAssert("next item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[2].ID); + AddAssert("second item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == true); + AddAssert("next item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[2].ID); } [Test] @@ -82,8 +82,8 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); AddStep("change queue mode", () => MultiplayerClient.ChangeSettings(queueMode: QueueMode.HostOnly)); - AddAssert("playlist has 3 items", () => MultiplayerClient.APIRoom?.Playlist.Count == 3); - AddAssert("item 2 is not expired", () => MultiplayerClient.APIRoom?.Playlist[1].Expired == false); + AddAssert("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3); + AddAssert("item 2 is not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false); AddAssert("current item is the other beatmap", () => MultiplayerClient.Room?.Settings.PlaylistItemId == 2); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index e436f418ab..8f19590731 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFirstItemSelectedByDefault() { - AddAssert("first item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); + AddAssert("first item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { selectNewItem(() => InitialBeatmap); - AddAssert("playlist item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); + AddAssert("playlist item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { selectNewItem(() => OtherBeatmap); - AddAssert("playlist item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); + AddAssert("playlist item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -48,10 +48,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { RunGameplay(); - AddAssert("playlist contains two items", () => MultiplayerClient.APIRoom?.Playlist.Count == 2); - AddAssert("first playlist item expired", () => MultiplayerClient.APIRoom?.Playlist[0].Expired == true); - AddAssert("second playlist item not expired", () => MultiplayerClient.APIRoom?.Playlist[1].Expired == false); - AddAssert("second playlist item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[1].ID); + AddAssert("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); + AddAssert("first playlist item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); + AddAssert("second playlist item not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false); + AddAssert("second playlist item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID); } [Test] @@ -60,12 +60,12 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); IBeatmapInfo firstBeatmap = null; - AddStep("get first playlist item beatmap", () => firstBeatmap = MultiplayerClient.APIRoom?.Playlist[0].Beatmap); + AddStep("get first playlist item beatmap", () => firstBeatmap = MultiplayerClient.ClientAPIRoom?.Playlist[0].Beatmap); selectNewItem(() => OtherBeatmap); - AddAssert("first playlist item hasn't changed", () => MultiplayerClient.APIRoom?.Playlist[0].Beatmap == firstBeatmap); - AddAssert("second playlist item changed", () => MultiplayerClient.APIRoom?.Playlist[1].Beatmap != firstBeatmap); + AddAssert("first playlist item hasn't changed", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Beatmap == firstBeatmap); + AddAssert("second playlist item changed", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Beatmap != firstBeatmap); } [Test] @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Multiplayer QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("api room updated", () => MultiplayerClient.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("api room updated", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); } [Test] @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { addItem(() => OtherBeatmap); - AddAssert("playlist contains two items", () => MultiplayerClient.APIRoom?.Playlist.Count == 2); + AddAssert("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); } private void selectNewItem(Func beatmap) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index d464527976..37cc803daf 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -243,8 +243,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddAssert("Check participant count correct", () => multiplayerClient.APIRoom?.ParticipantCount.Value == 1); - AddAssert("Check participant list contains user", () => multiplayerClient.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); + AddAssert("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1); + AddAssert("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } [Test] @@ -303,8 +303,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); AddUntilStep("wait for join", () => multiplayerClient.RoomJoined); - AddAssert("Check participant count correct", () => multiplayerClient.APIRoom?.ParticipantCount.Value == 1); - AddAssert("Check participant list contains user", () => multiplayerClient.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); + AddAssert("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1); + AddAssert("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } [Test] @@ -323,7 +323,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddAssert("room has password", () => multiplayerClient.APIRoom?.Password.Value == "password"); + AddAssert("room has password", () => multiplayerClient.ClientAPIRoom?.Password.Value == "password"); } [Test] @@ -377,7 +377,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddStep("change password", () => multiplayerClient.ChangeSettings(password: "password2")); - AddUntilStep("local password changed", () => multiplayerClient.APIRoom?.Password.Value == "password2"); + AddUntilStep("local password changed", () => multiplayerClient.ClientAPIRoom?.Password.Value == "password2"); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index bb7587ac56..478c8d3318 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -254,7 +254,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("last playlist item selected", () => { - var lastItem = this.ChildrenOfType().Single(p => p.Item.ID == MultiplayerClient.APIRoom?.Playlist.Last().ID); + var lastItem = this.ChildrenOfType().Single(p => p.Item.ID == MultiplayerClient.ClientAPIRoom?.Playlist.Last().ID); return lastItem.IsSelectedItem; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs index 042a9297eb..300980a53f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("initialise gameplay", () => { - Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.APIRoom, new PlaylistItem(Beatmap.Value.BeatmapInfo) + Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.ClientAPIRoom, new PlaylistItem(Beatmap.Value.BeatmapInfo) { RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, }, MultiplayerClient.Room?.Users.ToArray())); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index fcd6dd5bd2..8aa7d5aec2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(500, 300), - Items = { BindTarget = MultiplayerClient.APIRoom!.Playlist } + Items = { BindTarget = MultiplayerClient.ClientAPIRoom!.Playlist } }; }); @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestDeleteButtonAlwaysVisibleForHost() { AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("wait for queue mode change", () => MultiplayerClient.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); addPlaylistItem(() => API.LocalUser.Value.OnlineID); assertDeleteButtonVisibility(1, true); @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestDeleteButtonOnlyVisibleForItemOwnerIfNotHost() { AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("wait for queue mode change", () => MultiplayerClient.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); AddStep("join other user", () => MultiplayerClient.AddUser(new APIUser { Id = 1234 })); AddStep("set other user as host", () => MultiplayerClient.TransferHost(1234)); @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestCurrentItemDoesNotHaveDeleteButton() { AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("wait for queue mode change", () => MultiplayerClient.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); addPlaylistItem(() => API.LocalUser.Value.OnlineID); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 2e39449f64..7d93e550d0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -133,14 +133,14 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddUntilStep("match type head to head", () => multiplayerClient.APIRoom?.Type.Value == MatchType.HeadToHead); + AddUntilStep("match type head to head", () => multiplayerClient.ClientAPIRoom?.Type.Value == MatchType.HeadToHead); AddStep("change match type", () => multiplayerClient.ChangeSettings(new MultiplayerRoomSettings { MatchType = MatchType.TeamVersus }).WaitSafely()); - AddUntilStep("api room updated to team versus", () => multiplayerClient.APIRoom?.Type.Value == MatchType.TeamVersus); + AddUntilStep("api room updated to team versus", () => multiplayerClient.ClientAPIRoom?.Type.Value == MatchType.TeamVersus); } [Test] diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index ff83997936..860cf7dee9 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// The local client's . This is not always equivalent to the server-side room. /// - public new Room? APIRoom => base.APIRoom; + public Room? ClientAPIRoom => APIRoom; public Action? RoomSetupAction; @@ -126,8 +126,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private void updateRoomStateIfRequired() { - Debug.Assert(APIRoom != null); - Schedule(() => { Debug.Assert(Room != null); @@ -223,7 +221,6 @@ namespace osu.Game.Tests.Visual.Multiplayer protected override void OnRoomJoined() { - Debug.Assert(APIRoom != null); Debug.Assert(Room != null); // emulate the server sending this after the join room. scheduler required to make sure the join room event is fired first (in Join). @@ -250,7 +247,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public override async Task ChangeSettings(MultiplayerRoomSettings settings) { Debug.Assert(Room != null); - Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); // Server is authoritative for the time being. @@ -344,7 +340,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public async Task AddUserPlaylistItem(int userId, MultiplayerPlaylistItem item) { Debug.Assert(Room != null); - Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); if (Room.Settings.QueueMode == QueueMode.HostOnly && Room.Host?.UserID != LocalUser?.UserID) @@ -392,7 +387,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public async Task RemoveUserPlaylistItem(int userId, long playlistItemId) { Debug.Assert(Room != null); - Debug.Assert(APIRoom != null); Debug.Assert(serverSideAPIRoom != null); var item = serverSidePlaylist.Find(i => i.ID == playlistItemId); @@ -444,7 +438,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private async Task changeQueueMode(QueueMode newMode) { Debug.Assert(Room != null); - Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); // When changing to host-only mode, ensure that at least one non-expired playlist item exists by duplicating the current item. @@ -458,7 +451,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public async Task FinishCurrentItem() { Debug.Assert(Room != null); - Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); // Expire the current playlist item. From b64c0d011c877332d11c19170e620bd2f8a849a3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 18:58:22 +0900 Subject: [PATCH 332/803] Isolate client's Room from TestMultiplayerClient --- .../StatefulMultiplayerClientTest.cs | 10 +- .../TestSceneAllPlayersQueueMode.cs | 12 +- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 8 +- .../Multiplayer/TestSceneMultiplayer.cs | 64 ++-- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../TestSceneMultiplayerParticipantsList.cs | 8 +- .../Multiplayer/TestSceneMultiplayerPlayer.cs | 2 +- .../TestSceneMultiplayerQueueList.cs | 2 +- .../TestSceneMultiplayerSpectateButton.cs | 4 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 16 +- .../Online/Multiplayer/MultiplayerClient.cs | 2 +- .../Multiplayer/TestMultiplayerClient.cs | 335 ++++++++++-------- 12 files changed, 260 insertions(+), 205 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index 9c8b977ad9..0bcb38327e 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer var user = new APIUser { Id = 33 }; AddRepeatStep("add user multiple times", () => MultiplayerClient.AddUser(user), 3); - AddAssert("room has 2 users", () => MultiplayerClient.Room?.Users.Count == 2); + AddUntilStep("room has 2 users", () => MultiplayerClient.ClientRoom?.Users.Count == 2); } [Test] @@ -33,10 +33,10 @@ namespace osu.Game.Tests.NonVisual.Multiplayer var user = new APIUser { Id = 44 }; AddStep("add user", () => MultiplayerClient.AddUser(user)); - AddAssert("room has 2 users", () => MultiplayerClient.Room?.Users.Count == 2); + AddUntilStep("room has 2 users", () => MultiplayerClient.ClientRoom?.Users.Count == 2); AddRepeatStep("remove user multiple times", () => MultiplayerClient.RemoveUser(user), 3); - AddAssert("room has 1 user", () => MultiplayerClient.Room?.Users.Count == 1); + AddUntilStep("room has 1 user", () => MultiplayerClient.ClientRoom?.Users.Count == 1); } [Test] @@ -59,7 +59,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer changeState(6, MultiplayerUserState.WaitingForLoad); checkPlayingUserCount(6); - AddStep("another user left", () => MultiplayerClient.RemoveUser((MultiplayerClient.Room?.Users.Last().User).AsNonNull())); + AddStep("another user left", () => MultiplayerClient.RemoveUser((MultiplayerClient.ServerRoom?.Users.Last().User).AsNonNull())); checkPlayingUserCount(5); AddStep("leave room", () => MultiplayerClient.LeaveRoom()); @@ -103,7 +103,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer { for (int i = 0; i < userCount; ++i) { - int userId = MultiplayerClient.Room?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!"); + int userId = MultiplayerClient.ServerRoom?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!"); MultiplayerClient.ChangeUserState(userId, state); } }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index b08102df8c..a94a60f12d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFirstItemSelectedByDefault() { - AddAssert("first item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); + AddUntilStep("first item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer addItem(() => InitialBeatmap); AddAssert("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3); - AddAssert("first item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); + AddUntilStep("first item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("playlist has only one item", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 1); AddAssert("playlist item is expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); - AddAssert("last item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); + AddUntilStep("last item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -64,12 +64,12 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); AddAssert("first item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); - AddAssert("next item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID); + AddUntilStep("next item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID); RunGameplay(); AddAssert("second item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == true); - AddAssert("next item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[2].ID); + AddUntilStep("next item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[2].ID); } [Test] @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change queue mode", () => MultiplayerClient.ChangeSettings(queueMode: QueueMode.HostOnly)); AddAssert("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3); AddAssert("item 2 is not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false); - AddAssert("current item is the other beatmap", () => MultiplayerClient.Room?.Settings.PlaylistItemId == 2); + AddUntilStep("current item is the other beatmap", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == 2); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index 8f19590731..15a1354c98 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFirstItemSelectedByDefault() { - AddAssert("first item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); + AddUntilStep("first item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { selectNewItem(() => InitialBeatmap); - AddAssert("playlist item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); + AddUntilStep("playlist item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { selectNewItem(() => OtherBeatmap); - AddAssert("playlist item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); + AddUntilStep("playlist item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); AddAssert("first playlist item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); AddAssert("second playlist item not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false); - AddAssert("second playlist item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID); + AddUntilStep("second playlist item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 37cc803daf..193d1d4248 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -116,25 +116,25 @@ namespace osu.Game.Tests.Visual.Multiplayer // all ready AddUntilStep("all players ready", () => { - var nextUnready = multiplayerClient.Room?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Idle); + var nextUnready = multiplayerClient.ClientRoom?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Idle); if (nextUnready != null) multiplayerClient.ChangeUserState(nextUnready.UserID, MultiplayerUserState.Ready); - return multiplayerClient.Room?.Users.All(u => u.State == MultiplayerUserState.Ready) == true; + return multiplayerClient.ClientRoom?.Users.All(u => u.State == MultiplayerUserState.Ready) == true; }); AddStep("unready all players at once", () => { - Debug.Assert(multiplayerClient.Room != null); + Debug.Assert(multiplayerClient.ServerRoom != null); - foreach (var u in multiplayerClient.Room.Users) multiplayerClient.ChangeUserState(u.UserID, MultiplayerUserState.Idle); + foreach (var u in multiplayerClient.ServerRoom.Users) multiplayerClient.ChangeUserState(u.UserID, MultiplayerUserState.Idle); }); AddStep("ready all players at once", () => { - Debug.Assert(multiplayerClient.Room != null); + Debug.Assert(multiplayerClient.ServerRoom != null); - foreach (var u in multiplayerClient.Room.Users) multiplayerClient.ChangeUserState(u.UserID, MultiplayerUserState.Ready); + foreach (var u in multiplayerClient.ServerRoom.Users) multiplayerClient.ChangeUserState(u.UserID, MultiplayerUserState.Ready); }); } @@ -146,7 +146,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void removeLastUser() { - APIUser lastUser = multiplayerClient.Room?.Users.Last().User; + APIUser lastUser = multiplayerClient.ServerRoom?.Users.Last().User; if (lastUser == null || lastUser == multiplayerClient.LocalUser?.User) return; @@ -156,7 +156,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void kickLastUser() { - APIUser lastUser = multiplayerClient.Room?.Users.Last().User; + APIUser lastUser = multiplayerClient.ServerRoom?.Users.Last().User; if (lastUser == null || lastUser == multiplayerClient.LocalUser?.User) return; @@ -166,14 +166,14 @@ namespace osu.Game.Tests.Visual.Multiplayer private void markNextPlayerReady() { - var nextUnready = multiplayerClient.Room?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Idle); + var nextUnready = multiplayerClient.ServerRoom?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Idle); if (nextUnready != null) multiplayerClient.ChangeUserState(nextUnready.UserID, MultiplayerUserState.Ready); } private void markNextPlayerIdle() { - var nextUnready = multiplayerClient.Room?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Ready); + var nextUnready = multiplayerClient.ServerRoom?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Ready); if (nextUnready != null) multiplayerClient.ChangeUserState(nextUnready.UserID, MultiplayerUserState.Idle); } @@ -421,22 +421,22 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; - ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.Room?.Settings.PlaylistItemId); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); - AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == multiplayerClient.Room?.Playlist.First().BeatmapID); + AddUntilStep("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == multiplayerClient.ClientRoom?.Playlist.First().BeatmapID); AddStep("Select next beatmap", () => InputManager.Key(Key.Down)); - AddUntilStep("Beatmap doesn't match current item", () => Beatmap.Value.BeatmapInfo.OnlineID != multiplayerClient.Room?.Playlist.First().BeatmapID); + AddUntilStep("Beatmap doesn't match current item", () => Beatmap.Value.BeatmapInfo.OnlineID != multiplayerClient.ClientRoom?.Playlist.First().BeatmapID); AddStep("start match externally", () => multiplayerClient.StartMatch().WaitSafely()); AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player); - AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == multiplayerClient.Room?.Playlist.First().BeatmapID); + AddUntilStep("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == multiplayerClient.ClientRoom?.Playlist.First().BeatmapID); } [Test] @@ -459,22 +459,22 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; - ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.Room?.Settings.PlaylistItemId); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); - AddAssert("Ruleset matches current item", () => Ruleset.Value.OnlineID == multiplayerClient.Room?.Playlist.First().RulesetID); + AddUntilStep("Ruleset matches current item", () => Ruleset.Value.OnlineID == multiplayerClient.ClientRoom?.Playlist.First().RulesetID); AddStep("Switch ruleset", () => ((MultiplayerMatchSongSelect)multiplayerComponents.MultiplayerScreen.CurrentSubScreen).Ruleset.Value = new CatchRuleset().RulesetInfo); - AddUntilStep("Ruleset doesn't match current item", () => Ruleset.Value.OnlineID != multiplayerClient.Room?.Playlist.First().RulesetID); + AddUntilStep("Ruleset doesn't match current item", () => Ruleset.Value.OnlineID != multiplayerClient.ClientRoom?.Playlist.First().RulesetID); AddStep("start match externally", () => multiplayerClient.StartMatch().WaitSafely()); AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player); - AddAssert("Ruleset matches current item", () => Ruleset.Value.OnlineID == multiplayerClient.Room?.Playlist.First().RulesetID); + AddUntilStep("Ruleset matches current item", () => Ruleset.Value.OnlineID == multiplayerClient.ClientRoom?.Playlist.First().RulesetID); } [Test] @@ -497,25 +497,25 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; - ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.Room?.Settings.PlaylistItemId); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); - AddAssert("Mods match current item", - () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + AddUntilStep("Mods match current item", + () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.ClientRoom.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); AddStep("Switch required mods", () => ((MultiplayerMatchSongSelect)multiplayerComponents.MultiplayerScreen.CurrentSubScreen).Mods.Value = new Mod[] { new OsuModDoubleTime() }); - AddAssert("Mods don't match current item", - () => !SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + AddUntilStep("Mods don't match current item", + () => !SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.ClientRoom.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); AddStep("start match externally", () => multiplayerClient.StartMatch().WaitSafely()); AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player); - AddAssert("Mods match current item", - () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + AddUntilStep("Mods match current item", + () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.ClientRoom.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); } [Test] @@ -890,7 +890,7 @@ namespace osu.Game.Tests.Visual.Multiplayer RulesetID = new OsuRuleset().RulesetInfo.OnlineID, })).WaitSafely()); - AddUntilStep("item arrived in playlist", () => multiplayerClient.Room?.Playlist.Count == 2); + AddUntilStep("item arrived in playlist", () => multiplayerClient.ClientRoom?.Playlist.Count == 2); AddStep("exit gameplay as initial user", () => multiplayerComponents.MultiplayerScreen.MakeCurrent()); AddUntilStep("queue contains item", () => this.ChildrenOfType().Single().Items.Single().ID == 2); @@ -921,10 +921,10 @@ namespace osu.Game.Tests.Visual.Multiplayer RulesetID = new OsuRuleset().RulesetInfo.OnlineID, })).WaitSafely()); - AddUntilStep("item arrived in playlist", () => multiplayerClient.Room?.Playlist.Count == 2); + AddUntilStep("item arrived in playlist", () => multiplayerClient.ClientRoom?.Playlist.Count == 2); AddStep("delete item as other user", () => multiplayerClient.RemoveUserPlaylistItem(1234, 2).WaitSafely()); - AddUntilStep("item removed from playlist", () => multiplayerClient.Room?.Playlist.Count == 1); + AddUntilStep("item removed from playlist", () => multiplayerClient.ClientRoom?.Playlist.Count == 1); AddStep("exit gameplay as initial user", () => multiplayerComponents.MultiplayerScreen.MakeCurrent()); AddUntilStep("queue is empty", () => this.ChildrenOfType().Single().Items.Count == 0); @@ -957,7 +957,7 @@ namespace osu.Game.Tests.Visual.Multiplayer runGameplay(); AddStep("exit gameplay for other user", () => multiplayerClient.ChangeUserState(1234, MultiplayerUserState.Idle)); - AddUntilStep("wait for room to be idle", () => multiplayerClient.Room?.State == MultiplayerRoomState.Open); + AddUntilStep("wait for room to be idle", () => multiplayerClient.ClientRoom?.State == MultiplayerRoomState.Open); runGameplay(); @@ -969,9 +969,9 @@ namespace osu.Game.Tests.Visual.Multiplayer multiplayerClient.StartMatch().WaitSafely(); }); - AddUntilStep("wait for loading", () => multiplayerClient.Room?.State == MultiplayerRoomState.WaitingForLoad); + AddUntilStep("wait for loading", () => multiplayerClient.ClientRoom?.State == MultiplayerRoomState.WaitingForLoad); AddStep("set player loaded", () => multiplayerClient.ChangeUserState(1234, MultiplayerUserState.Loaded)); - AddUntilStep("wait for gameplay to start", () => multiplayerClient.Room?.State == MultiplayerRoomState.Playing); + AddUntilStep("wait for gameplay to start", () => multiplayerClient.ClientRoom?.State == MultiplayerRoomState.Playing); AddUntilStep("wait for local user to enter spectator", () => multiplayerComponents.CurrentScreen is MultiSpectatorScreen); } } @@ -996,7 +996,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("click ready button", () => { - user = playingUserId == null ? multiplayerClient.LocalUser : multiplayerClient.Room?.Users.Single(u => u.UserID == playingUserId); + user = playingUserId == null ? multiplayerClient.LocalUser : multiplayerClient.ServerRoom?.Users.Single(u => u.UserID == playingUserId); lastState = user?.State ?? MultiplayerUserState.Idle; InputManager.MoveMouseTo(readyButton); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 478c8d3318..f9eec6b728 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -152,7 +152,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); - AddUntilStep("match started", () => MultiplayerClient.Room?.State == MultiplayerRoomState.WaitingForLoad); + AddUntilStep("match started", () => MultiplayerClient.ClientRoom?.State == MultiplayerRoomState.WaitingForLoad); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 75e3da05d3..7db18d1127 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -53,14 +53,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("one unique panel", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 1); AddStep("add non-resolvable user", () => MultiplayerClient.TestAddUnresolvedUser()); - AddAssert("null user added", () => MultiplayerClient.Room.AsNonNull().Users.Count(u => u.User == null) == 1); + AddUntilStep("null user added", () => MultiplayerClient.ClientRoom.AsNonNull().Users.Count(u => u.User == null) == 1); AddUntilStep("two unique panels", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 2); AddStep("kick null user", () => this.ChildrenOfType().Single(p => p.User.User == null) .ChildrenOfType().Single().TriggerClick()); - AddAssert("null user kicked", () => MultiplayerClient.Room.AsNonNull().Users.Count == 1); + AddUntilStep("null user kicked", () => MultiplayerClient.ClientRoom.AsNonNull().Users.Count == 1); } [Test] @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("remove host", () => MultiplayerClient.RemoveUser(API.LocalUser.Value)); - AddAssert("single panel is for second user", () => this.ChildrenOfType().Single().User.User == secondUser); + AddAssert("single panel is for second user", () => this.ChildrenOfType().Single().User.UserID == secondUser.Id); } [Test] @@ -217,7 +217,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("kick second user", () => this.ChildrenOfType().Single(d => d.IsPresent).TriggerClick()); - AddAssert("second user kicked", () => MultiplayerClient.Room?.Users.Single().UserID == API.LocalUser.Value.Id); + AddUntilStep("second user kicked", () => MultiplayerClient.ClientRoom?.Users.Single().UserID == API.LocalUser.Value.Id); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs index 300980a53f..9fee771b97 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.ClientAPIRoom, new PlaylistItem(Beatmap.Value.BeatmapInfo) { RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, - }, MultiplayerClient.Room?.Users.ToArray())); + }, MultiplayerClient.ServerRoom?.Users.ToArray())); }); AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 8aa7d5aec2..e709a955b3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -109,7 +109,7 @@ namespace osu.Game.Tests.Visual.Multiplayer assertDeleteButtonVisibility(1, true); AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely()); - AddUntilStep("wait for next item to be selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == 2); + AddUntilStep("wait for next item to be selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == 2); AddUntilStep("wait for two items in playlist", () => playlist.ChildrenOfType().Count() == 2); assertDeleteButtonVisibility(0, false); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 486da48449..91c87548c7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -99,10 +99,10 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestToggleWhenIdle(MultiplayerUserState initialState) { ClickButtonWhenEnabled(); - AddUntilStep("user is spectating", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Spectating); + AddUntilStep("user is spectating", () => MultiplayerClient.ClientRoom?.Users[0].State == MultiplayerUserState.Spectating); ClickButtonWhenEnabled(); - AddUntilStep("user is idle", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Idle); + AddUntilStep("user is idle", () => MultiplayerClient.ClientRoom?.Users[0].State == MultiplayerUserState.Idle); } [TestCase(MultiplayerRoomState.Closed)] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 7d93e550d0..d80537a2e5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -76,8 +76,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddUntilStep("room type is team vs", () => multiplayerClient.Room?.Settings.MatchType == MatchType.TeamVersus); - AddAssert("user state arrived", () => multiplayerClient.Room?.Users.FirstOrDefault()?.MatchState is TeamVersusUserState); + AddUntilStep("room type is team vs", () => multiplayerClient.ClientRoom?.Settings.MatchType == MatchType.TeamVersus); + AddUntilStep("user state arrived", () => multiplayerClient.ClientRoom?.Users.FirstOrDefault()?.MatchState is TeamVersusUserState); } [Test] @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddAssert("user on team 0", () => (multiplayerClient.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); + AddUntilStep("user on team 0", () => (multiplayerClient.ClientRoom?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); AddStep("add another user", () => multiplayerClient.AddUser(new APIUser { Username = "otheruser", Id = 44 })); AddStep("press own button", () => @@ -104,17 +104,17 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.MoveMouseTo(multiplayerComponents.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); - AddAssert("user on team 1", () => (multiplayerClient.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 1); + AddUntilStep("user on team 1", () => (multiplayerClient.ClientRoom?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 1); AddStep("press own button again", () => InputManager.Click(MouseButton.Left)); - AddAssert("user on team 0", () => (multiplayerClient.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); + AddUntilStep("user on team 0", () => (multiplayerClient.ClientRoom?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); AddStep("press other user's button", () => { InputManager.MoveMouseTo(multiplayerComponents.ChildrenOfType().ElementAt(1)); InputManager.Click(MouseButton.Left); }); - AddAssert("user still on team 0", () => (multiplayerClient.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); + AddUntilStep("user still on team 0", () => (multiplayerClient.ClientRoom?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); } [Test] @@ -158,13 +158,13 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddUntilStep("room type is head to head", () => multiplayerClient.Room?.Settings.MatchType == MatchType.HeadToHead); + AddUntilStep("room type is head to head", () => multiplayerClient.ClientRoom?.Settings.MatchType == MatchType.HeadToHead); AddUntilStep("team displays are not displaying teams", () => multiplayerComponents.ChildrenOfType().All(d => d.DisplayedTeam == null)); AddStep("change to team vs", () => multiplayerClient.ChangeSettings(matchType: MatchType.TeamVersus)); - AddUntilStep("room type is team vs", () => multiplayerClient.Room?.Settings.MatchType == MatchType.TeamVersus); + AddUntilStep("room type is team vs", () => multiplayerClient.ClientRoom?.Settings.MatchType == MatchType.TeamVersus); AddUntilStep("team displays are displaying teams", () => multiplayerComponents.ChildrenOfType().All(d => d.DisplayedTeam != null)); } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 22f474ed42..9832acb140 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -413,7 +413,7 @@ namespace osu.Game.Online.Multiplayer UserJoined?.Invoke(user); RoomUpdated?.Invoke(); - }); + }, false); } Task IMultiplayerClient.UserLeft(MultiplayerRoomUser user) => diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 860cf7dee9..3165a4ecff 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -31,7 +31,30 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// The local client's . This is not always equivalent to the server-side room. /// - public Room? ClientAPIRoom => APIRoom; + public Room? ClientAPIRoom => base.APIRoom; + + /// + /// The local client's . This is not always equivalent to the server-side room. + /// + public MultiplayerRoom? ClientRoom => base.Room; + + /// + /// The server's . This is always up-to-date. + /// + public Room? ServerAPIRoom { get; private set; } + + /// + /// The server's . This is always up-to-date. + /// + public MultiplayerRoom? ServerRoom { get; private set; } + + [Obsolete] + protected new Room APIRoom => throw new InvalidOperationException($"Accessing the client-side API room via {nameof(TestMultiplayerClient)} is unsafe. " + + $"Use {nameof(ClientAPIRoom)} if this was intended."); + + [Obsolete] + public new MultiplayerRoom Room => throw new InvalidOperationException($"Accessing the client-side room via {nameof(TestMultiplayerClient)} is unsafe. " + + $"Use {nameof(ClientRoom)} if this was intended."); public Action? RoomSetupAction; @@ -42,17 +65,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private readonly TestMultiplayerRoomManager roomManager; - /// - /// Guaranteed up-to-date playlist. - /// - private readonly List serverSidePlaylist = new List(); - - /// - /// Guaranteed up-to-date API room. - /// - private Room? serverSideAPIRoom; - - private MultiplayerPlaylistItem? currentItem => serverSidePlaylist[currentIndex]; + private MultiplayerPlaylistItem? currentItem => ServerRoom?.Playlist[currentIndex]; private int currentIndex; private long lastPlaylistItemId; @@ -81,150 +94,160 @@ namespace osu.Game.Tests.Visual.Multiplayer private void addUser(MultiplayerRoomUser user) { + Debug.Assert(ServerRoom != null); + + ServerRoom.Users.Add(user); ((IMultiplayerClient)this).UserJoined(clone(user)).WaitSafely(); - // We want the user to be immediately available for testing, so force a scheduler update to run the update-bound continuation. - Scheduler.Update(); - - switch (Room?.MatchState) + switch (ServerRoom?.MatchState) { case TeamVersusRoomState teamVersus: // simulate the server's automatic assignment of users to teams on join. // the "best" team is the one with the least users on it. int bestTeam = teamVersus.Teams - .Select(team => (teamID: team.ID, userCount: Room.Users.Count(u => (u.MatchState as TeamVersusUserState)?.TeamID == team.ID))) + .Select(team => (teamID: team.ID, userCount: ServerRoom.Users.Count(u => (u.MatchState as TeamVersusUserState)?.TeamID == team.ID))) .OrderBy(pair => pair.userCount) .First().teamID; - ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), clone(new TeamVersusUserState { TeamID = bestTeam })).WaitSafely(); + + user.MatchState = new TeamVersusUserState { TeamID = bestTeam }; + ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), clone(user.MatchState)).WaitSafely(); break; } } public void RemoveUser(APIUser user) { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); + ServerRoom.Users.Remove(ServerRoom.Users.Single(u => u.UserID == user.Id)); ((IMultiplayerClient)this).UserLeft(clone(new MultiplayerRoomUser(user.Id))); - Schedule(() => - { - if (Room.Users.Any()) - TransferHost(Room.Users.First().UserID); - }); + if (ServerRoom.Users.Any()) + TransferHost(ServerRoom.Users.First().UserID); } public void ChangeRoomState(MultiplayerRoomState newState) { - ((IMultiplayerClient)this).RoomStateChanged(clone(newState)); + Debug.Assert(ServerRoom != null); + + ServerRoom.State = clone(newState); + + ((IMultiplayerClient)this).RoomStateChanged(clone(ServerRoom.State)); } public void ChangeUserState(int userId, MultiplayerUserState newState) { - ((IMultiplayerClient)this).UserStateChanged(clone(userId), clone(newState)); + Debug.Assert(ServerRoom != null); + + var user = ServerRoom.Users.Single(u => u.UserID == userId); + user.State = clone(newState); + + ((IMultiplayerClient)this).UserStateChanged(clone(userId), clone(user.State)); + updateRoomStateIfRequired(); } private void updateRoomStateIfRequired() { - Schedule(() => + Debug.Assert(ServerRoom != null); + + switch (ServerRoom.State) { - Debug.Assert(Room != null); + case MultiplayerRoomState.Open: + break; - switch (Room.State) - { - case MultiplayerRoomState.Open: - break; + case MultiplayerRoomState.WaitingForLoad: + if (ServerRoom.Users.All(u => u.State != MultiplayerUserState.WaitingForLoad)) + { + var loadedUsers = ServerRoom.Users.Where(u => u.State == MultiplayerUserState.Loaded).ToArray(); - case MultiplayerRoomState.WaitingForLoad: - if (Room.Users.All(u => u.State != MultiplayerUserState.WaitingForLoad)) + if (loadedUsers.Length == 0) { - var loadedUsers = Room.Users.Where(u => u.State == MultiplayerUserState.Loaded).ToArray(); - - if (loadedUsers.Length == 0) - { - // all users have bailed from the load sequence. cancel the game start. - ChangeRoomState(MultiplayerRoomState.Open); - return; - } - - foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.Loaded)) - ChangeUserState(u.UserID, MultiplayerUserState.Playing); - - ((IMultiplayerClient)this).GameplayStarted(); - - ChangeRoomState(MultiplayerRoomState.Playing); - } - - break; - - case MultiplayerRoomState.Playing: - if (Room.Users.All(u => u.State != MultiplayerUserState.Playing)) - { - foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.FinishedPlay)) - ChangeUserState(u.UserID, MultiplayerUserState.Results); - + // all users have bailed from the load sequence. cancel the game start. ChangeRoomState(MultiplayerRoomState.Open); - ((IMultiplayerClient)this).ResultsReady(); - - FinishCurrentItem().WaitSafely(); + return; } - break; - } - }); + foreach (var u in ServerRoom.Users.Where(u => u.State == MultiplayerUserState.Loaded)) + ChangeUserState(u.UserID, MultiplayerUserState.Playing); + + ((IMultiplayerClient)this).GameplayStarted(); + + ChangeRoomState(MultiplayerRoomState.Playing); + } + + break; + + case MultiplayerRoomState.Playing: + if (ServerRoom.Users.All(u => u.State != MultiplayerUserState.Playing)) + { + foreach (var u in ServerRoom.Users.Where(u => u.State == MultiplayerUserState.FinishedPlay)) + ChangeUserState(u.UserID, MultiplayerUserState.Results); + + ChangeRoomState(MultiplayerRoomState.Open); + ((IMultiplayerClient)this).ResultsReady(); + + FinishCurrentItem().WaitSafely(); + } + + break; + } } public void ChangeUserBeatmapAvailability(int userId, BeatmapAvailability newBeatmapAvailability) { - ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(clone(userId), clone(newBeatmapAvailability)); + Debug.Assert(ServerRoom != null); + + var user = ServerRoom.Users.Single(u => u.UserID == userId); + user.BeatmapAvailability = clone(newBeatmapAvailability); + + ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(clone(userId), clone(user.BeatmapAvailability)); } protected override async Task JoinRoom(long roomId, string? password = null) { - serverSideAPIRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == roomId); + ServerAPIRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == roomId); - if (password != serverSideAPIRoom.Password.Value) + if (password != ServerAPIRoom.Password.Value) throw new InvalidOperationException("Invalid password."); - serverSidePlaylist.Clear(); - serverSidePlaylist.AddRange(serverSideAPIRoom.Playlist.Select(item => new MultiplayerPlaylistItem(item))); - lastPlaylistItemId = serverSidePlaylist.Max(item => item.ID); + lastPlaylistItemId = ServerAPIRoom.Playlist.Max(item => item.ID); var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id) { User = api.LocalUser.Value }; - var room = new MultiplayerRoom(roomId) + ServerRoom = new MultiplayerRoom(roomId) { Settings = { - Name = serverSideAPIRoom.Name.Value, - MatchType = serverSideAPIRoom.Type.Value, + Name = ServerAPIRoom.Name.Value, + MatchType = ServerAPIRoom.Type.Value, Password = password, - QueueMode = serverSideAPIRoom.QueueMode.Value, - AutoStartDuration = serverSideAPIRoom.AutoStartDuration.Value + QueueMode = ServerAPIRoom.QueueMode.Value, + AutoStartDuration = ServerAPIRoom.AutoStartDuration.Value }, - Playlist = serverSidePlaylist, + Playlist = ServerAPIRoom.Playlist.Select(item => new MultiplayerPlaylistItem(item)).ToList(), Users = { localUser }, Host = localUser }; - await updatePlaylistOrder(room).ConfigureAwait(false); - await updateCurrentItem(room, false).ConfigureAwait(false); + await updatePlaylistOrder(ServerRoom).ConfigureAwait(false); + await updateCurrentItem(ServerRoom, false).ConfigureAwait(false); - RoomSetupAction?.Invoke(room); + RoomSetupAction?.Invoke(ServerRoom); RoomSetupAction = null; - return clone(room); + return clone(ServerRoom); } protected override void OnRoomJoined() { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); // emulate the server sending this after the join room. scheduler required to make sure the join room event is fired first (in Join). - changeMatchType(Room.Settings.MatchType).WaitSafely(); + changeMatchType(ServerRoom.Settings.MatchType).WaitSafely(); RoomJoined = true; } @@ -235,28 +258,42 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.CompletedTask; } - public override Task TransferHost(int userId) => ((IMultiplayerClient)this).HostChanged(clone(userId)); + public override Task TransferHost(int userId) + { + Debug.Assert(ServerRoom != null); + + ServerRoom.Host = ServerRoom.Users.Single(u => u.UserID == userId); + + return ((IMultiplayerClient)this).HostChanged(clone(userId)); + } public override Task KickUser(int userId) { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); - return ((IMultiplayerClient)this).UserKicked(clone(Room.Users.Single(u => u.UserID == userId))); + var user = ServerRoom.Users.Single(u => u.UserID == userId); + + ServerRoom.Users.Remove(user); + + return ((IMultiplayerClient)this).UserKicked(clone(user)); } public override async Task ChangeSettings(MultiplayerRoomSettings settings) { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); Debug.Assert(currentItem != null); + settings = clone(settings); + // Server is authoritative for the time being. - settings.PlaylistItemId = Room.Settings.PlaylistItemId; + settings.PlaylistItemId = ServerRoom.Settings.PlaylistItemId; + ServerRoom.Settings = settings; await changeQueueMode(settings.QueueMode).ConfigureAwait(false); await ((IMultiplayerClient)this).SettingsChanged(clone(settings)).ConfigureAwait(false); - foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready)) + foreach (var user in ServerRoom.Users.Where(u => u.State == MultiplayerUserState.Ready)) ChangeUserState(user.UserID, MultiplayerUserState.Idle); await changeMatchType(settings.MatchType).ConfigureAwait(false); @@ -283,7 +320,12 @@ namespace osu.Game.Tests.Visual.Multiplayer public void ChangeUserMods(int userId, IEnumerable newMods) { - ((IMultiplayerClient)this).UserModsChanged(clone(userId), clone(newMods)); + Debug.Assert(ServerRoom != null); + + var user = ServerRoom.Users.Single(u => u.UserID == userId); + user.Mods = clone(newMods).ToArray(); + + ((IMultiplayerClient)this).UserModsChanged(clone(userId), clone(user.Mods)); } public override Task ChangeUserMods(IEnumerable newMods) @@ -294,14 +336,14 @@ namespace osu.Game.Tests.Visual.Multiplayer public override async Task SendMatchRequest(MatchUserRequest request) { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); Debug.Assert(LocalUser != null); switch (request) { case ChangeTeamRequest changeTeam: - TeamVersusRoomState roomState = (TeamVersusRoomState)Room.MatchState!; + TeamVersusRoomState roomState = (TeamVersusRoomState)ServerRoom.MatchState!; TeamVersusUserState userState = (TeamVersusUserState)LocalUser.MatchState!; var targetTeam = roomState.Teams.FirstOrDefault(t => t.ID == changeTeam.TeamID); @@ -319,10 +361,10 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task StartMatch() { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); ChangeRoomState(MultiplayerRoomState.WaitingForLoad); - foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready)) + foreach (var user in ServerRoom.Users.Where(u => u.State == MultiplayerUserState.Ready)) ChangeUserState(user.UserID, MultiplayerUserState.WaitingForLoad); return ((IMultiplayerClient)this).LoadRequested(); @@ -339,35 +381,35 @@ namespace osu.Game.Tests.Visual.Multiplayer public async Task AddUserPlaylistItem(int userId, MultiplayerPlaylistItem item) { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); Debug.Assert(currentItem != null); - if (Room.Settings.QueueMode == QueueMode.HostOnly && Room.Host?.UserID != LocalUser?.UserID) + if (ServerRoom.Settings.QueueMode == QueueMode.HostOnly && ServerRoom.Host?.UserID != LocalUser?.UserID) throw new InvalidOperationException("Local user is not the room host."); item.OwnerID = userId; await addItem(item).ConfigureAwait(false); - await updateCurrentItem(Room).ConfigureAwait(false); + await updateCurrentItem(ServerRoom).ConfigureAwait(false); updateRoomStateIfRequired(); } - public override Task AddPlaylistItem(MultiplayerPlaylistItem item) => AddUserPlaylistItem(api.LocalUser.Value.OnlineID, item); + public override Task AddPlaylistItem(MultiplayerPlaylistItem item) => AddUserPlaylistItem(api.LocalUser.Value.OnlineID, clone(item)); public async Task EditUserPlaylistItem(int userId, MultiplayerPlaylistItem item) { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); Debug.Assert(currentItem != null); - Debug.Assert(serverSideAPIRoom != null); + Debug.Assert(ServerAPIRoom != null); item.OwnerID = userId; - var existingItem = serverSidePlaylist.SingleOrDefault(i => i.ID == item.ID); + var existingItem = ServerRoom.Playlist.SingleOrDefault(i => i.ID == item.ID); if (existingItem == null) throw new InvalidOperationException("Attempted to change an item that doesn't exist."); - if (existingItem.OwnerID != userId && Room.Host?.UserID != LocalUser?.UserID) + if (existingItem.OwnerID != userId && ServerRoom.Host?.UserID != LocalUser?.UserID) throw new InvalidOperationException("Attempted to change an item which is not owned by the user."); if (existingItem.Expired) @@ -376,20 +418,20 @@ namespace osu.Game.Tests.Visual.Multiplayer // Ensure the playlist order doesn't change. item.PlaylistOrder = existingItem.PlaylistOrder; - serverSidePlaylist[serverSidePlaylist.IndexOf(existingItem)] = item; - serverSideAPIRoom.Playlist[serverSideAPIRoom.Playlist.IndexOf(serverSideAPIRoom.Playlist.Single(i => i.ID == item.ID))] = new PlaylistItem(item); + ServerRoom.Playlist[ServerRoom.Playlist.IndexOf(existingItem)] = item; + ServerAPIRoom.Playlist[ServerAPIRoom.Playlist.IndexOf(ServerAPIRoom.Playlist.Single(i => i.ID == item.ID))] = new PlaylistItem(item); await ((IMultiplayerClient)this).PlaylistItemChanged(clone(item)).ConfigureAwait(false); } - public override Task EditPlaylistItem(MultiplayerPlaylistItem item) => EditUserPlaylistItem(api.LocalUser.Value.OnlineID, item); + public override Task EditPlaylistItem(MultiplayerPlaylistItem item) => EditUserPlaylistItem(api.LocalUser.Value.OnlineID, clone(item)); public async Task RemoveUserPlaylistItem(int userId, long playlistItemId) { - Debug.Assert(Room != null); - Debug.Assert(serverSideAPIRoom != null); + Debug.Assert(ServerRoom != null); + Debug.Assert(ServerAPIRoom != null); - var item = serverSidePlaylist.Find(i => i.ID == playlistItemId); + var item = ServerRoom.Playlist.FirstOrDefault(i => i.ID == playlistItemId); if (item == null) throw new InvalidOperationException("Item does not exist in the room."); @@ -403,11 +445,11 @@ namespace osu.Game.Tests.Visual.Multiplayer if (item.Expired) throw new InvalidOperationException("Attempted to remove an item which has already been played."); - serverSidePlaylist.Remove(item); - serverSideAPIRoom.Playlist.RemoveAll(i => i.ID == item.ID); + ServerRoom.Playlist.Remove(item); + ServerAPIRoom.Playlist.RemoveAll(i => i.ID == item.ID); await ((IMultiplayerClient)this).PlaylistItemRemoved(clone(playlistItemId)).ConfigureAwait(false); - await updateCurrentItem(Room).ConfigureAwait(false); + await updateCurrentItem(ServerRoom).ConfigureAwait(false); updateRoomStateIfRequired(); } @@ -415,42 +457,52 @@ namespace osu.Game.Tests.Visual.Multiplayer private async Task changeMatchType(MatchType type) { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); switch (type) { case MatchType.HeadToHead: - await ((IMultiplayerClient)this).MatchRoomStateChanged(null).ConfigureAwait(false); + ServerRoom.MatchState = null; + await ((IMultiplayerClient)this).MatchRoomStateChanged(clone(ServerRoom.MatchState)).ConfigureAwait(false); + + foreach (var user in ServerRoom.Users) + { + user.MatchState = null; + await ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), clone(user.MatchState)).ConfigureAwait(false); + } - foreach (var user in Room.Users) - await ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), null).ConfigureAwait(false); break; case MatchType.TeamVersus: - await ((IMultiplayerClient)this).MatchRoomStateChanged(clone(TeamVersusRoomState.CreateDefault())).ConfigureAwait(false); + ServerRoom.MatchState = TeamVersusRoomState.CreateDefault(); + await ((IMultiplayerClient)this).MatchRoomStateChanged(clone(ServerRoom.MatchState)).ConfigureAwait(false); + + foreach (var user in ServerRoom.Users) + { + user.MatchState = new TeamVersusUserState(); + await ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), clone(user.MatchState)).ConfigureAwait(false); + } - foreach (var user in Room.Users) - await ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), clone(new TeamVersusUserState())).ConfigureAwait(false); break; } } private async Task changeQueueMode(QueueMode newMode) { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); Debug.Assert(currentItem != null); // When changing to host-only mode, ensure that at least one non-expired playlist item exists by duplicating the current item. - if (newMode == QueueMode.HostOnly && serverSidePlaylist.All(item => item.Expired)) + if (newMode == QueueMode.HostOnly && ServerRoom.Playlist.All(item => item.Expired)) await duplicateCurrentItem().ConfigureAwait(false); - await updatePlaylistOrder(Room).ConfigureAwait(false); - await updateCurrentItem(Room).ConfigureAwait(false); + await updatePlaylistOrder(ServerRoom).ConfigureAwait(false); + await updateCurrentItem(ServerRoom).ConfigureAwait(false); } public async Task FinishCurrentItem() { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); Debug.Assert(currentItem != null); // Expire the current playlist item. @@ -458,13 +510,13 @@ namespace osu.Game.Tests.Visual.Multiplayer currentItem.PlayedAt = DateTimeOffset.Now; await ((IMultiplayerClient)this).PlaylistItemChanged(clone(currentItem)).ConfigureAwait(false); - await updatePlaylistOrder(Room).ConfigureAwait(false); + await updatePlaylistOrder(ServerRoom).ConfigureAwait(false); // In host-only mode, a duplicate playlist item will be used for the next round. - if (Room.Settings.QueueMode == QueueMode.HostOnly && serverSidePlaylist.All(item => item.Expired)) + if (ServerRoom.Settings.QueueMode == QueueMode.HostOnly && ServerRoom.Playlist.All(item => item.Expired)) await duplicateCurrentItem().ConfigureAwait(false); - await updateCurrentItem(Room).ConfigureAwait(false); + await updateCurrentItem(ServerRoom).ConfigureAwait(false); } private async Task duplicateCurrentItem() @@ -483,26 +535,28 @@ namespace osu.Game.Tests.Visual.Multiplayer private async Task addItem(MultiplayerPlaylistItem item) { - Debug.Assert(Room != null); - Debug.Assert(serverSideAPIRoom != null); + Debug.Assert(ServerRoom != null); + Debug.Assert(ServerAPIRoom != null); item.ID = ++lastPlaylistItemId; - serverSidePlaylist.Add(item); - serverSideAPIRoom.Playlist.Add(new PlaylistItem(item)); + ServerRoom.Playlist.Add(item); + ServerAPIRoom.Playlist.Add(new PlaylistItem(item)); await ((IMultiplayerClient)this).PlaylistItemAdded(clone(item)).ConfigureAwait(false); - await updatePlaylistOrder(Room).ConfigureAwait(false); + await updatePlaylistOrder(ServerRoom).ConfigureAwait(false); } - private IEnumerable upcomingItems => serverSidePlaylist.Where(i => !i.Expired).OrderBy(i => i.PlaylistOrder); + private IEnumerable upcomingItems => ServerRoom?.Playlist.Where(i => !i.Expired).OrderBy(i => i.PlaylistOrder) ?? Enumerable.Empty(); private async Task updateCurrentItem(MultiplayerRoom room, bool notify = true) { - // Pick the next non-expired playlist item by playlist order, or default to the most-recently-expired item. - MultiplayerPlaylistItem nextItem = upcomingItems.FirstOrDefault() ?? serverSidePlaylist.OrderByDescending(i => i.PlayedAt).First(); + Debug.Assert(ServerRoom != null); - currentIndex = serverSidePlaylist.IndexOf(nextItem); + // Pick the next non-expired playlist item by playlist order, or default to the most-recently-expired item. + MultiplayerPlaylistItem nextItem = upcomingItems.FirstOrDefault() ?? ServerRoom.Playlist.OrderByDescending(i => i.PlayedAt).First(); + + currentIndex = ServerRoom.Playlist.IndexOf(nextItem); long lastItem = room.Settings.PlaylistItemId; room.Settings.PlaylistItemId = nextItem.ID; @@ -513,21 +567,22 @@ namespace osu.Game.Tests.Visual.Multiplayer private async Task updatePlaylistOrder(MultiplayerRoom room) { - Debug.Assert(serverSideAPIRoom != null); + Debug.Assert(ServerRoom != null); + Debug.Assert(ServerAPIRoom != null); List orderedActiveItems; switch (room.Settings.QueueMode) { default: - orderedActiveItems = serverSidePlaylist.Where(item => !item.Expired).OrderBy(item => item.ID).ToList(); + orderedActiveItems = ServerRoom.Playlist.Where(item => !item.Expired).OrderBy(item => item.ID).ToList(); break; case QueueMode.AllPlayersRoundRobin: var itemsByPriority = new List<(MultiplayerPlaylistItem item, int priority)>(); // Assign a priority for items from each user, starting from 0 and increasing in order which the user added the items. - foreach (var group in serverSidePlaylist.Where(item => !item.Expired).OrderBy(item => item.ID).GroupBy(item => item.OwnerID)) + foreach (var group in ServerRoom.Playlist.Where(item => !item.Expired).OrderBy(item => item.ID).GroupBy(item => item.OwnerID)) { int priority = 0; itemsByPriority.AddRange(group.Select(item => (item, priority++))); @@ -562,8 +617,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } // Also ensure that the API room's playlist is correct. - foreach (var item in serverSideAPIRoom.Playlist) - item.PlaylistOrder = serverSidePlaylist.Single(i => i.ID == item.ID).PlaylistOrder; + foreach (var item in ServerAPIRoom.Playlist) + item.PlaylistOrder = ServerRoom.Playlist.Single(i => i.ID == item.ID).PlaylistOrder; } private T clone(T incoming) From 37acec1952f58552d3c0d1b721a12561fc71ef7c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 19:15:31 +0900 Subject: [PATCH 333/803] Isolate client's LocalUser from TestMultiplayerClient --- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 3165a4ecff..7c14851176 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -56,6 +56,8 @@ namespace osu.Game.Tests.Visual.Multiplayer public new MultiplayerRoom Room => throw new InvalidOperationException($"Accessing the client-side room via {nameof(TestMultiplayerClient)} is unsafe. " + $"Use {nameof(ClientRoom)} if this was intended."); + public new MultiplayerRoomUser? LocalUser => ServerRoom?.Users.SingleOrDefault(u => u.User?.Id == API.LocalUser.Value.Id); + public Action? RoomSetupAction; public bool RoomJoined { get; private set; } From 084a65578cc532482a2377754b72129e090b7deb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 19:36:37 +0900 Subject: [PATCH 334/803] Always clone messages incoming from client-side --- .../Multiplayer/TestMultiplayerClient.cs | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 7c14851176..19b887eea5 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -201,13 +201,16 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(ServerRoom != null); var user = ServerRoom.Users.Single(u => u.UserID == userId); - user.BeatmapAvailability = clone(newBeatmapAvailability); + user.BeatmapAvailability = newBeatmapAvailability; ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(clone(userId), clone(user.BeatmapAvailability)); } protected override async Task JoinRoom(long roomId, string? password = null) { + roomId = clone(roomId); + password = clone(password); + ServerAPIRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == roomId); if (password != ServerAPIRoom.Password.Value) @@ -262,6 +265,8 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task TransferHost(int userId) { + userId = clone(userId); + Debug.Assert(ServerRoom != null); ServerRoom.Host = ServerRoom.Users.Single(u => u.UserID == userId); @@ -271,10 +276,11 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task KickUser(int userId) { + userId = clone(userId); + Debug.Assert(ServerRoom != null); var user = ServerRoom.Users.Single(u => u.UserID == userId); - ServerRoom.Users.Remove(user); return ((IMultiplayerClient)this).UserKicked(clone(user)); @@ -282,11 +288,11 @@ namespace osu.Game.Tests.Visual.Multiplayer public override async Task ChangeSettings(MultiplayerRoomSettings settings) { + settings = clone(settings); + Debug.Assert(ServerRoom != null); Debug.Assert(currentItem != null); - settings = clone(settings); - // Server is authoritative for the time being. settings.PlaylistItemId = ServerRoom.Settings.PlaylistItemId; ServerRoom.Settings = settings; @@ -304,40 +310,44 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task ChangeState(MultiplayerUserState newState) { + newState = clone(newState); + if (newState == MultiplayerUserState.Idle && LocalUser?.State == MultiplayerUserState.WaitingForLoad) return Task.CompletedTask; - ChangeUserState(api.LocalUser.Value.Id, newState); + ChangeUserState(api.LocalUser.Value.Id, clone(newState)); return Task.CompletedTask; } public override Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability) { - ChangeUserBeatmapAvailability(api.LocalUser.Value.Id, newBeatmapAvailability); + ChangeUserBeatmapAvailability(api.LocalUser.Value.Id, clone(newBeatmapAvailability)); return Task.CompletedTask; } public void ChangeUserMods(int userId, IEnumerable newMods) - => ChangeUserMods(userId, newMods.Select(m => new APIMod(m)).ToList()); + => ChangeUserMods(userId, newMods.Select(m => new APIMod(m))); public void ChangeUserMods(int userId, IEnumerable newMods) { Debug.Assert(ServerRoom != null); var user = ServerRoom.Users.Single(u => u.UserID == userId); - user.Mods = clone(newMods).ToArray(); + user.Mods = newMods.ToArray(); ((IMultiplayerClient)this).UserModsChanged(clone(userId), clone(user.Mods)); } public override Task ChangeUserMods(IEnumerable newMods) { - ChangeUserMods(api.LocalUser.Value.Id, newMods); + ChangeUserMods(api.LocalUser.Value.Id, clone(newMods)); return Task.CompletedTask; } public override async Task SendMatchRequest(MatchUserRequest request) { + request = clone(request); + Debug.Assert(ServerRoom != null); Debug.Assert(LocalUser != null); @@ -455,7 +465,7 @@ namespace osu.Game.Tests.Visual.Multiplayer updateRoomStateIfRequired(); } - public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, playlistItemId); + public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, clone(playlistItemId)); private async Task changeMatchType(MatchType type) { From 93c68e4329fca6158d62984a0c714eb157f825f5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 19:46:46 +0900 Subject: [PATCH 335/803] More test hardening around ClientAPIRoom usages --- .../Visual/Multiplayer/QueueModeTestScene.cs | 2 +- .../Multiplayer/TestSceneAllPlayersQueueMode.cs | 16 ++++++++-------- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 14 +++++++------- .../Visual/Multiplayer/TestSceneMultiplayer.cs | 10 +++++----- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../Multiplayer/TestSceneMultiplayerPlayer.cs | 2 +- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 53bed75723..2b461cf6f6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestCreatedWithCorrectMode() { - AddAssert("room created with correct mode", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == Mode); + AddUntilStep("room created with correct mode", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == Mode); } protected void RunGameplay() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index a94a60f12d..5947cabf7f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -37,10 +37,10 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestItemAddedToTheEndOfQueue() { addItem(() => OtherBeatmap); - AddAssert("playlist has 2 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); + AddUntilStep("playlist has 2 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); addItem(() => InitialBeatmap); - AddAssert("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3); + AddUntilStep("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3); AddUntilStep("first item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } @@ -50,8 +50,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { RunGameplay(); - AddAssert("playlist has only one item", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 1); - AddAssert("playlist item is expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); + AddUntilStep("playlist has only one item", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 1); + AddUntilStep("playlist item is expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); AddUntilStep("last item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } @@ -63,12 +63,12 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); - AddAssert("first item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); + AddUntilStep("first item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); AddUntilStep("next item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID); RunGameplay(); - AddAssert("second item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == true); + AddUntilStep("second item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == true); AddUntilStep("next item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[2].ID); } @@ -82,8 +82,8 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); AddStep("change queue mode", () => MultiplayerClient.ChangeSettings(queueMode: QueueMode.HostOnly)); - AddAssert("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3); - AddAssert("item 2 is not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false); + AddUntilStep("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3); + AddUntilStep("item 2 is not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false); AddUntilStep("current item is the other beatmap", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == 2); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index 15a1354c98..800b523a9d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -48,9 +48,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { RunGameplay(); - AddAssert("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); - AddAssert("first playlist item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); - AddAssert("second playlist item not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false); + AddUntilStep("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); + AddUntilStep("first playlist item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); + AddUntilStep("second playlist item not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false); AddUntilStep("second playlist item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID); } @@ -60,12 +60,12 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); IBeatmapInfo firstBeatmap = null; - AddStep("get first playlist item beatmap", () => firstBeatmap = MultiplayerClient.ClientAPIRoom?.Playlist[0].Beatmap); + AddStep("get first playlist item beatmap", () => firstBeatmap = MultiplayerClient.ServerAPIRoom?.Playlist[0].Beatmap); selectNewItem(() => OtherBeatmap); - AddAssert("first playlist item hasn't changed", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Beatmap == firstBeatmap); - AddAssert("second playlist item changed", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Beatmap != firstBeatmap); + AddUntilStep("first playlist item hasn't changed", () => MultiplayerClient.ServerAPIRoom?.Playlist[0].Beatmap == firstBeatmap); + AddUntilStep("second playlist item changed", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Beatmap != firstBeatmap); } [Test] @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { addItem(() => OtherBeatmap); - AddAssert("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); + AddUntilStep("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); } private void selectNewItem(Func beatmap) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 193d1d4248..da48fb7332 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -243,8 +243,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddAssert("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1); - AddAssert("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); + AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1); + AddUntilStep("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } [Test] @@ -303,8 +303,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); AddUntilStep("wait for join", () => multiplayerClient.RoomJoined); - AddAssert("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1); - AddAssert("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); + AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1); + AddUntilStep("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } [Test] @@ -323,7 +323,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddAssert("room has password", () => multiplayerClient.ClientAPIRoom?.Password.Value == "password"); + AddUntilStep("room has password", () => multiplayerClient.ClientAPIRoom?.Password.Value == "password"); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index f9eec6b728..aa935cb1d4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -254,7 +254,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("last playlist item selected", () => { - var lastItem = this.ChildrenOfType().Single(p => p.Item.ID == MultiplayerClient.ClientAPIRoom?.Playlist.Last().ID); + var lastItem = this.ChildrenOfType().Single(p => p.Item.ID == MultiplayerClient.ServerAPIRoom?.Playlist.Last().ID); return lastItem.IsSelectedItem; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs index 9fee771b97..f6a6b3c667 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("initialise gameplay", () => { - Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.ClientAPIRoom, new PlaylistItem(Beatmap.Value.BeatmapInfo) + Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.ServerAPIRoom, new PlaylistItem(Beatmap.Value.BeatmapInfo) { RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, }, MultiplayerClient.ServerRoom?.Users.ToArray())); From 6995b34b08884384528d0a35afa9da56ad7c9985 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Fri, 1 Jul 2022 13:14:11 +0200 Subject: [PATCH 336/803] Add Android joystick settings --- osu.Android/AndroidJoystickSettings.cs | 76 ++++++++++++++++++++++++++ osu.Android/OsuGameAndroid.cs | 3 + 2 files changed, 79 insertions(+) create mode 100644 osu.Android/AndroidJoystickSettings.cs diff --git a/osu.Android/AndroidJoystickSettings.cs b/osu.Android/AndroidJoystickSettings.cs new file mode 100644 index 0000000000..26e921a426 --- /dev/null +++ b/osu.Android/AndroidJoystickSettings.cs @@ -0,0 +1,76 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Android.Input; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Game.Localisation; +using osu.Game.Overlays.Settings; + +namespace osu.Android +{ + public class AndroidJoystickSettings : SettingsSubsection + { + protected override LocalisableString Header => JoystickSettingsStrings.JoystickGamepad; + + private readonly AndroidJoystickHandler joystickHandler; + + private readonly Bindable enabled = new BindableBool(true); + + private SettingsSlider deadzoneSlider = null!; + + private Bindable handlerDeadzone = null!; + + private Bindable localDeadzone = null!; + + public AndroidJoystickSettings(AndroidJoystickHandler joystickHandler) + { + this.joystickHandler = joystickHandler; + } + + [BackgroundDependencyLoader] + private void load() + { + // use local bindable to avoid changing enabled state of game host's bindable. + handlerDeadzone = joystickHandler.DeadzoneThreshold.GetBoundCopy(); + localDeadzone = handlerDeadzone.GetUnboundCopy(); + + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = CommonStrings.Enabled, + Current = enabled + }, + deadzoneSlider = new SettingsSlider + { + LabelText = JoystickSettingsStrings.DeadzoneThreshold, + KeyboardStep = 0.01f, + DisplayAsPercentage = true, + Current = localDeadzone, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + enabled.BindTo(joystickHandler.Enabled); + enabled.BindValueChanged(e => deadzoneSlider.Current.Disabled = !e.NewValue, true); + + handlerDeadzone.BindValueChanged(val => + { + bool disabled = localDeadzone.Disabled; + + localDeadzone.Disabled = false; + localDeadzone.Value = val.NewValue; + localDeadzone.Disabled = disabled; + }, true); + + localDeadzone.BindValueChanged(val => handlerDeadzone.Value = val.NewValue); + } + } +} diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 636fc7d2df..062f2ce10c 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -96,6 +96,9 @@ namespace osu.Android case AndroidMouseHandler mh: return new AndroidMouseSettings(mh); + case AndroidJoystickHandler jh: + return new AndroidJoystickSettings(jh); + default: return base.CreateSettingsSubsectionFor(handler); } From 9d28d5f8ee5472356d6ef5fb2a992965b656d751 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 1 Jul 2022 20:43:12 +0900 Subject: [PATCH 337/803] Update SFX for mod overlay show/hide --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 39 ++++++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 69cb3a49fc..bf5200902c 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -28,6 +30,9 @@ namespace osu.Game.Overlays.Mods { public const int BUTTON_WIDTH = 200; + protected override string PopInSampleName => ""; + protected override string PopOutSampleName => @"SongSelect/mod-select-overlay-pop-out"; + [Cached] public Bindable> SelectedMods { get; private set; } = new Bindable>(Array.Empty()); @@ -101,17 +106,21 @@ namespace osu.Game.Overlays.Mods private ShearedToggleButton? customisationButton; + private Sample? columnAppearSample; + protected ModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green) : base(colourScheme) { } [BackgroundDependencyLoader] - private void load(OsuGameBase game, OsuColour colours) + private void load(OsuGameBase game, OsuColour colours, AudioManager audio) { Header.Title = ModSelectOverlayStrings.ModSelectTitle; Header.Description = ModSelectOverlayStrings.ModSelectDescription; + columnAppearSample = audio.Samples.Get(@"SongSelect/mod-column-pop-in"); + AddRange(new Drawable[] { new ClickToReturnContainer @@ -453,8 +462,32 @@ namespace osu.Game.Overlays.Mods .MoveToY(0, duration, Easing.OutQuint) .FadeIn(duration, Easing.OutQuint); - if (!allFiltered) - nonFilteredColumnCount += 1; + if (allFiltered) + continue; + + int columnNumber = nonFilteredColumnCount; + Scheduler.AddDelayed(() => + { + var channel = columnAppearSample?.GetChannel(); + if (channel == null) return; + + // don't play sample if column isn't visible, but try to play it at least 5 times (assuming there's at least 5 visible columns in `columnFlow`) + if (columnNumber > 5 && !column.Active.Value) return; + + // use X position of the column on screen as a basis for panning the sample + var bounds = column.Parent.BoundingBox; + float balance = (bounds.TopLeft.X + bounds.Width / 2) / RelativeToAbsoluteFactor.X; + + // dip frequency and ramp volume of sample over the first 5 displayed columns + float progress = Math.Min(1, columnNumber / 5f); + + channel.Frequency.Value = 1.3 - (progress * 0.3) + RNG.NextDouble(0.1); + channel.Volume.Value = Math.Max(progress, 0.2); + channel.Balance.Value = -1 + balance * 2; + channel.Play(); + }, delay); + + nonFilteredColumnCount += 1; } } From 6644f9a72a4fd7d9dbf96d00572eb1e57a7019bf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 21:14:05 +0900 Subject: [PATCH 338/803] Fix completely incorrect test --- .../NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index 0bcb38327e..d1c5e2d8b3 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer AddStep("add user", () => MultiplayerClient.AddUser(user)); AddUntilStep("room has 2 users", () => MultiplayerClient.ClientRoom?.Users.Count == 2); - AddRepeatStep("remove user multiple times", () => MultiplayerClient.RemoveUser(user), 3); + AddStep("remove user", () => MultiplayerClient.RemoveUser(user)); AddUntilStep("room has 1 user", () => MultiplayerClient.ClientRoom?.Users.Count == 1); } From 76d4f86ca3616bc00b9fb62b62244496ab8c5661 Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Fri, 1 Jul 2022 18:21:03 -0700 Subject: [PATCH 339/803] Make legacy slider ball fade out instantly --- .../Skinning/Legacy/LegacySliderBall.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs index 07e60c82d0..f3be52b227 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs @@ -7,6 +7,8 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK.Graphics; @@ -18,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private readonly ISkin skin; + private DrawableSlider slider; + private Sprite layerNd; private Sprite layerSpec; @@ -30,8 +34,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } [BackgroundDependencyLoader] - private void load() + private void load(DrawableHitObject dho) { + slider = (DrawableSlider)dho; + var ballColour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White; InternalChildren = new[] @@ -56,6 +62,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Blending = BlendingParameters.Additive, }, }; + + slider.ApplyCustomUpdateState += updateStateTransforms; } protected override void UpdateAfterChildren() @@ -68,5 +76,25 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy layerNd.Rotation = -appliedRotation; layerSpec.Rotation = -appliedRotation; } + + private void updateStateTransforms(DrawableHitObject obj, ArmedState _) + { + if (obj is not DrawableSlider) + return; + + using (BeginAbsoluteSequence(slider.StateUpdateTime)) + this.FadeIn(); + + using (BeginAbsoluteSequence(slider.HitStateUpdateTime)) + this.FadeOut(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (slider != null) + slider.ApplyCustomUpdateState -= updateStateTransforms; + } } } From 623a99d53abda7e654e4e7a660e0364e2ef54026 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 11:58:45 +0900 Subject: [PATCH 340/803] 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 584fe0a3ef..86f4337521 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 ff223f5107..40c22c1b21 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 b8a4aca02e..09ae70a01f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From 634b6cdbbeba1bd768d812fa57eb7ad51db994ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 12:16:16 +0900 Subject: [PATCH 341/803] Send beatmap has to server on solo score request Right now, the client does nothing to ensure a beatmap is in a valid state before requesting to submit a score. There is further work to be done client-side so it is more aware of this state (already handled for playlists, but not for the solo gameplay loop), but the solution I have in mind for that is a bit more involved. This is not used server-side yet, but I want to get this sending so we can start using it for some very basic validation. Will resolve the basic portion of #11922 after implemented server-side. --- osu.Game/Online/Solo/CreateSoloScoreRequest.cs | 10 ++++++---- osu.Game/Screens/Play/SoloPlayer.cs | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Solo/CreateSoloScoreRequest.cs b/osu.Game/Online/Solo/CreateSoloScoreRequest.cs index e4612b6659..8c92b32915 100644 --- a/osu.Game/Online/Solo/CreateSoloScoreRequest.cs +++ b/osu.Game/Online/Solo/CreateSoloScoreRequest.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Net.Http; using osu.Framework.IO.Network; +using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.Rooms; @@ -13,13 +14,13 @@ namespace osu.Game.Online.Solo { public class CreateSoloScoreRequest : APIRequest { - private readonly int beatmapId; + private readonly BeatmapInfo beatmapInfo; private readonly int rulesetId; private readonly string versionHash; - public CreateSoloScoreRequest(int beatmapId, int rulesetId, string versionHash) + public CreateSoloScoreRequest(BeatmapInfo beatmapInfo, int rulesetId, string versionHash) { - this.beatmapId = beatmapId; + this.beatmapInfo = beatmapInfo; this.rulesetId = rulesetId; this.versionHash = versionHash; } @@ -29,10 +30,11 @@ namespace osu.Game.Online.Solo var req = base.CreateWebRequest(); req.Method = HttpMethod.Post; req.AddParameter("version_hash", versionHash); + req.AddParameter("beatmap_hash", beatmapInfo.MD5Hash); req.AddParameter("ruleset_id", rulesetId.ToString(CultureInfo.InvariantCulture)); return req; } - protected override string Target => $@"beatmaps/{beatmapId}/solo/scores"; + protected override string Target => $@"beatmaps/{beatmapInfo.OnlineID}/solo/scores"; } } diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index b65602e7a8..565f256277 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Play if (!Ruleset.Value.IsLegacyRuleset()) return null; - return new CreateSoloScoreRequest(beatmapId, rulesetId, Game.VersionHash); + return new CreateSoloScoreRequest(Beatmap.Value.BeatmapInfo, rulesetId, Game.VersionHash); } protected override bool HandleTokenRetrievalFailure(Exception exception) => false; From 4fd47b5fa0fffdc882779ac11a78b7bbc0f492b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 12:35:29 +0900 Subject: [PATCH 342/803] Add more verbose logging to realm blocking process --- osu.Game.Tests/Database/GeneralUsageTests.cs | 8 ++++---- osu.Game.Tests/Database/RealmLiveTests.cs | 2 +- .../Database/RealmSubscriptionRegistrationTests.cs | 12 ++++++------ osu.Game/Database/EFToRealmMigrator.cs | 2 +- osu.Game/Database/RealmAccess.cs | 11 ++++++++--- osu.Game/OsuGameBase.cs | 2 +- .../Sections/DebugSettings/MemorySettings.cs | 4 ++-- 7 files changed, 23 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 5b6f7a0a53..fd0b391d0d 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, _) => { - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) { } }); @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Database { Task writeTask; - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) { writeTask = realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); Thread.Sleep(100); @@ -169,7 +169,7 @@ namespace osu.Game.Tests.Database Assert.Throws(() => { - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) { } }); @@ -177,7 +177,7 @@ namespace osu.Game.Tests.Database stopThreadedUsage.Set(); // Ensure we can block a second time after the usage has ended. - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) { } }); diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index fd1f564f59..a50eb22c67 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Database { migratedStorage.DeleteDirectory(string.Empty); - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) { storage.Migrate(migratedStorage); } diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index c74341b5c9..4ee302bbd0 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Database resolvedItems = null; lastChanges = null; - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) Assert.That(resolvedItems, Is.Empty); realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); @@ -154,7 +154,7 @@ namespace osu.Game.Tests.Database testEventsArriving(false); // And make sure even after another context loss we don't get firings. - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) Assert.That(resolvedItems, Is.Null); realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); @@ -212,7 +212,7 @@ namespace osu.Game.Tests.Database Assert.That(beatmapSetInfo, Is.Not.Null); - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) { // custom disposal action fired when context lost. Assert.That(beatmapSetInfo, Is.Null); @@ -226,7 +226,7 @@ namespace osu.Game.Tests.Database Assert.That(beatmapSetInfo, Is.Null); - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) Assert.That(beatmapSetInfo, Is.Null); realm.Run(r => r.Refresh()); @@ -251,7 +251,7 @@ namespace osu.Game.Tests.Database Assert.That(receivedValue, Is.Not.Null); receivedValue = null; - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) { } @@ -262,7 +262,7 @@ namespace osu.Game.Tests.Database subscription.Dispose(); receivedValue = null; - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) Assert.That(receivedValue, Is.Null); realm.Run(r => r.Refresh()); diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 896d111989..8f2ff600d8 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -126,7 +126,7 @@ namespace osu.Game.Database string backupSuffix = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; // required for initial backup. - var realmBlockOperations = realm.BlockAllOperations(); + var realmBlockOperations = realm.BlockAllOperations("EF migration"); Task.Factory.StartNew(() => { diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 8cf9bb4a47..9c7ecd8a03 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -780,7 +780,7 @@ namespace osu.Game.Database public void CreateBackup(string backupFilename, IDisposable? blockAllOperations = null) { - using (blockAllOperations ?? BlockAllOperations()) + using (blockAllOperations ?? BlockAllOperations("creating backup")) { Logger.Log($"Creating full realm database backup at {backupFilename}", LoggingTarget.Database); @@ -811,9 +811,12 @@ namespace osu.Game.Database /// 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. /// + /// The reason for blocking. Used for logging purposes. /// An which should be disposed to end the blocking section. - public IDisposable BlockAllOperations() + public IDisposable BlockAllOperations(string reason) { + Logger.Log($@"Attempting to block all realm operations for {reason}.", LoggingTarget.Database); + if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); @@ -843,7 +846,7 @@ namespace osu.Game.Database updateRealm = null; } - Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); + Logger.Log(@"Lock acquired for blocking operations", LoggingTarget.Database); const int sleep_length = 200; int timeout = 5000; @@ -867,6 +870,8 @@ namespace osu.Game.Database Logger.Log($"Realm compact failed with error {e}", LoggingTarget.Database); } + Logger.Log(@"Realm usage isolated via compact", LoggingTarget.Database); + // In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm, // and must be posted to the synchronization context. // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 9a001c92cd..ead3eeb0dc 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -446,7 +446,7 @@ namespace osu.Game Scheduler.Add(() => { - realmBlocker = realm.BlockAllOperations(); + realmBlocker = realm.BlockAllOperations("migration"); readyToRun.Set(); }, false); diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index 77eede0e46..42ac4adb34 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings Action = () => { // Blocking operations implicitly causes a Compact(). - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("compact")) { } } @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { try { - var token = realm.BlockAllOperations(); + var token = realm.BlockAllOperations("maintenance"); blockAction.Enabled.Value = false; From 1fd9c6480394798d4b0fb814fbed9a3b067db2c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 12:48:33 +0900 Subject: [PATCH 343/803] Change song select `FooterButton` sound --- osu.Game/Screens/Select/FooterButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index c251e94ef2..6c9032f5d8 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Select private readonly Box light; public FooterButton() - : base(HoverSampleSet.Toolbar) + : base(HoverSampleSet.Default) { AutoSizeAxes = Axes.Both; Shear = SHEAR; From 426c53fe65e07b39e3aadb09c478d94f67d2ad57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 12:50:28 +0900 Subject: [PATCH 344/803] Fix potential null being sent to extension method which doesn't support it --- osu.Game/Skinning/DefaultSkin.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 2467b7d039..5267861e3e 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -113,12 +113,12 @@ namespace osu.Game.Skinning accuracy.Position = new Vector2(-accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 - horizontal_padding, vertical_offset + 5); accuracy.Origin = Anchor.TopRight; accuracy.Anchor = Anchor.TopCentre; - } - if (combo != null) - { - combo.Position = new Vector2(accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 + horizontal_padding, vertical_offset + 5); - combo.Anchor = Anchor.TopCentre; + if (combo != null) + { + combo.Position = new Vector2(accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 + horizontal_padding, vertical_offset + 5); + combo.Anchor = Anchor.TopCentre; + } } var hitError = container.OfType().FirstOrDefault(); From a03abc747b9ff4042d1be6117f7de620884da249 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 12:58:34 +0900 Subject: [PATCH 345/803] Tidy up comments and simplify bounding box centre logic --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 5 ++--- osu.Game/Screens/Select/FooterButton.cs | 10 ++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index bf5200902c..04c424461e 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -471,12 +471,11 @@ namespace osu.Game.Overlays.Mods var channel = columnAppearSample?.GetChannel(); if (channel == null) return; - // don't play sample if column isn't visible, but try to play it at least 5 times (assuming there's at least 5 visible columns in `columnFlow`) + // Still play sound effects for off-screen columns up to a certain point. if (columnNumber > 5 && !column.Active.Value) return; // use X position of the column on screen as a basis for panning the sample - var bounds = column.Parent.BoundingBox; - float balance = (bounds.TopLeft.X + bounds.Width / 2) / RelativeToAbsoluteFactor.X; + float balance = column.Parent.BoundingBox.Centre.X / RelativeToAbsoluteFactor.X; // dip frequency and ramp volume of sample over the first 5 displayed columns float progress = Math.Min(1, columnNumber / 5f); diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 6c9032f5d8..3f8cf2e13a 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -4,19 +4,18 @@ #nullable disable using System; -using osuTK; -using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Localisation; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; -using osu.Framework.Input.Bindings; -using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Select { @@ -68,7 +67,6 @@ namespace osu.Game.Screens.Select private readonly Box light; public FooterButton() - : base(HoverSampleSet.Default) { AutoSizeAxes = Axes.Both; Shear = SHEAR; From 1a14808c396856cabe0048a7d0c0b89b4752cec2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 13:03:13 +0900 Subject: [PATCH 346/803] Fix `BeatmapInfoWedge` test potentially failing due to quick initial load Was doing a comparison of previous vs new content after explicitly doing a load in a previous step. --- .../Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index f9d18f4236..a144111fd3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -53,13 +53,8 @@ namespace osu.Game.Tests.Visual.SongSelect Margin = new MarginPadding { Top = 20 } }); - AddStep("show", () => - { - infoWedge.Show(); - infoWedge.Beatmap = Beatmap.Value; - }); + AddStep("show", () => infoWedge.Show()); - // select part is redundant, but wait for load isn't selectBeatmap(Beatmap.Value.Beatmap); AddWaitStep("wait for select", 3); From 2352636bf2455b8f8e111df0aadc6c2858a7f427 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 13:11:49 +0900 Subject: [PATCH 347/803] Fix ID clash with announce and PM channels in chat overlay tests Yet Another Issue. https://github.com/ppy/osu/runs/7159306924?check_suite_focus=true Not sure why announce channel was using a user id for its channel ID. Bad copy paste? --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 247ea52648..0b982a5745 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -576,12 +576,13 @@ namespace osu.Game.Tests.Visual.Online private Channel createAnnounceChannel() { - int id = RNG.Next(0, DummyAPIAccess.DUMMY_USER_ID - 1); + const int announce_channel_id = 133337; + return new Channel { - Name = $"Announce {id}", + Name = $"Announce {announce_channel_id}", Type = ChannelType.Announce, - Id = id, + Id = announce_channel_id, }; } From 2e3ff2c7e0a86b1d9e03859fa6f83bc057d9d962 Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Fri, 1 Jul 2022 21:12:36 -0700 Subject: [PATCH 348/803] Prefer [Resolved] and LoadComplete --- .../Skinning/Legacy/LegacySliderBall.cs | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs index f3be52b227..fdf7f18bb9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs @@ -1,14 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK.Graphics; @@ -20,10 +18,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private readonly ISkin skin; - private DrawableSlider slider; + [Resolved(canBeNull: true)] + private DrawableHitObject? drawableObject { get; set; } - private Sprite layerNd; - private Sprite layerSpec; + private Sprite layerNd = null!; + private Sprite layerSpec = null!; public LegacySliderBall(Drawable animationContent, ISkin skin) { @@ -36,8 +35,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy [BackgroundDependencyLoader] private void load(DrawableHitObject dho) { - slider = (DrawableSlider)dho; - var ballColour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White; InternalChildren = new[] @@ -62,8 +59,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Blending = BlendingParameters.Additive, }, }; + } - slider.ApplyCustomUpdateState += updateStateTransforms; + protected override void LoadComplete() + { + base.LoadComplete(); + + if (drawableObject != null) + { + drawableObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(drawableObject, drawableObject.State.Value); + } } protected override void UpdateAfterChildren() @@ -79,13 +85,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void updateStateTransforms(DrawableHitObject obj, ArmedState _) { - if (obj is not DrawableSlider) - return; - - using (BeginAbsoluteSequence(slider.StateUpdateTime)) + using (BeginAbsoluteSequence(drawableObject.AsNonNull().StateUpdateTime)) this.FadeIn(); - using (BeginAbsoluteSequence(slider.HitStateUpdateTime)) + using (BeginAbsoluteSequence(drawableObject.AsNonNull().HitStateUpdateTime)) this.FadeOut(); } @@ -93,8 +96,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { base.Dispose(isDisposing); - if (slider != null) - slider.ApplyCustomUpdateState -= updateStateTransforms; + if (drawableObject != null) + drawableObject.ApplyCustomUpdateState -= updateStateTransforms; } } } From 6d6ffd35d01c9ffb8d40943fa50776ef3974a2be Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Fri, 1 Jul 2022 21:14:53 -0700 Subject: [PATCH 349/803] Remove unused parameter --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs index fdf7f18bb9..57422e3709 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(DrawableHitObject dho) + private void load() { var ballColour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White; From 471b64bd20619ff1124193cde8bc9621bce4d476 Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Fri, 1 Jul 2022 21:19:54 -0700 Subject: [PATCH 350/803] Reinstate early return with comment Whoops, didn't read the reply until after I pushed... --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs index 57422e3709..e9fa5cbe43 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK.Graphics; @@ -85,6 +86,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void updateStateTransforms(DrawableHitObject obj, ArmedState _) { + // Gets called by slider ticks, tails, etc., leading to duplicated + // animations which in this case have no visual impact (due to + // instant fade) but may negatively affect performance + if (obj is not DrawableSlider) + return; + using (BeginAbsoluteSequence(drawableObject.AsNonNull().StateUpdateTime)) this.FadeIn(); From f2141715b8c6f22eba91df5f98d1f1eb91017c35 Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Fri, 1 Jul 2022 21:22:48 -0700 Subject: [PATCH 351/803] Replace AsNonNull() with Assert() --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs index e9fa5cbe43..d0745450da 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs @@ -1,8 +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.Diagnostics; using osu.Framework.Allocation; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -92,10 +92,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (obj is not DrawableSlider) return; - using (BeginAbsoluteSequence(drawableObject.AsNonNull().StateUpdateTime)) + Debug.Assert(drawableObject != null); + + using (BeginAbsoluteSequence(drawableObject.StateUpdateTime)) this.FadeIn(); - using (BeginAbsoluteSequence(drawableObject.AsNonNull().HitStateUpdateTime)) + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) this.FadeOut(); } From 9a5431d93f2688a01249729619d9154af84f2f9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 13:36:41 +0900 Subject: [PATCH 352/803] Minor renaming / refactoring to use local parameter rather than DI'd object --- .../Skinning/Legacy/LegacySliderBall.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs index d0745450da..414879f42d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.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.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private readonly ISkin skin; [Resolved(canBeNull: true)] - private DrawableHitObject? drawableObject { get; set; } + private DrawableHitObject? parentObject { get; set; } private Sprite layerNd = null!; private Sprite layerSpec = null!; @@ -66,10 +65,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { base.LoadComplete(); - if (drawableObject != null) + if (parentObject != null) { - drawableObject.ApplyCustomUpdateState += updateStateTransforms; - updateStateTransforms(drawableObject, drawableObject.State.Value); + parentObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(parentObject, parentObject.State.Value); } } @@ -84,16 +83,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy layerSpec.Rotation = -appliedRotation; } - private void updateStateTransforms(DrawableHitObject obj, ArmedState _) + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState _) { // Gets called by slider ticks, tails, etc., leading to duplicated // animations which in this case have no visual impact (due to // instant fade) but may negatively affect performance - if (obj is not DrawableSlider) + if (drawableObject is not DrawableSlider) return; - Debug.Assert(drawableObject != null); - using (BeginAbsoluteSequence(drawableObject.StateUpdateTime)) this.FadeIn(); @@ -105,8 +102,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { base.Dispose(isDisposing); - if (drawableObject != null) - drawableObject.ApplyCustomUpdateState -= updateStateTransforms; + if (parentObject != null) + parentObject.ApplyCustomUpdateState -= updateStateTransforms; } } } From fa5f7c1a00e084eb4498c99228408f17f6172de4 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 2 Jul 2022 13:03:01 +0800 Subject: [PATCH 353/803] Remove the nullable disable annotation in the Replays namespace. --- osu.Game/Replays/Legacy/LegacyReplayFrame.cs | 2 -- osu.Game/Replays/Legacy/ReplayButtonState.cs | 2 -- osu.Game/Replays/Replay.cs | 2 -- 3 files changed, 6 deletions(-) diff --git a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs index d0574d65ab..f6abf259e8 100644 --- a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs +++ b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using MessagePack; using Newtonsoft.Json; using osu.Framework.Extensions.EnumExtensions; diff --git a/osu.Game/Replays/Legacy/ReplayButtonState.cs b/osu.Game/Replays/Legacy/ReplayButtonState.cs index 7788918ba9..4b02cf2cd5 100644 --- a/osu.Game/Replays/Legacy/ReplayButtonState.cs +++ b/osu.Game/Replays/Legacy/ReplayButtonState.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; namespace osu.Game.Replays.Legacy diff --git a/osu.Game/Replays/Replay.cs b/osu.Game/Replays/Replay.cs index 4903f8c47f..30e176b5c7 100644 --- a/osu.Game/Replays/Replay.cs +++ b/osu.Game/Replays/Replay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Replays; From ea573f314e9db529e163aef109f5f3b3c163df6f Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 2 Jul 2022 13:08:25 +0800 Subject: [PATCH 354/803] Remove the nullable disable annotation in the Rulesets.Replays namespace in the osu.game project. --- osu.Game/Rulesets/Replays/AutoGenerator.cs | 8 ++------ .../Rulesets/Replays/Types/IConvertibleReplayFrame.cs | 4 +--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Replays/AutoGenerator.cs b/osu.Game/Rulesets/Replays/AutoGenerator.cs index 7fab84eba8..f4b96b3884 100644 --- a/osu.Game/Rulesets/Replays/AutoGenerator.cs +++ b/osu.Game/Rulesets/Replays/AutoGenerator.cs @@ -1,11 +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 disable - using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Game.Beatmaps; using osu.Game.Replays; using osu.Game.Rulesets.Objects; @@ -34,7 +31,7 @@ namespace osu.Game.Rulesets.Replays /// public abstract Replay Generate(); - protected virtual HitObject GetNextObject(int currentIndex) + protected virtual HitObject? GetNextObject(int currentIndex) { if (currentIndex >= Beatmap.HitObjects.Count - 1) return null; @@ -51,8 +48,7 @@ namespace osu.Game.Rulesets.Replays /// protected readonly List Frames = new List(); - [CanBeNull] - protected TFrame LastFrame => Frames.Count == 0 ? null : Frames[^1]; + protected TFrame? LastFrame => Frames.Count == 0 ? null : Frames[^1]; protected AutoGenerator(IBeatmap beatmap) : base(beatmap) diff --git a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs index 6753a23bca..9a4af9e4ee 100644 --- a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Replays.Legacy; @@ -19,7 +17,7 @@ namespace osu.Game.Rulesets.Replays.Types /// The to extract values from. /// The beatmap. /// The last post-conversion , used to fill in missing delta information. May be null. - void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null); + void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null); /// /// Populates this using values from a . From c6d0f0f81b11389c817306e534bbcc9b1629bc02 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 2 Jul 2022 13:20:46 +0800 Subject: [PATCH 355/803] pretend that the beatmap property will not be null. Not really throw exception will be the better way? --- osu.Game/Online/Spectator/SpectatorClient.cs | 2 +- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 08e1e78d86..46e58217c5 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -188,7 +188,7 @@ namespace osu.Game.Online.Spectator } if (frame is IConvertibleReplayFrame convertible) - pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap)); + pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap!)); if (pendingFrames.Count > max_pending_frames) purgePendingFrames(); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 85cf463a13..4b1931e7d1 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -143,7 +143,7 @@ namespace osu.Game.Scoring.Legacy return legacyFrame; case IConvertibleReplayFrame convertibleFrame: - return convertibleFrame.ToLegacy(beatmap); + return convertibleFrame.ToLegacy(beatmap!); default: throw new ArgumentException(@"Frame could not be converted to legacy frames", nameof(replayFrame)); From 19721a9bbbff59ca99f19989404885ed0e50d322 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 2 Jul 2022 13:33:05 +0800 Subject: [PATCH 356/803] Remove the nullable disable annotation and fix the breaking api. --- osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs | 2 -- .../Replays/CatchFramedReplayInputHandler.cs | 2 -- osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs | 6 ++---- osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs | 6 ++---- .../Replays/ManiaFramedReplayInputHandler.cs | 2 -- osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs | 4 +--- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 6 ++---- osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs | 2 -- .../Replays/OsuFramedReplayInputHandler.cs | 2 -- osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs | 4 +--- osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs | 2 -- .../Replays/TaikoFramedReplayInputHandler.cs | 2 -- osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs | 4 +--- 13 files changed, 9 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 86c249a7c1..7c84cb24f3 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Utils; diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index 64a573149f..b6af88a771 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Framework.Input.StateChanges; diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs index 5cf03e4706..e30e535e9b 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Replays.Legacy; @@ -22,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Replays { } - public CatchReplayFrame(double time, float? position = null, bool dashing = false, CatchReplayFrame lastFrame = null) + public CatchReplayFrame(double time, float? position = null, bool dashing = false, CatchReplayFrame? lastFrame = null) : base(time) { Position = position ?? -1; @@ -40,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.Replays } } - public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null) { Position = currentFrame.Position.X; Dashing = currentFrame.ButtonState == ReplayButtonState.Left1; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 26572de412..ba4dfc7ab0 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Mania.Beatmaps; @@ -85,7 +83,7 @@ namespace osu.Game.Rulesets.Mania.Replays } } - private double calculateReleaseTime(HitObject currentObject, HitObject nextObject) + private double calculateReleaseTime(HitObject currentObject, HitObject? nextObject) { double endTime = currentObject.GetEndTime(); @@ -99,7 +97,7 @@ namespace osu.Game.Rulesets.Mania.Replays return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject.StartTime - endTime) * 0.9); } - protected override HitObject GetNextObject(int currentIndex) + protected override HitObject? GetNextObject(int currentIndex) { int desiredColumn = Beatmap.HitObjects[currentIndex].Column; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs index c786e1db61..aa164f95da 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Framework.Input.StateChanges; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index 66edc87992..29249ba474 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Game.Beatmaps; @@ -27,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Replays Actions.AddRange(actions); } - public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null) { var maniaBeatmap = (ManiaBeatmap)beatmap; diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index b0155c02cf..5a3d882ef0 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osuTK; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -95,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Replays { double endTime = prev.GetEndTime(); - HitWindows hitWindows = null; + HitWindows? hitWindows = null; switch (h) { @@ -245,7 +243,7 @@ namespace osu.Game.Rulesets.Osu.Replays } double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime); - OsuReplayFrame lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null; + OsuReplayFrame? lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null; if (timeDifference > 0) { diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index b41d123380..1cb3208c30 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osuTK; using osu.Game.Beatmaps; using System; diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs index 8857bfa32d..ea36ecc399 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Framework.Input.StateChanges; diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs index 019d8035ed..85060261fe 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Replays.Legacy; @@ -28,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Replays Actions.AddRange(actions); } - public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null) { Position = currentFrame.Position; if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton); diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index f7f72df023..5fd281f9fa 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs index 0090409443..2f9b6c7f60 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Replays; using System.Collections.Generic; using System.Linq; diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs index d8f88785db..a0a687dca6 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Replays.Legacy; @@ -25,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Replays Actions.AddRange(actions); } - public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null) { if (currentFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim); if (currentFrame.MouseRight2) Actions.Add(TaikoAction.RightRim); From 59c83a34235c8fd37a6c8a1fca14b0bbe9f4b2a1 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 2 Jul 2022 13:33:51 +0800 Subject: [PATCH 357/803] Mark as non-null because next object in here will always has value. --- osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs | 2 +- osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index ba4dfc7ab0..77ca7ab6b8 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Mania.Replays bool canDelayKeyUpFully = nextObject == null || nextObject.StartTime > endTime + RELEASE_DELAY; - return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject.StartTime - endTime) * 0.9); + return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject!.StartTime - endTime) * 0.9); } protected override HitObject? GetNextObject(int currentIndex) diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 5fd281f9fa..44c68e6af1 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Taiko.Replays var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button bool canDelayKeyUp = nextHitObject == null || nextHitObject.StartTime > endTime + KEY_UP_DELAY; - double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject.StartTime - endTime) * 0.9; + double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject!.StartTime - endTime) * 0.9; Frames.Add(new TaikoReplayFrame(endTime + calculatedDelay)); hitButton = !hitButton; From 19d300e311438668fea18237a99bf9134a86f4a5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 2 Jul 2022 09:17:38 +0300 Subject: [PATCH 358/803] Fix player loader not accepting star difficulty updates after display --- .../Screens/Play/BeatmapMetadataDisplay.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index dca9401872..0e56cafaaa 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -197,21 +197,19 @@ namespace osu.Game.Screens.Play starRatingDisplay.Show(); } else - { starRatingDisplay.Hide(); - starDifficulty.ValueChanged += d => - { - Debug.Assert(d.NewValue != null); + starDifficulty.ValueChanged += d => + { + Debug.Assert(d.NewValue != null); - starRatingDisplay.Current.Value = d.NewValue.Value; + starRatingDisplay.Current.Value = d.NewValue.Value; - versionFlow.AutoSizeDuration = 300; - versionFlow.AutoSizeEasing = Easing.OutQuint; + versionFlow.AutoSizeDuration = 300; + versionFlow.AutoSizeEasing = Easing.OutQuint; - starRatingDisplay.FadeIn(300, Easing.InQuint); - }; - } + starRatingDisplay.FadeIn(300, Easing.InQuint); + }; } private class MetadataLineLabel : OsuSpriteText From 93809a92d4d9fdf2e355fc81e03c65c783c2958b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 16:30:04 +0900 Subject: [PATCH 359/803] Fix clashing error messaging during realm block operations --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 9c7ecd8a03..cfb0280d2d 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -860,7 +860,7 @@ namespace osu.Game.Database timeout -= sleep_length; if (timeout < 0) - throw new TimeoutException(@"Took too long to acquire lock"); + throw new TimeoutException($@"Realm compact failed after {timeout / sleep_length} attempts over {timeout / 1000} seconds"); } } catch (RealmException e) From e28ee8bc7a7bc6ce7628cc1ee9e50a1c8e783719 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 2 Jul 2022 10:46:52 +0300 Subject: [PATCH 360/803] Count time spent upwards to display attempts correctly --- osu.Game/Database/RealmAccess.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index cfb0280d2d..ed56049064 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -849,7 +849,7 @@ namespace osu.Game.Database Logger.Log(@"Lock acquired for blocking operations", LoggingTarget.Database); const int sleep_length = 200; - int timeout = 5000; + int timeSpent = 0; try { @@ -857,10 +857,10 @@ namespace osu.Game.Database while (!Compact()) { Thread.Sleep(sleep_length); - timeout -= sleep_length; + timeSpent += sleep_length; - if (timeout < 0) - throw new TimeoutException($@"Realm compact failed after {timeout / sleep_length} attempts over {timeout / 1000} seconds"); + if (timeSpent > 5000) + throw new TimeoutException($@"Realm compact failed after {timeSpent / sleep_length} attempts over {timeSpent / 1000} seconds"); } } catch (RealmException e) From 24cd845d1b9f69b24a23f4bcc53cb4d4b4c83933 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 17:18:50 +0900 Subject: [PATCH 361/803] 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 86f4337521..ceea60a1c1 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 40c22c1b21..560f7409ff 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 09ae70a01f..0133c6334c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From b626135940eb728c64acc49dd0facc98f7e8b10b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 04:39:59 +0900 Subject: [PATCH 362/803] Fix dragging slider bars on nub not performing correctly relative movement --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 3356153e17..7cd89e5b87 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -165,6 +165,9 @@ namespace osu.Game.Graphics.UserInterface base.OnHoverLost(e); } + protected override bool ShouldHandleAsRelativeDrag(MouseDownEvent e) + => Nub.ReceivePositionalInputAt(e.ScreenSpaceMouseDownPosition); + protected override void OnDragEnd(DragEndEvent e) { updateGlow(); From 0a1543c6e8b143c7f3db436c0a0517c93331668d Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 2 Jul 2022 19:48:32 +0800 Subject: [PATCH 363/803] Use `AsNonNull()` instead. --- osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs | 3 ++- osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs | 3 ++- osu.Game/Online/Spectator/SpectatorClient.cs | 3 ++- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 77ca7ab6b8..7c8afdff12 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; @@ -94,7 +95,7 @@ namespace osu.Game.Rulesets.Mania.Replays bool canDelayKeyUpFully = nextObject == null || nextObject.StartTime > endTime + RELEASE_DELAY; - return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject!.StartTime - endTime) * 0.9); + return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject.AsNonNull().StartTime - endTime) * 0.9); } protected override HitObject? GetNextObject(int currentIndex) diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 44c68e6af1..11136ad695 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Replays; @@ -117,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko.Replays var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button bool canDelayKeyUp = nextHitObject == null || nextHitObject.StartTime > endTime + KEY_UP_DELAY; - double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject!.StartTime - endTime) * 0.9; + double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject.AsNonNull().StartTime - endTime) * 0.9; Frames.Add(new TaikoReplayFrame(endTime + calculatedDelay)); hitButton = !hitButton; diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 46e58217c5..28aa81c9ee 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Development; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Beatmaps; @@ -188,7 +189,7 @@ namespace osu.Game.Online.Spectator } if (frame is IConvertibleReplayFrame convertible) - pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap!)); + pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap.AsNonNull())); if (pendingFrames.Count > max_pending_frames) purgePendingFrames(); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 4b1931e7d1..44ffe6b6ad 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Text; using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; @@ -143,7 +144,7 @@ namespace osu.Game.Scoring.Legacy return legacyFrame; case IConvertibleReplayFrame convertibleFrame: - return convertibleFrame.ToLegacy(beatmap!); + return convertibleFrame.ToLegacy(beatmap.AsNonNull()); default: throw new ArgumentException(@"Frame could not be converted to legacy frames", nameof(replayFrame)); From 7d8ea5e286fca76aba8b7d8202cee750d8bb1404 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 Jul 2022 02:59:10 +0900 Subject: [PATCH 364/803] Remove unnecessary proxying --- .../Select/DifficultyRangeFilterControl.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs index 7115c59ebf..ac30cc5491 100644 --- a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs @@ -23,37 +23,32 @@ namespace osu.Game.Screens.Select private Bindable lowerStars; private Bindable upperStars; - private StarsSlider lowerSlider; - private MaximumStarsSlider upperSlider; - [BackgroundDependencyLoader] private void load(OsuConfigManager config) { const float vertical_offset = 13; - InternalChildren = new[] + InternalChildren = new Drawable[] { new OsuSpriteText { Text = "Difficulty range", Font = OsuFont.GetFont(size: 14), }, - upperSlider = new MaximumStarsSlider + new MaximumStarsSlider { Current = config.GetBindable(OsuSetting.DisplayStarsMaximum), KeyboardStep = 0.1f, RelativeSizeAxes = Axes.X, Y = vertical_offset, }, - lowerSlider = new MinimumStarsSlider + new MinimumStarsSlider { Current = config.GetBindable(OsuSetting.DisplayStarsMinimum), KeyboardStep = 0.1f, RelativeSizeAxes = Axes.X, Y = vertical_offset, - }, - upperSlider.Nub.CreateProxy(), - lowerSlider.Nub.CreateProxy(), + } }; lowerStars = config.GetBindable(OsuSetting.DisplayStarsMinimum); @@ -107,8 +102,6 @@ namespace osu.Game.Screens.Select ? UserInterfaceStrings.NoLimit : Current.Value.ToString(@"0.## stars"); - public new Nub Nub => base.Nub; - protected override void LoadComplete() { base.LoadComplete(); From 052a786eff3233fc8fb04750099705da9033007e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 Jul 2022 03:06:03 +0900 Subject: [PATCH 365/803] Only show hover effect on one nub at a time --- osu.Game/Screens/Select/DifficultyRangeFilterControl.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs index ac30cc5491..2884211df3 100644 --- a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics; @@ -102,6 +103,12 @@ namespace osu.Game.Screens.Select ? UserInterfaceStrings.NoLimit : Current.Value.ToString(@"0.## stars"); + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + return true; // Make sure only one nub shows hover effect at once. + } + protected override void LoadComplete() { base.LoadComplete(); From 8c2f4b48fc4420e5d0992d0e1a4383dedc711bbd Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 3 Jul 2022 19:27:56 +0800 Subject: [PATCH 366/803] Use debug.assert for better readable. --- osu.Game/Online/Spectator/SpectatorClient.cs | 6 ++++-- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 28aa81c9ee..68c8b57019 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Development; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Beatmaps; @@ -189,7 +188,10 @@ namespace osu.Game.Online.Spectator } if (frame is IConvertibleReplayFrame convertible) - pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap.AsNonNull())); + { + Debug.Assert(currentBeatmap != null); + pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap)); + } if (pendingFrames.Count > max_pending_frames) purgePendingFrames(); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 44ffe6b6ad..750bb50be3 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -2,11 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using osu.Framework.Extensions; -using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; @@ -144,7 +144,8 @@ namespace osu.Game.Scoring.Legacy return legacyFrame; case IConvertibleReplayFrame convertibleFrame: - return convertibleFrame.ToLegacy(beatmap.AsNonNull()); + Debug.Assert(beatmap != null); + return convertibleFrame.ToLegacy(beatmap); default: throw new ArgumentException(@"Frame could not be converted to legacy frames", nameof(replayFrame)); From 724fd19038851078034c5253d64b669fa2d10eee Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 3 Jul 2022 21:15:13 +0900 Subject: [PATCH 367/803] Remove nullable disables --- .../SongSelect/TestSceneDifficultyRangeFilterControl.cs | 1 - osu.Game/Screens/Select/DifficultyRangeFilterControl.cs | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs index ffad15287b..7ae2c6e5e2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.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. -#nullable disable using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Screens.Select; diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs index 2884211df3..a82c969805 100644 --- a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.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. -#nullable disable using System; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -21,8 +20,8 @@ namespace osu.Game.Screens.Select { internal class DifficultyRangeFilterControl : CompositeDrawable { - private Bindable lowerStars; - private Bindable upperStars; + private Bindable lowerStars = null!; + private Bindable upperStars = null!; [BackgroundDependencyLoader] private void load(OsuConfigManager config) @@ -97,8 +96,6 @@ namespace osu.Game.Screens.Select private class StarsSlider : OsuSliderBar { - private OsuSpriteText currentDisplay; - public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : Current.Value.ToString(@"0.## stars"); @@ -115,6 +112,8 @@ namespace osu.Game.Screens.Select Nub.Width = Nub.HEIGHT; RangePadding = Nub.Width / 2; + OsuSpriteText currentDisplay; + Nub.Add(currentDisplay = new OsuSpriteText { Anchor = Anchor.Centre, From 59e3d4b9e571cf46abd21e520ba316d4c3bff056 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 3 Jul 2022 21:00:54 +0800 Subject: [PATCH 368/803] Remove the nullable disable annotation in the Ruleset.Filter namespace. --- osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs index fa44f81df3..dd2ad2cbfa 100644 --- a/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs +++ b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; From a7bcc32cc21151ff4817e260b73c8f1d1a75b185 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 3 Jul 2022 21:04:39 +0800 Subject: [PATCH 369/803] Remove the nullable disable annotation in the mania ruleset. --- osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs index 58aef4dbf8..c8832dfdfb 100644 --- a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs +++ b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Rulesets.Filter; using osu.Game.Rulesets.Mania.Beatmaps; From 14ef7712903145d55ec0c6f0ca01f1bc980c2fd2 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 3 Jul 2022 21:05:14 +0800 Subject: [PATCH 370/803] Remove the nullable disable annotation in the test case. --- osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs | 2 -- osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 2657468b03..33204d33a7 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets; diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 500f3159e2..bd0617515b 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using NUnit.Framework; using osu.Game.Beatmaps; @@ -256,7 +254,7 @@ namespace osu.Game.Tests.NonVisual.Filtering private class CustomFilterCriteria : IRulesetFilterCriteria { - public string CustomValue { get; set; } + public string? CustomValue { get; set; } public bool Matches(BeatmapInfo beatmapInfo) => true; From e4bf2224c6d1493d54154b72ece66215be5892b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 Jul 2022 23:16:51 +0900 Subject: [PATCH 371/803] 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 ceea60a1c1..550088cbfa 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 560f7409ff..d428139343 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 0133c6334c..e3e8aecbbe 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From df152421a8e5b75ab2af2d522343828bce780328 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 3 Jul 2022 10:23:17 -0700 Subject: [PATCH 372/803] Fix personal best score showing delete option on context menu --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index e32bc63aa1..62827f50aa 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -426,10 +426,10 @@ namespace osu.Game.Online.Leaderboards items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = Score.Mods)); if (Score.Files.Count > 0) + { items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => new LegacyScoreExporter(storage).Export(Score))); - - if (!isOnlineScope) items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score)))); + } return items.ToArray(); } From e6a05ce3e2d8312581dd660ec9518bebee4b230f Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Sun, 3 Jul 2022 13:51:30 -0700 Subject: [PATCH 373/803] Slow down legacy followcircle animations --- .../Objects/Drawables/DrawableSlider.cs | 4 -- .../Objects/Drawables/DrawableSliderBall.cs | 11 ++-- .../Skinning/Default/DefaultFollowCircle.cs | 50 ++++++++++++++ .../Skinning/Default/DefaultSliderBall.cs | 63 ++++++++++++++++-- .../Skinning/Legacy/LegacyFollowCircle.cs | 65 +++++++++++++++++++ 5 files changed, 175 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 91bb7f95f6..d83f5df7a3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -319,13 +319,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables const float fade_out_time = 450; - // intentionally pile on an extra FadeOut to make it happen much faster. - Ball.FadeOut(fade_out_time / 4, Easing.Out); - switch (state) { case ArmedState.Hit: - Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out); if (SliderBody?.SnakingOut.Value == true) Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear. break; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs index 7bde60b39d..6bfb4e8aae 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs @@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour { + public const float FOLLOW_AREA = 2.4f; + public Func GetInitialHitAction; public Color4 AccentColour @@ -31,7 +33,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables set => ball.Colour = value; } - private Drawable followCircle; private Drawable followCircleReceptor; private DrawableSlider drawableSlider; private Drawable ball; @@ -47,12 +48,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Children = new[] { - followCircle = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()) + new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()) { Origin = Anchor.Centre, Anchor = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Alpha = 0, }, followCircleReceptor = new CircularContainer { @@ -103,10 +103,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables tracking = value; - followCircleReceptor.Scale = new Vector2(tracking ? 2.4f : 1f); - - followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint); - followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint); + followCircleReceptor.Scale = new Vector2(tracking ? FOLLOW_AREA : 1f); } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 8211448705..c39a07da4e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -1,17 +1,25 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Default { public class DefaultFollowCircle : CompositeDrawable { + [Resolved(canBeNull: true)] + private DrawableHitObject? parentObject { get; set; } + public DefaultFollowCircle() { + Alpha = 0f; RelativeSizeAxes = Axes.Both; InternalChild = new CircularContainer @@ -29,5 +37,47 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default } }; } + + [BackgroundDependencyLoader] + private void load() + { + if (parentObject != null) + { + var slider = (DrawableSlider)parentObject; + slider.Tracking.BindValueChanged(trackingChanged, true); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (parentObject != null) + { + parentObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(parentObject, parentObject.State.Value); + } + } + + private void trackingChanged(ValueChangedEvent tracking) + { + const float scale_duration = 300f; + const float fade_duration = 300f; + + this.ScaleTo(tracking.NewValue ? DrawableSliderBall.FOLLOW_AREA : 1f, scale_duration, Easing.OutQuint) + .FadeTo(tracking.NewValue ? 1f : 0, fade_duration, Easing.OutQuint); + } + + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) + { + if (drawableObject is not DrawableSlider) + return; + + const float fade_time = 450f; + + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + // intentionally pile on an extra FadeOut to make it happen much faster. + this.FadeOut(fade_time / 4, Easing.Out); + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs index 47308375e6..3c7dd7ba5d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -19,13 +17,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { public class DefaultSliderBall : CompositeDrawable { - private Box box; + private Box box = null!; + + [Resolved(canBeNull: true)] + private DrawableHitObject? parentObject { get; set; } [BackgroundDependencyLoader] - private void load(DrawableHitObject drawableObject, ISkinSource skin) + private void load(ISkinSource skin) { - var slider = (DrawableSlider)drawableObject; - RelativeSizeAxes = Axes.Both; float radius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS; @@ -51,10 +50,60 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default } }; - slider.Tracking.BindValueChanged(trackingChanged, true); + if (parentObject != null) + { + var slider = (DrawableSlider)parentObject; + slider.Tracking.BindValueChanged(trackingChanged, true); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (parentObject != null) + { + parentObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(parentObject, parentObject.State.Value); + } } private void trackingChanged(ValueChangedEvent tracking) => box.FadeTo(tracking.NewValue ? 0.3f : 0.05f, 200, Easing.OutQuint); + + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) + { + if (drawableObject is not DrawableSlider) + return; + + const float fade_out_time = 450f; + + using (BeginAbsoluteSequence(drawableObject.StateUpdateTime)) + { + this.ScaleTo(1f) + .FadeIn(); + } + + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + { + // intentionally pile on an extra FadeOut to make it happen much faster + this.FadeOut(fade_out_time / 4, Easing.Out); + + switch (state) + { + case ArmedState.Hit: + this.ScaleTo(1.4f, fade_out_time, Easing.Out); + break; + } + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (parentObject != null) + parentObject.ApplyCustomUpdateState -= updateStateTransforms; + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index b8a559ce07..df33180c7b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -1,13 +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.Diagnostics; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public class LegacyFollowCircle : CompositeDrawable { + [Resolved(canBeNull: true)] + private DrawableHitObject? parentObject { get; set; } + public LegacyFollowCircle(Drawable animationContent) { // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x @@ -15,8 +23,65 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy animationContent.Anchor = Anchor.Centre; animationContent.Origin = Anchor.Centre; + Alpha = 0f; RelativeSizeAxes = Axes.Both; InternalChild = animationContent; } + + [BackgroundDependencyLoader] + private void load() + { + if (parentObject != null) + { + var slider = (DrawableSlider)parentObject; + slider.Tracking.BindValueChanged(trackingChanged, true); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (parentObject != null) + { + parentObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(parentObject, parentObject.State.Value); + } + } + + private void trackingChanged(ValueChangedEvent tracking) + { + Debug.Assert(parentObject != null); + + if (parentObject.Judged) + return; + + const float scale_duration = 180f; + const float fade_duration = 90f; + + double maxScaleDuration = parentObject.HitStateUpdateTime - Time.Current; + double realScaleDuration = scale_duration; + if (tracking.NewValue && maxScaleDuration < realScaleDuration) + realScaleDuration = maxScaleDuration; + double realFadeDuration = fade_duration * realScaleDuration / fade_duration; + + this.ScaleTo(tracking.NewValue ? DrawableSliderBall.FOLLOW_AREA : 1f, realScaleDuration, Easing.OutQuad) + .FadeTo(tracking.NewValue ? 1f : 0f, realFadeDuration, Easing.OutQuad); + } + + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) + { + if (drawableObject is not DrawableSlider) + return; + + const float shrink_duration = 200f; + const float fade_duration = 240f; + + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + { + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.8f, shrink_duration, Easing.OutQuint) + .FadeOut(fade_duration, Easing.InQuint); + } + } } } From 925d7b1fdc10018436071cdae8b0fc654440d0d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Jul 2022 15:01:01 +0900 Subject: [PATCH 374/803] 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 550088cbfa..398c1d91dd 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 d428139343..ed6577651a 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 e3e8aecbbe..7d70b96f62 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 64edc6888d84fc5a6b7e179a8a968c685ef5b19e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Jul 2022 16:45:21 +0900 Subject: [PATCH 375/803] Add test coverage of corrupt realm creating backup --- .../Collections/IO/ImportCollectionsTest.cs | 2 +- .../NonVisual/CustomDataDirectoryTest.cs | 22 +++++++++++++++- osu.Game/Tests/CleanRunHeadlessGameHost.cs | 26 ++++++++++++------- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index c31aafa67f..9a8f29647d 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -138,7 +138,7 @@ namespace osu.Game.Tests.Collections.IO { string firstRunName; - using (var host = new CleanRunHeadlessGameHost(bypassCleanup: true)) + using (var host = new CleanRunHeadlessGameHost(bypassCleanupOnDispose: true)) { firstRunName = host.Name; diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 216bd0fd3c..216db2121c 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -315,6 +315,26 @@ namespace osu.Game.Tests.NonVisual } } + [Test] + public void TestBackupCreatedOnCorruptRealm() + { + using (var host = new CustomTestHeadlessGameHost()) + { + try + { + File.WriteAllText(host.InitialStorage.GetFullPath(OsuGameBase.CLIENT_DATABASE_FILENAME, true), "i am definitely not a realm file"); + + LoadOsuIntoHost(host); + + Assert.That(host.InitialStorage.GetFiles(string.Empty, "*_corrupt.realm"), Has.One.Items); + } + finally + { + host.Exit(); + } + } + } + private static string getDefaultLocationFor(CustomTestHeadlessGameHost host) { string path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, host.Name); @@ -347,7 +367,7 @@ namespace osu.Game.Tests.NonVisual public Storage InitialStorage { get; } public CustomTestHeadlessGameHost([CallerMemberName] string callingMethodName = @"") - : base(callingMethodName: callingMethodName) + : base(callingMethodName: callingMethodName, bypassCleanupOnSetup: true) { string defaultStorageLocation = getDefaultLocationFor(this); diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs index d36168d3dd..02d67de5a5 100644 --- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs +++ b/osu.Game/Tests/CleanRunHeadlessGameHost.cs @@ -15,30 +15,38 @@ namespace osu.Game.Tests /// public class CleanRunHeadlessGameHost : TestRunHeadlessGameHost { + private readonly bool bypassCleanupOnSetup; + /// /// Create a new instance. /// /// Whether to bind IPC channels. /// Whether the host should be forced to run in realtime, rather than accelerated test time. - /// Whether to bypass directory cleanup on host disposal. Should be used only if a subsequent test relies on the files still existing. + /// Whether to bypass directory cleanup on . + /// Whether to bypass directory cleanup on host disposal. Should be used only if a subsequent test relies on the files still existing. /// The name of the calling method, used for test file isolation and clean-up. - public CleanRunHeadlessGameHost(bool bindIPC = false, bool realtime = true, bool bypassCleanup = false, [CallerMemberName] string callingMethodName = @"") + public CleanRunHeadlessGameHost(bool bindIPC = false, bool realtime = true, bool bypassCleanupOnSetup = false, bool bypassCleanupOnDispose = false, + [CallerMemberName] string callingMethodName = @"") : base($"{callingMethodName}-{Guid.NewGuid()}", new HostOptions { BindIPC = bindIPC, - }, bypassCleanup: bypassCleanup, realtime: realtime) + }, bypassCleanup: bypassCleanupOnDispose, realtime: realtime) { + this.bypassCleanupOnSetup = bypassCleanupOnSetup; } protected override void SetupForRun() { - try + if (!bypassCleanupOnSetup) { - Storage.DeleteDirectory(string.Empty); - } - catch - { - // May fail if a logging target has already been set via OsuStorage.ChangeTargetStorage. + try + { + Storage.DeleteDirectory(string.Empty); + } + catch + { + // May fail if a logging target has already been set via OsuStorage.ChangeTargetStorage. + } } // base call needs to be run *after* storage is emptied, as it updates the (static) logger's storage and may start writing From 506409a9c471246dc37fee8ad52fd2634c1d6d19 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Jul 2022 16:27:49 +0900 Subject: [PATCH 376/803] Fix realm backup creation failing when run from `RealmAccess` constructor At the point of construction, we are not on the update thread, but it doesn't really matter at this point because there's no other usages. --- osu.Game/Database/EFToRealmMigrator.cs | 5 ++- osu.Game/Database/RealmAccess.cs | 53 +++++++++++++++----------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 8f2ff600d8..3b5424b3fb 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -132,11 +132,12 @@ namespace osu.Game.Database { try { - realm.CreateBackup(Path.Combine(backup_folder, $"client.{backupSuffix}.realm"), realmBlockOperations); + realm.CreateBackup(Path.Combine(backup_folder, $"client.{backupSuffix}.realm")); } finally { - // Above call will dispose of the blocking token when done. + // Once the backup is created, we need to stop blocking operations so the migration can complete. + realmBlockOperations.Dispose(); // Clean up here so we don't accidentally dispose twice. realmBlockOperations = null; } diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index ed56049064..00218e4fe3 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -184,14 +184,14 @@ namespace osu.Game.Database // If a newer version database already exists, don't backup again. We can presume that the first backup is the one we care about. if (!storage.Exists(newerVersionFilename)) - CreateBackup(newerVersionFilename); + createBackup(newerVersionFilename); storage.Delete(Filename); } else { Logger.Error(e, "Realm startup failed with unrecoverable error; starting with a fresh database. A backup of your database has been made."); - CreateBackup($"{Filename.Replace(realm_extension, string.Empty)}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}_corrupt{realm_extension}"); + createBackup($"{Filename.Replace(realm_extension, string.Empty)}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}_corrupt{realm_extension}"); storage.Delete(Filename); } @@ -236,7 +236,7 @@ namespace osu.Game.Database } // For extra safety, also store the temporarily-used database which we are about to replace. - CreateBackup($"{Filename.Replace(realm_extension, string.Empty)}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}_newer_version_before_recovery{realm_extension}"); + createBackup($"{Filename.Replace(realm_extension, string.Empty)}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}_newer_version_before_recovery{realm_extension}"); storage.Delete(Filename); @@ -778,28 +778,37 @@ namespace osu.Game.Database private string? getRulesetShortNameFromLegacyID(long rulesetId) => efContextFactory?.Get().RulesetInfo.FirstOrDefault(r => r.ID == rulesetId)?.ShortName; - public void CreateBackup(string backupFilename, IDisposable? blockAllOperations = null) + /// + /// Create a full realm backup. + /// + /// The filename for the backup. + public void CreateBackup(string backupFilename) { - using (blockAllOperations ?? BlockAllOperations("creating backup")) + if (realmRetrievalLock.CurrentCount != 0) + throw new InvalidOperationException($"Call {nameof(BlockAllOperations)} before creating a backup."); + + createBackup(backupFilename); + } + + private void createBackup(string backupFilename) + { + Logger.Log($"Creating full realm database backup at {backupFilename}", LoggingTarget.Database); + + int attempts = 10; + + while (attempts-- > 0) { - Logger.Log($"Creating full realm database backup at {backupFilename}", LoggingTarget.Database); - - int attempts = 10; - - while (attempts-- > 0) + try { - try - { - using (var source = storage.GetStream(Filename, mode: FileMode.Open)) - using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) - source.CopyTo(destination); - return; - } - catch (IOException) - { - // file may be locked during use. - Thread.Sleep(500); - } + using (var source = storage.GetStream(Filename, mode: FileMode.Open)) + using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) + source.CopyTo(destination); + return; + } + catch (IOException) + { + // file may be locked during use. + Thread.Sleep(500); } } } From f1b55d743cdc4e99a5efce22481d87e155e36945 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Jul 2022 11:04:20 -0700 Subject: [PATCH 377/803] Fix delete local score test having no files --- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 31406af87a..cee917f6cf 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -18,6 +18,7 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; +using osu.Game.Models; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Overlays; @@ -100,6 +101,7 @@ namespace osu.Game.Tests.Visual.UserInterface Rank = ScoreRank.XH, User = new APIUser { Username = "TestUser" }, Ruleset = new OsuRuleset().RulesetInfo, + Files = { new RealmNamedFileUsage(new RealmFile { Hash = $"{i}" }, string.Empty) } }; importedScores.Add(scoreManager.Import(score).Value); From 5b96f67a8b7e49a7d12da341fc298a125b4f5786 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Mon, 4 Jul 2022 20:49:26 +0100 Subject: [PATCH 378/803] Remove non-overlapping velocity buff --- .../Difficulty/Evaluators/AimEvaluator.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs index 0694746cbf..76d5ccf682 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs @@ -108,13 +108,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // Reward for % distance up to 125 / strainTime for overlaps where velocity is still changing. double overlapVelocityBuff = Math.Min(125 / Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Abs(prevVelocity - currVelocity)); - // Reward for % distance slowed down compared to previous, paying attention to not award overlap - double nonOverlapVelocityBuff = Math.Abs(prevVelocity - currVelocity) - // do not award overlap - * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, Math.Min(osuCurrObj.LazyJumpDistance, osuLastObj.LazyJumpDistance) / 100)), 2); - - // Choose the largest bonus, multiplied by ratio. - velocityChangeBonus = Math.Max(overlapVelocityBuff, nonOverlapVelocityBuff) * distRatio; + velocityChangeBonus = overlapVelocityBuff * distRatio; // Penalize for rhythm changes. velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2); From bb0f2124480ef35736b3aef4a5562e7058260991 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Tue, 5 Jul 2022 00:38:18 +0200 Subject: [PATCH 379/803] Trigger the initial cookie with midi and joysticks --- osu.Game/Screens/Menu/ButtonSystem.cs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 74be02728f..fadc80277a 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -205,6 +205,28 @@ namespace osu.Game.Screens.Menu return base.OnKeyDown(e); } + protected override bool OnJoystickPress(JoystickPressEvent e) + { + if (State == ButtonSystemState.Initial) + { + logo?.TriggerClick(); + return true; + } + + return base.OnJoystickPress(e); + } + + protected override bool OnMidiDown(MidiDownEvent e) + { + if (State == ButtonSystemState.Initial) + { + logo?.TriggerClick(); + return true; + } + + return base.OnMidiDown(e); + } + public bool OnPressed(KeyBindingPressEvent e) { if (e.Repeat) From 1b98936328df8261bb5c2b1586d794c41f0edc20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Jul 2022 14:59:44 +0900 Subject: [PATCH 380/803] Rename realm `ThreadLocal` to better convey what it's doing Every time I looked at this code I have to re-learn what it's doing. Changing these variable names should help quite a bit. --- osu.Game/Database/RealmAccess.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 00218e4fe3..7ba599bfed 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -66,7 +66,10 @@ namespace osu.Game.Database /// private readonly SemaphoreSlim realmRetrievalLock = new SemaphoreSlim(1); - private readonly ThreadLocal currentThreadCanCreateRealmInstances = new ThreadLocal(); + /// + /// true when the current thread has already entered the . + /// + private readonly ThreadLocal currentThreadHasRealmRetrievalLock = new ThreadLocal(); /// /// Holds a map of functions registered via and and a coinciding action which when triggered, @@ -584,10 +587,11 @@ namespace osu.Game.Database try { - if (!currentThreadCanCreateRealmInstances.Value) + // Ensure that the thread that currently has the `realmRetrievalLock` can retrieve nested contexts and not deadlock on itself. + if (!currentThreadHasRealmRetrievalLock.Value) { realmRetrievalLock.Wait(); - currentThreadCanCreateRealmInstances.Value = true; + currentThreadHasRealmRetrievalLock.Value = true; tookSemaphoreLock = true; } else @@ -611,7 +615,7 @@ namespace osu.Game.Database if (tookSemaphoreLock) { realmRetrievalLock.Release(); - currentThreadCanCreateRealmInstances.Value = false; + currentThreadHasRealmRetrievalLock.Value = false; } } } From 5adec2c7382245ddd0debadaef706876acaf01a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Jul 2022 00:27:38 +0900 Subject: [PATCH 381/803] Ensure blocking restoration only completes after update callback work is completed --- osu.Game/Database/RealmAccess.cs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 7ba599bfed..7dd03d5ce7 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -923,13 +923,34 @@ namespace osu.Game.Database Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); realmRetrievalLock.Release(); + if (syncContext == null) return; + + ManualResetEventSlim updateRealmReestablished = new ManualResetEventSlim(); + // Post back to the update thread to revive any subscriptions. // In the case we are on the update thread, let's also require this to run synchronously. // This requirement is mostly due to test coverage, but shouldn't cause any harm. if (ThreadSafety.IsUpdateThread) - syncContext?.Send(_ => ensureUpdateRealm(), null); + { + syncContext.Send(_ => + { + ensureUpdateRealm(); + updateRealmReestablished.Set(); + }, null); + } else - syncContext?.Post(_ => ensureUpdateRealm(), null); + { + syncContext.Post(_ => + { + ensureUpdateRealm(); + updateRealmReestablished.Set(); + }, null); + } + + // Wait for the post to complete to ensure a second `Migrate` operation doesn't start in the mean time. + // This is important to ensure `ensureUpdateRealm` is run before another blocking migration operation starts. + if (!updateRealmReestablished.Wait(10000)) + throw new TimeoutException(@"Reestablishing update realm after block took too long"); } } From 1e6def8209ddadbebbf9541bc5fb3aeb6683bd29 Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Mon, 4 Jul 2022 22:58:41 -0700 Subject: [PATCH 382/803] Fix spinner accent animation on rewind --- .../Skinning/Default/DefaultSpinnerDisc.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs index ab14f939d4..03db76336c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs @@ -137,6 +137,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default this.ScaleTo(initial_scale); this.RotateTo(0); + updateComplete(false, 0); + using (BeginDelayedSequence(spinner.TimePreempt / 2)) { // constant ambient rotation to give the spinner "spinning" character. @@ -177,9 +179,11 @@ 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)) - updateComplete(state == ArmedState.Hit, 0); + if (drawableSpinner.Result?.TimeCompleted is double completionTime) + { + using (BeginAbsoluteSequence(completionTime)) + updateComplete(true, 200); + } } private void updateComplete(bool complete, double duration) From 33db5083013bd2433fd16d646bd5dfa8477fa212 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Jul 2022 16:23:10 +0900 Subject: [PATCH 383/803] Add note regarding why the `realmRetrievalLock` return is done on the async thread --- osu.Game/Database/RealmAccess.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 7dd03d5ce7..8cf57b802b 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -920,6 +920,8 @@ namespace osu.Game.Database void restoreOperation() { + // Release of lock needs to happen here rather than on the update thread, as there may be another + // operation already blocking the update thread waiting for the blocking operation to complete. Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); realmRetrievalLock.Release(); From 4c3789ec5d67422459154fc452d2a320706ee793 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Tue, 5 Jul 2022 11:15:37 +0200 Subject: [PATCH 384/803] Split into a function --- osu.Game/Screens/Menu/ButtonSystem.cs | 30 ++++++++++++++++----------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index fadc80277a..04bffda81b 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -191,38 +191,44 @@ namespace osu.Game.Screens.Menu State = ButtonSystemState.Initial; } - protected override bool OnKeyDown(KeyDownEvent e) + /// + /// Triggers the if the current is . + /// + /// true if the was triggered, false otherwise. + private bool triggerInitialOsuLogo() { - if (e.Repeat || e.ControlPressed || e.ShiftPressed || e.AltPressed || e.SuperPressed) - return false; - if (State == ButtonSystemState.Initial) { logo?.TriggerClick(); return true; } + return false; + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.Repeat || e.ControlPressed || e.ShiftPressed || e.AltPressed || e.SuperPressed) + return false; + + if (triggerInitialOsuLogo()) + return true; + return base.OnKeyDown(e); } protected override bool OnJoystickPress(JoystickPressEvent e) { - if (State == ButtonSystemState.Initial) - { - logo?.TriggerClick(); + if (triggerInitialOsuLogo()) return true; - } return base.OnJoystickPress(e); } protected override bool OnMidiDown(MidiDownEvent e) { - if (State == ButtonSystemState.Initial) - { - logo?.TriggerClick(); + if (triggerInitialOsuLogo()) return true; - } return base.OnMidiDown(e); } From d217d668526745e63e8f0c3c08139e2f997a2c9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Jul 2022 17:18:33 +0900 Subject: [PATCH 385/803] Add `OnlineMetadataClient` --- osu.Game/Online/EndpointConfiguration.cs | 5 ++ osu.Game/Online/Metadata/BeatmapUpdates.cs | 28 +++++++++ osu.Game/Online/Metadata/IMetadataClient.cs | 12 ++++ osu.Game/Online/Metadata/IMetadataServer.cs | 21 +++++++ osu.Game/Online/Metadata/MetadataClient.cs | 15 +++++ .../Online/Metadata/OnlineMetadataClient.cs | 61 +++++++++++++++++++ osu.Game/OsuGameBase.cs | 8 ++- 7 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/Metadata/BeatmapUpdates.cs create mode 100644 osu.Game/Online/Metadata/IMetadataClient.cs create mode 100644 osu.Game/Online/Metadata/IMetadataServer.cs create mode 100644 osu.Game/Online/Metadata/MetadataClient.cs create mode 100644 osu.Game/Online/Metadata/OnlineMetadataClient.cs diff --git a/osu.Game/Online/EndpointConfiguration.cs b/osu.Game/Online/EndpointConfiguration.cs index af4e88a05c..f3bcced630 100644 --- a/osu.Game/Online/EndpointConfiguration.cs +++ b/osu.Game/Online/EndpointConfiguration.cs @@ -39,5 +39,10 @@ namespace osu.Game.Online /// The endpoint for the SignalR multiplayer server. /// public string MultiplayerEndpointUrl { get; set; } + + /// + /// The endpoint for the SignalR metadata server. + /// + public string MetadataEndpointUrl { get; set; } } } diff --git a/osu.Game/Online/Metadata/BeatmapUpdates.cs b/osu.Game/Online/Metadata/BeatmapUpdates.cs new file mode 100644 index 0000000000..8814e35e1f --- /dev/null +++ b/osu.Game/Online/Metadata/BeatmapUpdates.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using MessagePack; + +namespace osu.Game.Online.Metadata +{ + /// + /// Describes a set of beatmaps which have been updated in some way. + /// + [MessagePackObject] + [Serializable] + public class BeatmapUpdates + { + [Key(0)] + public int[] BeatmapSetIDs { get; set; } + + [Key(1)] + public uint LastProcessedQueueID { get; set; } + + public BeatmapUpdates(int[] beatmapSetIDs, uint lastProcessedQueueID) + { + BeatmapSetIDs = beatmapSetIDs; + LastProcessedQueueID = lastProcessedQueueID; + } + } +} diff --git a/osu.Game/Online/Metadata/IMetadataClient.cs b/osu.Game/Online/Metadata/IMetadataClient.cs new file mode 100644 index 0000000000..ad1e7ebbaf --- /dev/null +++ b/osu.Game/Online/Metadata/IMetadataClient.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading.Tasks; + +namespace osu.Game.Online.Metadata +{ + public interface IMetadataClient + { + Task BeatmapSetsUpdated(BeatmapUpdates updates); + } +} diff --git a/osu.Game/Online/Metadata/IMetadataServer.cs b/osu.Game/Online/Metadata/IMetadataServer.cs new file mode 100644 index 0000000000..edf288b6cf --- /dev/null +++ b/osu.Game/Online/Metadata/IMetadataServer.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.Threading.Tasks; + +namespace osu.Game.Online.Metadata +{ + /// + /// Metadata server is responsible for keeping the osu! client up-to-date with any changes. + /// + public interface IMetadataServer + { + /// + /// Get any changes since a specific point in the queue. + /// Should be used to allow the client to catch up with any changes after being closed or disconnected. + /// + /// The last processed queue ID. + /// + Task GetChangesSince(uint queueId); + } +} diff --git a/osu.Game/Online/Metadata/MetadataClient.cs b/osu.Game/Online/Metadata/MetadataClient.cs new file mode 100644 index 0000000000..65817b3152 --- /dev/null +++ b/osu.Game/Online/Metadata/MetadataClient.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 System.Threading.Tasks; +using osu.Framework.Graphics; + +namespace osu.Game.Online.Metadata +{ + public abstract class MetadataClient : Component, IMetadataClient, IMetadataServer + { + public abstract Task BeatmapSetsUpdated(BeatmapUpdates updates); + + public abstract Task GetChangesSince(uint queueId); + } +} diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs new file mode 100644 index 0000000000..cfcebf73d3 --- /dev/null +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.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 System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR.Client; +using osu.Framework.Allocation; +using osu.Framework.Logging; +using osu.Game.Online.API; + +namespace osu.Game.Online.Metadata +{ + public class OnlineMetadataClient : MetadataClient + { + private readonly string endpoint; + + private IHubClientConnector? connector; + + private HubConnection? connection => connector?.CurrentConnection; + + public OnlineMetadataClient(EndpointConfiguration endpoints) + { + endpoint = endpoints.MetadataEndpointUrl; + } + + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { + // Importantly, we are intentionally not using MessagePack here to correctly support derived class serialization. + // More information on the limitations / reasoning can be found in osu-server-spectator's initialisation code. + connector = api.GetHubConnector(nameof(OnlineMetadataClient), endpoint); + + if (connector != null) + { + connector.ConfigureConnection = connection => + { + // this is kind of SILLY + // https://github.com/dotnet/aspnetcore/issues/15198 + connection.On(nameof(IMetadataClient.BeatmapSetsUpdated), ((IMetadataClient)this).BeatmapSetsUpdated); + }; + } + } + + public override Task BeatmapSetsUpdated(BeatmapUpdates updates) + { + Logger.Log($"Received beatmap updates {updates.BeatmapSetIDs.Length} updates with last id {updates.LastProcessedQueueID}"); + return Task.CompletedTask; + } + + public override Task GetChangesSince(uint queueId) + { + throw new NotImplementedException(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + connector?.Dispose(); + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index ead3eeb0dc..bf3cc16728 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Reflection; using System.Threading; @@ -41,6 +40,7 @@ using osu.Game.IO; using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.Chat; +using osu.Game.Online.Metadata; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Overlays; @@ -52,6 +52,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Skinning; using osu.Game.Utils; +using File = System.IO.File; using RuntimeInfo = osu.Framework.RuntimeInfo; namespace osu.Game @@ -180,6 +181,8 @@ namespace osu.Game private MultiplayerClient multiplayerClient; + private MetadataClient metadataClient; + private RealmAccess realm; protected override Container Content => content; @@ -265,6 +268,7 @@ namespace osu.Game dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints)); + dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); @@ -316,8 +320,10 @@ namespace osu.Game // add api components to hierarchy. if (API is APIAccess apiAccess) AddInternal(apiAccess); + AddInternal(spectatorClient); AddInternal(multiplayerClient); + AddInternal(metadataClient); AddInternal(rulesetConfigCache); From b0d4f7aff6f26724e4a59464db4998e672b78a33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Jul 2022 17:49:08 +0900 Subject: [PATCH 386/803] Add recovery logic after disconnection --- .../Online/Metadata/OnlineMetadataClient.cs | 69 ++++++++++++++++++- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index cfcebf73d3..0d31060a8b 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -1,10 +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.Diagnostics; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Game.Online.API; @@ -16,6 +17,8 @@ namespace osu.Game.Online.Metadata private IHubClientConnector? connector; + private uint? lastQueueId; + private HubConnection? connection => connector?.CurrentConnection; public OnlineMetadataClient(EndpointConfiguration endpoints) @@ -38,18 +41,78 @@ namespace osu.Game.Online.Metadata // https://github.com/dotnet/aspnetcore/issues/15198 connection.On(nameof(IMetadataClient.BeatmapSetsUpdated), ((IMetadataClient)this).BeatmapSetsUpdated); }; + + connector.IsConnected.BindValueChanged(isConnectedChanged, true); } } - public override Task BeatmapSetsUpdated(BeatmapUpdates updates) + private bool catchingUp; + + private void isConnectedChanged(ValueChangedEvent connected) + { + if (!connected.NewValue) + return; + + if (lastQueueId != null) + { + catchingUp = true; + + Task.Run(async () => + { + try + { + while (true) + { + Logger.Log($"Requesting catch-up from {lastQueueId.Value}"); + var catchUpChanges = await GetChangesSince(lastQueueId.Value); + + lastQueueId = catchUpChanges.LastProcessedQueueID; + + if (catchUpChanges.BeatmapSetIDs.Length == 0) + { + Logger.Log($"Catch-up complete at {lastQueueId.Value}"); + break; + } + + await ProcessChanges(catchUpChanges.BeatmapSetIDs); + } + } + finally + { + catchingUp = false; + } + }); + } + } + + public override async Task BeatmapSetsUpdated(BeatmapUpdates updates) { Logger.Log($"Received beatmap updates {updates.BeatmapSetIDs.Length} updates with last id {updates.LastProcessedQueueID}"); + + // If we're still catching up, avoid updating the last ID as it will interfere with catch-up efforts. + if (!catchingUp) + lastQueueId = updates.LastProcessedQueueID; + + await ProcessChanges(updates.BeatmapSetIDs); + } + + protected Task ProcessChanges(int[] beatmapSetIDs) + { + foreach (int id in beatmapSetIDs) + Logger.Log($"Processing {id}..."); return Task.CompletedTask; } public override Task GetChangesSince(uint queueId) { - throw new NotImplementedException(); + if (connector?.IsConnected.Value != true) + return Task.FromCanceled(default); + + Logger.Log($"Requesting any changes since last known queue id {queueId}"); + + Debug.Assert(connection != null); + + return connection.InvokeAsync(nameof(IMetadataServer.GetChangesSince), queueId); } protected override void Dispose(bool isDisposing) From 59d0bac728546fbc5f018b8b94d89aae87f0d333 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Jul 2022 18:13:53 +0900 Subject: [PATCH 387/803] Hook up update flow to metadata stream --- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 8 +++---- osu.Game/Beatmaps/BeatmapImporter.cs | 7 +++--- osu.Game/Beatmaps/BeatmapManager.cs | 24 ++++++------------- osu.Game/Beatmaps/BeatmapUpdater.cs | 16 ++++++++++++- .../Online/Metadata/OnlineMetadataClient.cs | 9 ++++++- osu.Game/OsuGameBase.cs | 18 ++++++++++---- 6 files changed, 50 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 0bf47141e4..fcf69bf6f2 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -212,17 +212,17 @@ namespace osu.Game.Tests.Online { } - protected override BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapUpdater beatmapUpdater) + protected override BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm) { - return new TestBeatmapImporter(this, storage, realm, beatmapUpdater); + return new TestBeatmapImporter(this, storage, realm); } internal class TestBeatmapImporter : BeatmapImporter { private readonly TestBeatmapManager testBeatmapManager; - public TestBeatmapImporter(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapUpdater beatmapUpdater) - : base(storage, databaseAccess, beatmapUpdater) + public TestBeatmapImporter(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess) + : base(storage, databaseAccess) { this.testBeatmapManager = testBeatmapManager; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 5382b98c22..92f1fc17d5 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -31,12 +31,11 @@ namespace osu.Game.Beatmaps protected override string[] HashableFileTypes => new[] { ".osu" }; - private readonly BeatmapUpdater? beatmapUpdater; + public Action? ProcessBeatmap { private get; set; } - public BeatmapImporter(Storage storage, RealmAccess realm, BeatmapUpdater? beatmapUpdater = null) + public BeatmapImporter(Storage storage, RealmAccess realm) : base(storage, realm) { - this.beatmapUpdater = beatmapUpdater; } protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == ".osz"; @@ -100,7 +99,7 @@ namespace osu.Game.Beatmaps { base.PostImport(model, realm); - beatmapUpdater?.Process(model); + ProcessBeatmap?.Invoke(model); } private void validateOnlineIds(BeatmapSetInfo beatmapSet, Realm realm) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e16a87eb50..b1acf78ec6 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -34,14 +34,15 @@ namespace osu.Game.Beatmaps /// Handles general operations related to global beatmap management. /// [ExcludeFromDynamicCompile] - public class BeatmapManager : ModelManager, IModelImporter, IWorkingBeatmapCache, IDisposable + public class BeatmapManager : ModelManager, IModelImporter, IWorkingBeatmapCache { public ITrackStore BeatmapTrackStore { get; } private readonly BeatmapImporter beatmapImporter; private readonly WorkingBeatmapCache workingBeatmapCache; - private readonly BeatmapUpdater? beatmapUpdater; + + public Action? ProcessBeatmap { private get; set; } public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, BeatmapDifficultyCache? difficultyCache = null, bool performOnlineLookups = false) @@ -54,15 +55,14 @@ namespace osu.Game.Beatmaps if (difficultyCache == null) throw new ArgumentNullException(nameof(difficultyCache), "Difficulty cache must be provided if online lookups are required."); - - beatmapUpdater = new BeatmapUpdater(this, difficultyCache, api, storage); } var userResources = new RealmFileStore(realm, storage).Store; BeatmapTrackStore = audioManager.GetTrackStore(userResources); - beatmapImporter = CreateBeatmapImporter(storage, realm, rulesets, beatmapUpdater); + beatmapImporter = CreateBeatmapImporter(storage, realm); + beatmapImporter.ProcessBeatmap = obj => ProcessBeatmap?.Invoke(obj); beatmapImporter.PostNotification = obj => PostNotification?.Invoke(obj); workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); @@ -74,8 +74,7 @@ namespace osu.Game.Beatmaps return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host); } - protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapUpdater? beatmapUpdater) => - new BeatmapImporter(storage, realm, beatmapUpdater); + protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm) => new BeatmapImporter(storage, realm); /// /// Create a new beatmap set, backed by a model, @@ -323,7 +322,7 @@ namespace osu.Game.Beatmaps setInfo.CopyChangesToRealm(liveBeatmapSet); - beatmapUpdater?.Process(liveBeatmapSet, r); + ProcessBeatmap?.Invoke(liveBeatmapSet); }); } @@ -468,15 +467,6 @@ namespace osu.Game.Beatmaps #endregion - #region Implementation of IDisposable - - public void Dispose() - { - beatmapUpdater?.Dispose(); - } - - #endregion - #region Implementation of IPostImports public Action>>? PresentImport diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index d800b09a2b..d0ec44a034 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -31,6 +31,14 @@ namespace osu.Game.Beatmaps onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); } + /// + /// Queue a beatmap for background processing. + /// + public void Queue(int beatmapSetId) + { + // TODO: implement + } + /// /// Queue a beatmap for background processing. /// @@ -44,7 +52,13 @@ namespace osu.Game.Beatmaps /// /// Run all processing on a beatmap immediately. /// - public void Process(BeatmapSetInfo beatmapSet) => beatmapSet.Realm.Write(r => Process(beatmapSet, r)); + public void Process(BeatmapSetInfo beatmapSet) + { + if (beatmapSet.Realm.IsInTransaction) + Process(beatmapSet, beatmapSet.Realm); + else + beatmapSet.Realm.Write(r => Process(beatmapSet, r)); + } public void Process(BeatmapSetInfo beatmapSet, Realm realm) { diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index 0d31060a8b..b05efd5311 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -7,12 +7,14 @@ using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; +using osu.Game.Beatmaps; using osu.Game.Online.API; namespace osu.Game.Online.Metadata { public class OnlineMetadataClient : MetadataClient { + private readonly BeatmapUpdater beatmapUpdater; private readonly string endpoint; private IHubClientConnector? connector; @@ -21,8 +23,9 @@ namespace osu.Game.Online.Metadata private HubConnection? connection => connector?.CurrentConnection; - public OnlineMetadataClient(EndpointConfiguration endpoints) + public OnlineMetadataClient(EndpointConfiguration endpoints, BeatmapUpdater beatmapUpdater) { + this.beatmapUpdater = beatmapUpdater; endpoint = endpoints.MetadataEndpointUrl; } @@ -99,7 +102,11 @@ namespace osu.Game.Online.Metadata protected Task ProcessChanges(int[] beatmapSetIDs) { foreach (int id in beatmapSetIDs) + { Logger.Log($"Processing {id}..."); + beatmapUpdater.Queue(id); + } + return Task.CompletedTask; } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index bf3cc16728..356013a410 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -171,6 +171,7 @@ namespace osu.Game public readonly Bindable>> AvailableMods = new Bindable>>(new Dictionary>()); private BeatmapDifficultyCache difficultyCache; + private BeatmapUpdater beatmapUpdater; private UserLookupCache userCache; private BeatmapLookupCache beatmapCache; @@ -266,16 +267,13 @@ namespace osu.Game dependencies.CacheAs(API ??= new APIAccess(LocalConfig, endpoints, VersionHash)); - dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints)); - dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints)); - dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); - var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, difficultyCache, LocalConfig)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true)); dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API)); @@ -284,6 +282,15 @@ namespace osu.Game // Add after all the above cache operations as it depends on them. AddInternal(difficultyCache); + // TODO: OsuGame or OsuGameBase? + beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage); + + dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints)); + dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints)); + dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints, beatmapUpdater)); + + BeatmapManager.ProcessBeatmap = set => beatmapUpdater.Process(set); + dependencies.Cache(userCache = new UserLookupCache()); AddInternal(userCache); @@ -580,9 +587,10 @@ namespace osu.Game base.Dispose(isDisposing); RulesetStore?.Dispose(); - BeatmapManager?.Dispose(); LocalConfig?.Dispose(); + beatmapUpdater?.Dispose(); + realm?.Dispose(); if (Host != null) From bdd1bf4da00d8d8de36370c2054c6d93b7ba562b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Jul 2022 21:42:35 +0900 Subject: [PATCH 388/803] Save last processed id to config for now --- osu.Game/Configuration/OsuConfigManager.cs | 3 +++ osu.Game/Online/Metadata/BeatmapUpdates.cs | 4 ++-- osu.Game/Online/Metadata/IMetadataServer.cs | 2 +- osu.Game/Online/Metadata/MetadataClient.cs | 2 +- osu.Game/Online/Metadata/OnlineMetadataClient.cs | 15 +++++++++------ 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 713166a9a0..a523507205 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -167,6 +167,8 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full); SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f); + + SetDefault(OsuSetting.LastProcessedMetadataId, -1); } public IDictionary GetLoggableState() => @@ -363,5 +365,6 @@ namespace osu.Game.Configuration DiscordRichPresence, AutomaticallyDownloadWhenSpectating, ShowOnlineExplicitContent, + LastProcessedMetadataId } } diff --git a/osu.Game/Online/Metadata/BeatmapUpdates.cs b/osu.Game/Online/Metadata/BeatmapUpdates.cs index 8814e35e1f..a0cf616c70 100644 --- a/osu.Game/Online/Metadata/BeatmapUpdates.cs +++ b/osu.Game/Online/Metadata/BeatmapUpdates.cs @@ -17,9 +17,9 @@ namespace osu.Game.Online.Metadata public int[] BeatmapSetIDs { get; set; } [Key(1)] - public uint LastProcessedQueueID { get; set; } + public int LastProcessedQueueID { get; set; } - public BeatmapUpdates(int[] beatmapSetIDs, uint lastProcessedQueueID) + public BeatmapUpdates(int[] beatmapSetIDs, int lastProcessedQueueID) { BeatmapSetIDs = beatmapSetIDs; LastProcessedQueueID = lastProcessedQueueID; diff --git a/osu.Game/Online/Metadata/IMetadataServer.cs b/osu.Game/Online/Metadata/IMetadataServer.cs index edf288b6cf..994f60f877 100644 --- a/osu.Game/Online/Metadata/IMetadataServer.cs +++ b/osu.Game/Online/Metadata/IMetadataServer.cs @@ -16,6 +16,6 @@ namespace osu.Game.Online.Metadata /// /// The last processed queue ID. /// - Task GetChangesSince(uint queueId); + Task GetChangesSince(int queueId); } } diff --git a/osu.Game/Online/Metadata/MetadataClient.cs b/osu.Game/Online/Metadata/MetadataClient.cs index 65817b3152..1e5eeb4eb0 100644 --- a/osu.Game/Online/Metadata/MetadataClient.cs +++ b/osu.Game/Online/Metadata/MetadataClient.cs @@ -10,6 +10,6 @@ namespace osu.Game.Online.Metadata { public abstract Task BeatmapSetsUpdated(BeatmapUpdates updates); - public abstract Task GetChangesSince(uint queueId); + public abstract Task GetChangesSince(int queueId); } } diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index b05efd5311..1b0d1884dc 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Online.API; namespace osu.Game.Online.Metadata @@ -19,7 +20,7 @@ namespace osu.Game.Online.Metadata private IHubClientConnector? connector; - private uint? lastQueueId; + private Bindable lastQueueId = null!; private HubConnection? connection => connector?.CurrentConnection; @@ -30,7 +31,7 @@ namespace osu.Game.Online.Metadata } [BackgroundDependencyLoader] - private void load(IAPIProvider api) + private void load(IAPIProvider api, OsuConfigManager config) { // Importantly, we are intentionally not using MessagePack here to correctly support derived class serialization. // More information on the limitations / reasoning can be found in osu-server-spectator's initialisation code. @@ -47,6 +48,8 @@ namespace osu.Game.Online.Metadata connector.IsConnected.BindValueChanged(isConnectedChanged, true); } + + lastQueueId = config.GetBindable(OsuSetting.LastProcessedMetadataId); } private bool catchingUp; @@ -56,7 +59,7 @@ namespace osu.Game.Online.Metadata if (!connected.NewValue) return; - if (lastQueueId != null) + if (lastQueueId.Value >= 0) { catchingUp = true; @@ -69,7 +72,7 @@ namespace osu.Game.Online.Metadata Logger.Log($"Requesting catch-up from {lastQueueId.Value}"); var catchUpChanges = await GetChangesSince(lastQueueId.Value); - lastQueueId = catchUpChanges.LastProcessedQueueID; + lastQueueId.Value = catchUpChanges.LastProcessedQueueID; if (catchUpChanges.BeatmapSetIDs.Length == 0) { @@ -94,7 +97,7 @@ namespace osu.Game.Online.Metadata // If we're still catching up, avoid updating the last ID as it will interfere with catch-up efforts. if (!catchingUp) - lastQueueId = updates.LastProcessedQueueID; + lastQueueId.Value = updates.LastProcessedQueueID; await ProcessChanges(updates.BeatmapSetIDs); } @@ -110,7 +113,7 @@ namespace osu.Game.Online.Metadata return Task.CompletedTask; } - public override Task GetChangesSince(uint queueId) + public override Task GetChangesSince(int queueId) { if (connector?.IsConnected.Value != true) return Task.FromCanceled(default); From 99afbc7b73d76c68a41d96fd4fa1b5efe69d9ec5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Jul 2022 22:15:52 +0900 Subject: [PATCH 389/803] Add missing endpoint URLs --- osu.Game/Online/DevelopmentEndpointConfiguration.cs | 1 + osu.Game/Online/ProductionEndpointConfiguration.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game/Online/DevelopmentEndpointConfiguration.cs b/osu.Game/Online/DevelopmentEndpointConfiguration.cs index 83fd02512b..3171d15fc2 100644 --- a/osu.Game/Online/DevelopmentEndpointConfiguration.cs +++ b/osu.Game/Online/DevelopmentEndpointConfiguration.cs @@ -14,6 +14,7 @@ namespace osu.Game.Online APIClientID = "5"; SpectatorEndpointUrl = $"{APIEndpointUrl}/spectator"; MultiplayerEndpointUrl = $"{APIEndpointUrl}/multiplayer"; + MetadataEndpointUrl = $"{APIEndpointUrl}/metadata"; } } } diff --git a/osu.Game/Online/ProductionEndpointConfiguration.cs b/osu.Game/Online/ProductionEndpointConfiguration.cs index f431beac1c..316452280d 100644 --- a/osu.Game/Online/ProductionEndpointConfiguration.cs +++ b/osu.Game/Online/ProductionEndpointConfiguration.cs @@ -14,6 +14,7 @@ namespace osu.Game.Online APIClientID = "5"; SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator"; MultiplayerEndpointUrl = "https://spectator.ppy.sh/multiplayer"; + MetadataEndpointUrl = "https://spectator.ppy.sh/metadata"; } } } From 3a68f386a8e7230ece6c402b0d98c9b8a3de24cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 01:25:04 +0900 Subject: [PATCH 390/803] Add forgotten password link to login form --- osu.Game/Overlays/Login/LoginForm.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs index f545e2892f..b3e120f443 100644 --- a/osu.Game/Overlays/Login/LoginForm.cs +++ b/osu.Game/Overlays/Login/LoginForm.cs @@ -49,6 +49,7 @@ namespace osu.Game.Overlays.Login RelativeSizeAxes = Axes.X; ErrorTextFlowContainer errorText; + LinkFlowContainer forgottenPaswordLink; Children = new Drawable[] { @@ -80,6 +81,12 @@ namespace osu.Game.Overlays.Login LabelText = "Stay signed in", Current = config.GetBindable(OsuSetting.SavePassword), }, + forgottenPaswordLink = new LinkFlowContainer + { + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, new Container { RelativeSizeAxes = Axes.X, @@ -109,6 +116,8 @@ namespace osu.Game.Overlays.Login } }; + forgottenPaswordLink.AddLink(LayoutStrings.PopupLoginLoginForgot, "https://osu.ppy.sh/home/password-reset"); + password.OnCommit += (_, _) => performLogin(); if (api?.LastLoginError?.Message is string error) From 6fb00d84f997cfa318ae81636c064b8498d822b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 01:29:00 +0900 Subject: [PATCH 391/803] Apply NRT to `LoginForm` and remove nullability of `IAPIProvider` --- osu.Game/Overlays/Login/LoginForm.cs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs index b3e120f443..0042f4607d 100644 --- a/osu.Game/Overlays/Login/LoginForm.cs +++ b/osu.Game/Overlays/Login/LoginForm.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Extensions.LocalisationExtensions; @@ -23,19 +21,19 @@ namespace osu.Game.Overlays.Login { public class LoginForm : FillFlowContainer { - private TextBox username; - private TextBox password; - private ShakeContainer shakeSignIn; + private TextBox username = null!; + private TextBox password = null!; + private ShakeContainer shakeSignIn = null!; - [Resolved(CanBeNull = true)] - private IAPIProvider api { get; set; } + [Resolved] + private IAPIProvider api { get; set; } = null!; - public Action RequestHide; + public Action? RequestHide; private void performLogin() { if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text)) - api?.Login(username.Text, password.Text); + api.Login(username.Text, password.Text); else shakeSignIn.Shake(); } @@ -57,7 +55,7 @@ namespace osu.Game.Overlays.Login { PlaceholderText = UsersStrings.LoginUsername.ToLower(), RelativeSizeAxes = Axes.X, - Text = api?.ProvidedUsername ?? string.Empty, + Text = api.ProvidedUsername, TabbableContentContainer = this }, password = new OsuPasswordTextBox @@ -110,17 +108,17 @@ namespace osu.Game.Overlays.Login Text = "Register", Action = () => { - RequestHide(); + RequestHide?.Invoke(); accountCreation.Show(); } } }; - forgottenPaswordLink.AddLink(LayoutStrings.PopupLoginLoginForgot, "https://osu.ppy.sh/home/password-reset"); + forgottenPaswordLink.AddLink(LayoutStrings.PopupLoginLoginForgot, $"{api.WebsiteRootUrl}/home/password-reset"); password.OnCommit += (_, _) => performLogin(); - if (api?.LastLoginError?.Message is string error) + if (api.LastLoginError?.Message is string error) errorText.AddErrors(new[] { error }); } From 72fb1ae892e71424a45b383b684f791802444fdf Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Tue, 5 Jul 2022 21:04:13 -0700 Subject: [PATCH 392/803] Add forgotten unsubscribes --- .../Skinning/Default/DefaultFollowCircle.cs | 17 ++++++++-- .../Skinning/Default/DefaultSliderBall.cs | 11 +++--- .../Skinning/Legacy/LegacyFollowCircle.cs | 34 ++++++++++++++++--- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index c39a07da4e..7dd45c295d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Logging; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK.Graphics; @@ -70,14 +72,23 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { + // see comment in LegacySliderBall.updateStateTransforms if (drawableObject is not DrawableSlider) return; - const float fade_time = 450f; + const float fade_duration = 450f; + // intentionally pile on an extra FadeOut to make it happen much faster using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) - // intentionally pile on an extra FadeOut to make it happen much faster. - this.FadeOut(fade_time / 4, Easing.Out); + this.FadeOut(fade_duration / 4, Easing.Out); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (parentObject != null) + parentObject.ApplyCustomUpdateState -= updateStateTransforms; } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs index 3c7dd7ba5d..37e5e150bd 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs @@ -73,26 +73,27 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { + // see comment in LegacySliderBall.updateStateTransforms if (drawableObject is not DrawableSlider) return; - const float fade_out_time = 450f; + const float fade_duration = 450f; using (BeginAbsoluteSequence(drawableObject.StateUpdateTime)) { - this.ScaleTo(1f) - .FadeIn(); + this.FadeIn() + .ScaleTo(1f); } using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) { // intentionally pile on an extra FadeOut to make it happen much faster - this.FadeOut(fade_out_time / 4, Easing.Out); + this.FadeOut(fade_duration / 4, Easing.Out); switch (state) { case ArmedState.Hit: - this.ScaleTo(1.4f, fade_out_time, Easing.Out); + this.ScaleTo(1.4f, fade_duration, Easing.Out); break; } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index df33180c7b..dc44c86de0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -1,13 +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 System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Transforms; +using osu.Framework.Logging; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -23,7 +27,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy animationContent.Anchor = Anchor.Centre; animationContent.Origin = Anchor.Centre; - Alpha = 0f; RelativeSizeAxes = Axes.Both; InternalChild = animationContent; } @@ -44,6 +47,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (parentObject != null) { + parentObject.HitObjectApplied += onHitObjectApplied; + onHitObjectApplied(parentObject); + parentObject.ApplyCustomUpdateState += updateStateTransforms; updateStateTransforms(parentObject, parentObject.State.Value); } @@ -69,18 +75,38 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy .FadeTo(tracking.NewValue ? 1f : 0f, realFadeDuration, Easing.OutQuad); } + private void onHitObjectApplied(DrawableHitObject drawableObject) + { + this.ScaleTo(1f) + .FadeOut(); + } + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { + // see comment in LegacySliderBall.updateStateTransforms if (drawableObject is not DrawableSlider) return; const float shrink_duration = 200f; - const float fade_duration = 240f; + const float fade_delay = 175f; + const float fade_duration = 35f; using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) { - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.8f, shrink_duration, Easing.OutQuint) - .FadeOut(fade_duration, Easing.InQuint); + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.75f, shrink_duration, Easing.OutQuad) + .Delay(fade_delay) + .FadeOut(fade_duration); + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (parentObject != null) + { + parentObject.HitObjectApplied -= onHitObjectApplied; + parentObject.ApplyCustomUpdateState -= updateStateTransforms; } } } From c95eb2d2c1c6b93e89e2a11755fe2895f583b611 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 14:28:47 +0900 Subject: [PATCH 393/803] Fix incorrect case in `CatcherArea` parameter --- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index a0279b5c83..7a29ba9801 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -120,10 +120,10 @@ namespace osu.Game.Rulesets.Catch.UI lastHyperDashState = Catcher.HyperDashing; } - public void SetCatcherPosition(float X) + public void SetCatcherPosition(float x) { float lastPosition = Catcher.X; - float newPosition = Math.Clamp(X, 0, CatchPlayfield.WIDTH); + float newPosition = Math.Clamp(x, 0, CatchPlayfield.WIDTH); Catcher.X = newPosition; From a52ea3cabe34c6e1c90a6126ab5a506151ca6362 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 14:29:55 +0900 Subject: [PATCH 394/803] Enable NRT and simplify `LineBufferedReader` --- .../Beatmaps/IO/LineBufferedReaderTest.cs | 12 ++--- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 +- osu.Game/IO/LineBufferedReader.cs | 48 ++++++++----------- 3 files changed, 23 insertions(+), 39 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs index 8f20fd7a68..14fbcb6176 100644 --- a/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs @@ -108,19 +108,13 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestReadToEndAfterReadsAndPeeks() { - const string contents = "this line is gone\rthis one shouldn't be\r\nthese ones\ndefinitely not"; + const string contents = "first line\r\nsecond line"; using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) using (var bufferedReader = new LineBufferedReader(stream)) { - Assert.AreEqual("this line is gone", bufferedReader.ReadLine()); - Assert.AreEqual("this one shouldn't be", bufferedReader.PeekLine()); - - string[] endingLines = bufferedReader.ReadToEnd().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); - Assert.AreEqual(3, endingLines.Length); - Assert.AreEqual("this one shouldn't be", endingLines[0]); - Assert.AreEqual("these ones", endingLines[1]); - Assert.AreEqual("definitely not", endingLines[2]); + bufferedReader.PeekLine(); + Assert.Throws(() => bufferedReader.ReadToEnd()); } } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index a5e6ac0a1c..52e760a068 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps.Formats { Section section = Section.General; - string line; + string? line; while ((line = stream.ReadLine()) != null) { diff --git a/osu.Game/IO/LineBufferedReader.cs b/osu.Game/IO/LineBufferedReader.cs index db435576bf..1e329c786f 100644 --- a/osu.Game/IO/LineBufferedReader.cs +++ b/osu.Game/IO/LineBufferedReader.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; -using System.Collections.Generic; using System.IO; using System.Text; @@ -17,58 +14,51 @@ namespace osu.Game.IO public class LineBufferedReader : IDisposable { private readonly StreamReader streamReader; - private readonly Queue lineBuffer; + + private string? peekedLine; public LineBufferedReader(Stream stream, bool leaveOpen = false) { streamReader = new StreamReader(stream, Encoding.UTF8, true, 1024, leaveOpen); - lineBuffer = new Queue(); } /// /// Reads the next line from the stream without consuming it. /// Subsequent calls to without a will return the same string. /// - public string PeekLine() - { - if (lineBuffer.Count > 0) - return lineBuffer.Peek(); - - string line = streamReader.ReadLine(); - if (line != null) - lineBuffer.Enqueue(line); - return line; - } + public string? PeekLine() => peekedLine ??= streamReader.ReadLine(); /// /// Reads the next line from the stream and consumes it. /// If a line was peeked, that same line will then be consumed and returned. /// - public string ReadLine() => lineBuffer.Count > 0 ? lineBuffer.Dequeue() : streamReader.ReadLine(); + public string? ReadLine() + { + try + { + return peekedLine ?? streamReader.ReadLine(); + } + finally + { + peekedLine = null; + } + } /// /// Reads the stream to its end and returns the text read. - /// This includes any peeked but unconsumed lines. + /// Not compatible with calls to . /// public string ReadToEnd() { - string remainingText = streamReader.ReadToEnd(); - if (lineBuffer.Count == 0) - return remainingText; + if (peekedLine != null) + throw new InvalidOperationException($"Do not use {nameof(ReadToEnd)} when also peeking for lines."); - var builder = new StringBuilder(); - - // this might not be completely correct due to varying platform line endings - while (lineBuffer.Count > 0) - builder.AppendLine(lineBuffer.Dequeue()); - builder.Append(remainingText); - - return builder.ToString(); + return streamReader.ReadToEnd(); } public void Dispose() { - streamReader?.Dispose(); + streamReader.Dispose(); } } } From ae49aafde215e65f19db42b2a73d2848a509ed0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 14:50:30 +0900 Subject: [PATCH 395/803] Fix `BeatSyncedContainer` unintentionally blocking on beatmap load --- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 45a935d165..4b40add87f 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -86,7 +86,7 @@ namespace osu.Game.Graphics.Containers TimingControlPoint timingPoint; EffectControlPoint effectPoint; - IsBeatSyncedWithTrack = BeatSyncSource.Clock?.IsRunning == true; + IsBeatSyncedWithTrack = BeatSyncSource.Clock?.IsRunning == true && BeatSyncSource.ControlPoints != null; double currentTrackTime; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index ead3eeb0dc..c6a32e1d5b 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -583,7 +583,7 @@ namespace osu.Game Host.ExceptionThrown -= onExceptionThrown; } - ControlPointInfo IBeatSyncProvider.ControlPoints => Beatmap.Value.Beatmap.ControlPointInfo; + ControlPointInfo IBeatSyncProvider.ControlPoints => Beatmap.Value.BeatmapLoaded ? Beatmap.Value.Beatmap.ControlPointInfo : null; IClock IBeatSyncProvider.Clock => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track : (IClock)null; ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : null; } From 12d396a51359dcec41f24a34aa8424f43d5acc2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 15:43:35 +0900 Subject: [PATCH 396/803] Use `-1` to specify default buffer size --- osu.Game/IO/LineBufferedReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/LineBufferedReader.cs b/osu.Game/IO/LineBufferedReader.cs index 1e329c786f..0210d7207e 100644 --- a/osu.Game/IO/LineBufferedReader.cs +++ b/osu.Game/IO/LineBufferedReader.cs @@ -19,7 +19,7 @@ namespace osu.Game.IO public LineBufferedReader(Stream stream, bool leaveOpen = false) { - streamReader = new StreamReader(stream, Encoding.UTF8, true, 1024, leaveOpen); + streamReader = new StreamReader(stream, Encoding.UTF8, true, -1, leaveOpen); } /// From a1b6ec60c899fe564e563b45b712fec9616876ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 18:38:39 +0900 Subject: [PATCH 397/803] Add statistics display for `MemoryCachingComponent`s Never sure if these are working as they should (or how well they are working). This helps quite a bit. --- osu.Game/Database/MemoryCachingComponent.cs | 46 +++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/osu.Game/Database/MemoryCachingComponent.cs b/osu.Game/Database/MemoryCachingComponent.cs index 104943bbae..571a9ccc7c 100644 --- a/osu.Game/Database/MemoryCachingComponent.cs +++ b/osu.Game/Database/MemoryCachingComponent.cs @@ -8,7 +8,9 @@ using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; +using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; +using osu.Framework.Statistics; namespace osu.Game.Database { @@ -20,8 +22,16 @@ namespace osu.Game.Database { private readonly ConcurrentDictionary cache = new ConcurrentDictionary(); + private readonly GlobalStatistic statistics; + protected virtual bool CacheNullValues => true; + protected MemoryCachingComponent() + { + statistics = GlobalStatistics.Get(nameof(MemoryCachingComponent), GetType().ReadableName()); + statistics.Value = new MemoryCachingStatistics(); + } + /// /// Retrieve the cached value for the given lookup. /// @@ -30,12 +40,20 @@ namespace osu.Game.Database protected async Task GetAsync([NotNull] TLookup lookup, CancellationToken token = default) { if (CheckExists(lookup, out TValue performance)) + { + statistics.Value.HitCount++; return performance; + } var computed = await ComputeValueAsync(lookup, token).ConfigureAwait(false); + statistics.Value.MissCount++; + if (computed != null || CacheNullValues) + { cache[lookup] = computed; + statistics.Value.Usage = cache.Count; + } return computed; } @@ -51,6 +69,8 @@ namespace osu.Game.Database if (matchKeyPredicate(kvp.Key)) cache.TryRemove(kvp.Key, out _); } + + statistics.Value.Usage = cache.Count; } protected bool CheckExists([NotNull] TLookup lookup, out TValue value) => @@ -63,5 +83,31 @@ namespace osu.Game.Database /// An optional to cancel the operation. /// The computed value. protected abstract Task ComputeValueAsync(TLookup lookup, CancellationToken token = default); + + private class MemoryCachingStatistics + { + /// + /// Total number of cache hits. + /// + public int HitCount; + + /// + /// Total number of cache misses. + /// + public int MissCount; + + /// + /// Total number of cached entities. + /// + public int Usage; + + public override string ToString() + { + int totalAccesses = HitCount + MissCount; + double hitRate = totalAccesses == 0 ? 0 : (double)HitCount / totalAccesses; + + return $"i:{Usage} h:{HitCount} m:{MissCount} {hitRate:0%}"; + } + } } } From 01bc6e5cb7ef98d26e001b8f89bef14fd1ddabd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 19:53:19 +0900 Subject: [PATCH 398/803] Revert old behaviour of `ReadToEnd` --- .../Beatmaps/IO/LineBufferedReaderTest.cs | 12 +++++++++--- osu.Game/IO/LineBufferedReader.cs | 15 +++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs index 14fbcb6176..8f20fd7a68 100644 --- a/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs @@ -108,13 +108,19 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestReadToEndAfterReadsAndPeeks() { - const string contents = "first line\r\nsecond line"; + const string contents = "this line is gone\rthis one shouldn't be\r\nthese ones\ndefinitely not"; using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) using (var bufferedReader = new LineBufferedReader(stream)) { - bufferedReader.PeekLine(); - Assert.Throws(() => bufferedReader.ReadToEnd()); + Assert.AreEqual("this line is gone", bufferedReader.ReadLine()); + Assert.AreEqual("this one shouldn't be", bufferedReader.PeekLine()); + + string[] endingLines = bufferedReader.ReadToEnd().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + Assert.AreEqual(3, endingLines.Length); + Assert.AreEqual("this one shouldn't be", endingLines[0]); + Assert.AreEqual("these ones", endingLines[1]); + Assert.AreEqual("definitely not", endingLines[2]); } } } diff --git a/osu.Game/IO/LineBufferedReader.cs b/osu.Game/IO/LineBufferedReader.cs index 0210d7207e..6fcea08990 100644 --- a/osu.Game/IO/LineBufferedReader.cs +++ b/osu.Game/IO/LineBufferedReader.cs @@ -46,14 +46,21 @@ namespace osu.Game.IO /// /// Reads the stream to its end and returns the text read. - /// Not compatible with calls to . + /// This includes any peeked but unconsumed lines. /// public string ReadToEnd() { - if (peekedLine != null) - throw new InvalidOperationException($"Do not use {nameof(ReadToEnd)} when also peeking for lines."); + string remainingText = streamReader.ReadToEnd(); + if (peekedLine == null) + return remainingText; - return streamReader.ReadToEnd(); + var builder = new StringBuilder(); + + // this might not be completely correct due to varying platform line endings + builder.AppendLine(peekedLine); + builder.Append(remainingText); + + return builder.ToString(); } public void Dispose() From c2f106907364de79167a92cf8e0f2f1930168449 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 19:55:43 +0900 Subject: [PATCH 399/803] Avoid usage of `finally` in potentially hot path --- osu.Game/IO/LineBufferedReader.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/IO/LineBufferedReader.cs b/osu.Game/IO/LineBufferedReader.cs index 6fcea08990..93e554b43d 100644 --- a/osu.Game/IO/LineBufferedReader.cs +++ b/osu.Game/IO/LineBufferedReader.cs @@ -34,14 +34,10 @@ namespace osu.Game.IO /// public string? ReadLine() { - try - { - return peekedLine ?? streamReader.ReadLine(); - } - finally - { - peekedLine = null; - } + string? line = peekedLine ?? streamReader.ReadLine(); + + peekedLine = null; + return line; } /// From 0281bf672c42d5cedf8a7ac607dec280ad4acbdd Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 6 Jul 2022 15:58:25 -0400 Subject: [PATCH 400/803] operate on vectors instead of vector components --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 052c7128f6..ad3a54c630 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -51,24 +51,19 @@ namespace osu.Game.Rulesets.Osu.Mods foreach (var drawable in playfield.HitObjectContainer.AliveObjects) { - float x = Math.Clamp(2 * drawable.X - cursorPos.X, 0, OsuPlayfield.BASE_SIZE.X); - float y = Math.Clamp(2 * drawable.Y - cursorPos.Y, 0, OsuPlayfield.BASE_SIZE.Y); + var destination = Vector2.Clamp(2 * drawable.Position - cursorPos, Vector2.Zero, OsuPlayfield.BASE_SIZE); if (drawable.HitObject is Slider thisSlider) { var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(thisSlider); - x = possibleMovementBounds.Width < 0 - ? x - : Math.Clamp(x, possibleMovementBounds.Left, possibleMovementBounds.Right); - - y = possibleMovementBounds.Height < 0 - ? y - : Math.Clamp(y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); + destination = Vector2.Clamp( + destination, + new Vector2(possibleMovementBounds.Left, possibleMovementBounds.Top), + new Vector2(possibleMovementBounds.Right, possibleMovementBounds.Bottom) + ); } - var destination = new Vector2(x, y); - switch (drawable) { case DrawableHitCircle circle: From 40e98f84f304c6288c2decbecda614f5d8b1042d Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 6 Jul 2022 16:01:08 -0400 Subject: [PATCH 401/803] change default strength back to 0.5 --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index ad3a54c630..c53ac58752 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods private IFrameStableClock gameplayClock; [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] - public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.6f) + public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, MinValue = 0.05f, From 7f94405c9e4ea5bc81416b270cb9cce22a1cc4b8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 6 Jul 2022 23:38:11 +0300 Subject: [PATCH 402/803] Rename method and make duration optional --- .../Skinning/Default/DefaultSpinnerDisc.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs index 03db76336c..60489c1b22 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { base.LoadComplete(); - complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200)); + complete.BindValueChanged(complete => updateDiscColour(complete.NewValue, 200)); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default this.ScaleTo(initial_scale); this.RotateTo(0); - updateComplete(false, 0); + updateDiscColour(false); using (BeginDelayedSequence(spinner.TimePreempt / 2)) { @@ -182,11 +182,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default if (drawableSpinner.Result?.TimeCompleted is double completionTime) { using (BeginAbsoluteSequence(completionTime)) - updateComplete(true, 200); + updateDiscColour(true, 200); } } - private void updateComplete(bool complete, double duration) + private void updateDiscColour(bool complete, double duration = 0) { var colour = complete ? completeColour : normalColour; From d5b4d146708a5d712924d02a479f8063e152ab5a Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:01:14 -0400 Subject: [PATCH 403/803] modify damp length to effectively invert repulsion strength --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index c53ac58752..0aab019d73 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void easeTo(DrawableHitObject hitObject, Vector2 destination) { - double dampLength = Interpolation.Lerp(3000, 40, 0.8 * RepulsionStrength.Value); + double dampLength = Vector2.Distance(hitObject.Position, destination) / (0.04 * RepulsionStrength.Value + 0.04); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); From 62beae4063846c0022930925506d48435985d756 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:18:21 -0400 Subject: [PATCH 404/803] add nullable directive --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 0aab019d73..1cb5bf31a4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.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 disable + using System; using osu.Framework.Bindables; using osu.Framework.Utils; From a5b01b89201ef4ba5db1fa64f370d180aa80d567 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 7 Jul 2022 01:00:42 +0300 Subject: [PATCH 405/803] Improve asserts in `TestSeekPerformsInGameplayTime` to be more descriptive --- .../Gameplay/TestSceneMasterGameplayClockContainer.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index ae431e77ae..0395ae9d99 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -9,7 +9,6 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Testing; using osu.Framework.Timing; -using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; @@ -116,10 +115,10 @@ namespace osu.Game.Tests.Gameplay AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset)); AddStep("seek to 2500", () => gameplayClockContainer.Seek(2500)); - AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 2500, 10f)); + AddStep("gameplay clock time = 2500", () => Assert.AreEqual(gameplayClockContainer.CurrentTime, 2500, 10f)); AddStep("seek to 10000", () => gameplayClockContainer.Seek(10000)); - AddAssert("gameplay clock time = 10000", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 10000, 10f)); + AddStep("gameplay clock time = 10000", () => Assert.AreEqual(gameplayClockContainer.CurrentTime, 10000, 10f)); } protected override void Dispose(bool isDisposing) From 911507291788745f1fc90f4824455b2698408186 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 7 Jul 2022 04:24:10 +0300 Subject: [PATCH 406/803] Fix flaky tests not running at all with environment variable set --- osu.Game/Tests/FlakyTestAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/FlakyTestAttribute.cs b/osu.Game/Tests/FlakyTestAttribute.cs index 299dbb89a2..c61ce80bf5 100644 --- a/osu.Game/Tests/FlakyTestAttribute.cs +++ b/osu.Game/Tests/FlakyTestAttribute.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests } public FlakyTestAttribute(int tryCount) - : base(Environment.GetEnvironmentVariable("OSU_TESTS_FAIL_FLAKY") == "1" ? 0 : tryCount) + : base(Environment.GetEnvironmentVariable("OSU_TESTS_FAIL_FLAKY") == "1" ? 1 : tryCount) { } } From c4b6893709c15ca2aace28c40697a3ed30555911 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 14:29:15 +0900 Subject: [PATCH 407/803] Add local handling of cases where a beatmap's file cannot be found on disk --- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 76 ++++++++++++++++++++---- 1 file changed, 65 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 9d31c58709..088cbd9d60 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -137,8 +137,17 @@ namespace osu.Game.Beatmaps try { - using (var stream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path)))) - return Decoder.GetDecoder(stream).Decode(stream); + string fileStorePath = BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path); + var stream = GetStream(fileStorePath); + + if (stream == null) + { + Logger.Log($"Beatmap failed to load (file {BeatmapInfo.Path} not found on disk at expected location {fileStorePath}).", level: LogLevel.Error); + return null; + } + + using (var reader = new LineBufferedReader(stream)) + return Decoder.GetDecoder(reader).Decode(reader); } catch (Exception e) { @@ -154,7 +163,16 @@ namespace osu.Game.Beatmaps try { - return resources.LargeTextureStore.Get(BeatmapSetInfo.GetPathForFile(Metadata.BackgroundFile)); + string fileStorePath = BeatmapSetInfo.GetPathForFile(Metadata.BackgroundFile); + var texture = resources.LargeTextureStore.Get(fileStorePath); + + if (texture == null) + { + Logger.Log($"Beatmap background failed to load (file {Metadata.BackgroundFile} not found on disk at expected location {fileStorePath}).", level: LogLevel.Error); + return null; + } + + return texture; } catch (Exception e) { @@ -173,7 +191,16 @@ namespace osu.Game.Beatmaps try { - return resources.Tracks.Get(BeatmapSetInfo.GetPathForFile(Metadata.AudioFile)); + string fileStorePath = BeatmapSetInfo.GetPathForFile(Metadata.AudioFile); + var track = resources.Tracks.Get(fileStorePath); + + if (track == null) + { + Logger.Log($"Beatmap failed to load (file {Metadata.AudioFile} not found on disk at expected location {fileStorePath}).", level: LogLevel.Error); + return null; + } + + return track; } catch (Exception e) { @@ -192,8 +219,17 @@ namespace osu.Game.Beatmaps try { - var trackData = GetStream(BeatmapSetInfo.GetPathForFile(Metadata.AudioFile)); - return trackData == null ? null : new Waveform(trackData); + string fileStorePath = BeatmapSetInfo.GetPathForFile(Metadata.AudioFile); + + var trackData = GetStream(fileStorePath); + + if (trackData == null) + { + Logger.Log($"Beatmap waveform failed to load (file {Metadata.AudioFile} not found on disk at expected location {fileStorePath}).", level: LogLevel.Error); + return null; + } + + return new Waveform(trackData); } catch (Exception e) { @@ -211,19 +247,37 @@ namespace osu.Game.Beatmaps try { - using (var stream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path)))) + string fileStorePath = BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path); + var stream = GetStream(fileStorePath); + + if (stream == null) { - var decoder = Decoder.GetDecoder(stream); + Logger.Log($"Beatmap failed to load (file {BeatmapInfo.Path} not found on disk at expected location {fileStorePath})", level: LogLevel.Error); + return null; + } + + using (var reader = new LineBufferedReader(stream)) + { + var decoder = Decoder.GetDecoder(reader); string storyboardFilename = BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; // todo: support loading from both set-wide storyboard *and* beatmap specific. if (string.IsNullOrEmpty(storyboardFilename)) - storyboard = decoder.Decode(stream); + storyboard = decoder.Decode(reader); else { - using (var secondaryStream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(storyboardFilename)))) - storyboard = decoder.Decode(stream, secondaryStream); + string storyboardFileStorePath = BeatmapSetInfo.GetPathForFile(storyboardFilename); + var secondaryStream = GetStream(storyboardFileStorePath); + + if (secondaryStream == null) + { + Logger.Log($"Storyboard failed to load (file {storyboardFilename} not found on disk at expected location {fileStorePath})", level: LogLevel.Error); + return null; + } + + using (var secondaryReader = new LineBufferedReader(secondaryStream)) + storyboard = decoder.Decode(reader, secondaryReader); } } } From e81cebf27d4a5030cce1d2550e9895515b9acfdc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 14:33:17 +0900 Subject: [PATCH 408/803] Change storyboard parsing logic to not completely fail if only `.osb` read fails Changes to allow the storyboard to exist if only the `.osu` is available. Reads better IMO. --- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 088cbd9d60..ce883a7092 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -248,37 +248,37 @@ namespace osu.Game.Beatmaps try { string fileStorePath = BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path); - var stream = GetStream(fileStorePath); + var beatmapFileStream = GetStream(fileStorePath); - if (stream == null) + if (beatmapFileStream == null) { Logger.Log($"Beatmap failed to load (file {BeatmapInfo.Path} not found on disk at expected location {fileStorePath})", level: LogLevel.Error); return null; } - using (var reader = new LineBufferedReader(stream)) + using (var reader = new LineBufferedReader(beatmapFileStream)) { var decoder = Decoder.GetDecoder(reader); - string storyboardFilename = BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; + Stream storyboardFileStream = null; - // todo: support loading from both set-wide storyboard *and* beatmap specific. - if (string.IsNullOrEmpty(storyboardFilename)) - storyboard = decoder.Decode(reader); - else + if (BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename is string storyboardFilename) { - string storyboardFileStorePath = BeatmapSetInfo.GetPathForFile(storyboardFilename); - var secondaryStream = GetStream(storyboardFileStorePath); + string storyboardFileStorePath = BeatmapSetInfo?.GetPathForFile(storyboardFilename); + storyboardFileStream = GetStream(storyboardFileStorePath); - if (secondaryStream == null) - { - Logger.Log($"Storyboard failed to load (file {storyboardFilename} not found on disk at expected location {fileStorePath})", level: LogLevel.Error); - return null; - } + if (storyboardFileStream == null) + Logger.Log($"Storyboard failed to load (file {storyboardFilename} not found on disk at expected location {storyboardFileStorePath})", level: LogLevel.Error); + } - using (var secondaryReader = new LineBufferedReader(secondaryStream)) + if (storyboardFileStream != null) + { + // Stand-alone storyboard was found, so parse in addition to the beatmap's local storyboard. + using (var secondaryReader = new LineBufferedReader(storyboardFileStream)) storyboard = decoder.Decode(reader, secondaryReader); } + else + storyboard = decoder.Decode(reader); } } catch (Exception e) From e1b434b5dc3b685dc55b2d806fa8fb72fd852ee7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 14:46:51 +0900 Subject: [PATCH 409/803] Fix song select placeholder not showing convert hint for custom rulesets --- osu.Game/Screens/Select/NoResultsPlaceholder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/NoResultsPlaceholder.cs b/osu.Game/Screens/Select/NoResultsPlaceholder.cs index 5d5eafd2e6..b8b589ff99 100644 --- a/osu.Game/Screens/Select/NoResultsPlaceholder.cs +++ b/osu.Game/Screens/Select/NoResultsPlaceholder.cs @@ -135,7 +135,7 @@ namespace osu.Game.Screens.Select // TODO: Add realm queries to hint at which ruleset results are available in (and allow clicking to switch). // TODO: Make this message more certain by ensuring the osu! beatmaps exist before suggesting. - if (filter?.Ruleset?.OnlineID > 0 && !filter.AllowConvertedBeatmaps) + if (filter?.Ruleset?.OnlineID != 0 && filter?.AllowConvertedBeatmaps == false) { textFlow.AddParagraph("- Try"); textFlow.AddLink(" enabling ", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); From 9d730f84400b236e0cabd8a8f01dc4fd6e78d09c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 14:49:22 +0900 Subject: [PATCH 410/803] Fix custom rulesets not importing scores at all Replaces the error with the ability to import, minus replays. Closes https://github.com/ppy/osu/issues/17350 (arguably, but let's go with it for now). --- osu.Game/Screens/Play/Player.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4040adc48d..b4016fc1cf 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -22,6 +22,7 @@ using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Extensions; using osu.Game.Graphics.Containers; using osu.Game.IO.Archives; using osu.Game.Online.API; @@ -1064,12 +1065,15 @@ namespace osu.Game.Screens.Play if (DrawableRuleset.ReplayScore != null) return Task.CompletedTask; - LegacyByteArrayReader replayReader; + LegacyByteArrayReader replayReader = null; - using (var stream = new MemoryStream()) + if (score.ScoreInfo.Ruleset.IsLegacyRuleset()) { - new LegacyScoreEncoder(score, GameplayState.Beatmap).Encode(stream); - replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr"); + using (var stream = new MemoryStream()) + { + new LegacyScoreEncoder(score, GameplayState.Beatmap).Encode(stream); + replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr"); + } } // the import process will re-attach managed beatmap/rulesets to this score. we don't want this for now, so create a temporary copy to import. From f88e2aa025e676ad2f3d82c459fd3b25420f1a24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 15:02:03 +0900 Subject: [PATCH 411/803] Remove EF test workaround --- .../Gameplay/TestScenePlayerScoreSubmission.cs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index e0c8989389..501ad9bd28 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -363,23 +363,9 @@ namespace osu.Game.Tests.Visual.Gameplay await AllowImportCompletion.WaitAsync().ConfigureAwait(false); - ImportedScore = score; + await base.ImportScore(score); - // It was discovered that Score members could sometimes be half-populated. - // In particular, the RulesetID property could be set to 0 even on non-osu! maps. - // We want to test that the state of that property is consistent in this test. - // EF makes this impossible. - // - // First off, because of the EF navigational property-explicit foreign key field duality, - // it can happen that - for example - the Ruleset navigational property is correctly initialised to mania, - // but the RulesetID foreign key property is not initialised and remains 0. - // EF silently bypasses this by prioritising the Ruleset navigational property over the RulesetID foreign key one. - // - // Additionally, adding an entity to an EF DbSet CAUSES SIDE EFFECTS with regard to the foreign key property. - // In the above instance, if a ScoreInfo with Ruleset = {mania} and RulesetID = 0 is attached to an EF context, - // RulesetID WILL BE SILENTLY SET TO THE CORRECT VALUE of 3. - // - // For the above reasons, actual importing is disabled in this test. + ImportedScore = score; } } } From 1a41d3ef209302df0976302c15f96b5604272615 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 16:42:36 +0900 Subject: [PATCH 412/803] Allow `PlayerTestScene` to import the beatmap it's using --- osu.Game/Tests/Visual/PlayerTestScene.cs | 27 +++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index e1714299b6..036ac8a639 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -4,12 +4,15 @@ #nullable disable using System; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -22,6 +25,11 @@ namespace osu.Game.Tests.Visual /// protected virtual bool HasCustomSteps => false; + /// + /// WARNING: ONLY WORKS IF RUN HEADLESS because reasons. + /// + protected virtual bool ImportBeatmapToDatabase => false; + protected TestPlayer Player; protected OsuConfigManager LocalConfig; @@ -49,7 +57,7 @@ namespace osu.Game.Tests.Visual action?.Invoke(); - AddStep(CreatePlayerRuleset().Description, LoadPlayer); + AddStep($"Load player for {CreatePlayerRuleset().Description}", LoadPlayer); AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); } @@ -57,6 +65,9 @@ namespace osu.Game.Tests.Visual protected virtual bool Autoplay => false; + [Resolved] + private BeatmapManager beatmaps { get; set; } + protected void LoadPlayer() { var ruleset = CreatePlayerRuleset(); @@ -64,6 +75,20 @@ namespace osu.Game.Tests.Visual var beatmap = CreateBeatmap(ruleset.RulesetInfo); + if (ImportBeatmapToDatabase) + { + Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null); + + var imported = beatmaps.Import(beatmap.BeatmapInfo.BeatmapSet); + + Debug.Assert(imported != null); + + beatmap.BeatmapInfo = null; + beatmap.Difficulty = null; + + beatmap.BeatmapInfo = imported.Value.Detach().Beatmaps.First(); + } + Beatmap.Value = CreateWorkingBeatmap(beatmap); SelectedMods.Value = Array.Empty(); From 461d133c1f0a9a736bfa265e5c7dd060d6dd68b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 16:42:48 +0900 Subject: [PATCH 413/803] Add test coverage of score importing --- .../TestScenePlayerLocalScoreImport.cs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs new file mode 100644 index 0000000000..61f0483c86 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.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 System.Linq; +using NUnit.Framework; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [HeadlessTest] // Importing rulesets doesn't work in interactive flows. + public class TestScenePlayerLocalScoreImport : PlayerTestScene + { + private Ruleset? customRuleset; + + protected override bool ImportBeatmapToDatabase => true; + + protected override Ruleset CreatePlayerRuleset() => customRuleset ?? new OsuRuleset(); + + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false); + + protected override bool HasCustomSteps => true; + + protected override bool AllowFail => false; + + [Test] + public void TestScoreStoredLocally() + { + AddStep("set no custom ruleset", () => customRuleset = null); + + CreateTest(); + + 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); + AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); + } + + [Test] + public void TestScoreStoredLocallyCustomRuleset() + { + Ruleset createCustomRuleset() => new OsuRuleset + { + RulesetInfo = + { + Name = "custom", + ShortName = "custom", + OnlineID = -1 + } + }; + + AddStep("import custom ruleset", () => Realm.Write(r => r.Add(createCustomRuleset().RulesetInfo))); + AddStep("set custom ruleset", () => customRuleset = createCustomRuleset()); + + CreateTest(); + + 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); + AddUntilStep("score in database", () => Realm.Run(r => r.All().Count() == 1)); + } + } +} From b663986b9fd2922ca3a29e4de95af012c04f30a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Apr 2022 16:12:06 +0900 Subject: [PATCH 414/803] Add test coverage of locally available without replay button handling --- .../Gameplay/TestSceneReplayDownloadButton.cs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 10a6b196b0..c259d5f0a8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -142,6 +142,28 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); } + [Test] + public void TestLocallyAvailableWithoutReplay() + { + Live imported = null; + + AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(false, false))); + + AddStep("create button without replay", () => + { + Child = downloadButton = new TestReplayDownloadButton(imported.Value) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + }); + + AddUntilStep("wait for load", () => downloadButton.IsLoaded); + + AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); + AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); + } + [Test] public void TestScoreImportThenDelete() { @@ -189,11 +211,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); } - private ScoreInfo getScoreInfo(bool replayAvailable) + private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true) { return new APIScore { - OnlineID = online_score_id, + OnlineID = hasOnlineId ? online_score_id : 0, RulesetID = 0, Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(), HasReplay = replayAvailable, From 45c5b7e7dd4061157195b9f4f5a1a5fb388467f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 17:13:16 +0900 Subject: [PATCH 415/803] 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 398c1d91dd..8c15ed7949 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 ed6577651a..06b022ea44 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 7d70b96f62..9085205bbc 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 7ef03dd2cbe7b723d09ae898f8bced3d13812d2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 16:32:53 +0900 Subject: [PATCH 416/803] Use fire-and-forget async operations on global track This avoids any blocking overhead caused by a backlogged audio thread. Test seem to pass so might be okay? Note that order is still guaranteed due to the `ensureUpdateThread` queueing system framework-side. --- osu.Game/Overlays/MusicController.cs | 8 ++++---- osu.Game/Tests/Visual/OsuTestScene.cs | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 4a10f30a7a..8af295dfe8 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -133,9 +133,9 @@ namespace osu.Game.Overlays UserPauseRequested = false; if (restart) - CurrentTrack.Restart(); + CurrentTrack.RestartAsync(); else if (!IsPlaying) - CurrentTrack.Start(); + CurrentTrack.StartAsync(); return true; } @@ -152,7 +152,7 @@ namespace osu.Game.Overlays { UserPauseRequested |= requestedByUser; if (CurrentTrack.IsRunning) - CurrentTrack.Stop(); + CurrentTrack.StopAsync(); } /// @@ -250,7 +250,7 @@ namespace osu.Game.Overlays { // if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase). // we probably want to move this to a central method for switching to a new working beatmap in the future. - Schedule(() => CurrentTrack.Restart()); + Schedule(() => CurrentTrack.RestartAsync()); } private WorkingBeatmap current; diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index c13cdff820..012c512266 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -430,11 +430,19 @@ namespace osu.Game.Tests.Visual return accumulated == seek; } + public override Task SeekAsync(double seek) => Task.FromResult(Seek(seek)); + public override void Start() { running = true; } + public override Task StartAsync() + { + Start(); + return Task.CompletedTask; + } + public override void Reset() { Seek(0); @@ -450,6 +458,12 @@ namespace osu.Game.Tests.Visual } } + public override Task StopAsync() + { + Stop(); + return Task.CompletedTask; + } + public override bool IsRunning => running; private double? lastReferenceTime; From 5197d0fa9e1afbc25f004b56413121a9637b386a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 17:32:48 +0900 Subject: [PATCH 417/803] Add automatic transaction handling to realm helper methods --- osu.Game.Tests/Database/RealmLiveTests.cs | 19 ++++++++++++ osu.Game/Database/RealmExtensions.cs | 35 ++++++++++++++++++++--- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index a50eb22c67..d15e038723 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -59,6 +59,25 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestNestedWriteCalls() + { + RunTestWithRealm((realm, _) => + { + var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); + + var liveBeatmap = beatmap.ToLive(realm); + + realm.Run(r => + r.Write(_ => + r.Write(_ => + r.Add(beatmap))) + ); + + Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); + }); + } + [Test] public void TestAccessAfterAttach() { diff --git a/osu.Game/Database/RealmExtensions.cs b/osu.Game/Database/RealmExtensions.cs index 73e9f16d33..2cd81b6af1 100644 --- a/osu.Game/Database/RealmExtensions.cs +++ b/osu.Game/Database/RealmExtensions.cs @@ -8,18 +8,45 @@ namespace osu.Game.Database { public static class RealmExtensions { + /// + /// Perform a write operation against the provided realm instance. + /// + /// + /// This will automatically start a transaction if not already in one. + /// + /// The realm to operate on. + /// The write operation to run. public static void Write(this Realm realm, Action function) { - using var transaction = realm.BeginWrite(); + Transaction? transaction = null; + + if (!realm.IsInTransaction) + transaction = realm.BeginWrite(); + function(realm); - transaction.Commit(); + + transaction?.Commit(); } + /// + /// Perform a write operation against the provided realm instance. + /// + /// + /// This will automatically start a transaction if not already in one. + /// + /// The realm to operate on. + /// The write operation to run. public static T Write(this Realm realm, Func function) { - using var transaction = realm.BeginWrite(); + Transaction? transaction = null; + + if (!realm.IsInTransaction) + transaction = realm.BeginWrite(); + var result = function(realm); - transaction.Commit(); + + transaction?.Commit(); + return result; } From e2c4c94993e61234c42b08d359681ce12df39fa0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 17:37:46 +0900 Subject: [PATCH 418/803] Simplify `BeatmapUpdater` transaction handling using nested transaction support --- osu.Game/Beatmaps/BeatmapUpdater.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index d0ec44a034..978c6de35c 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -52,15 +52,7 @@ namespace osu.Game.Beatmaps /// /// Run all processing on a beatmap immediately. /// - public void Process(BeatmapSetInfo beatmapSet) - { - if (beatmapSet.Realm.IsInTransaction) - Process(beatmapSet, beatmapSet.Realm); - else - beatmapSet.Realm.Write(r => Process(beatmapSet, r)); - } - - public void Process(BeatmapSetInfo beatmapSet, Realm realm) + public void Process(BeatmapSetInfo beatmapSet) => beatmapSet.Realm.Write(r => { // Before we use below, we want to invalidate. workingBeatmapCache.Invalidate(beatmapSet); @@ -85,7 +77,7 @@ namespace osu.Game.Beatmaps // And invalidate again afterwards as re-fetching the most up-to-date database metadata will be required. workingBeatmapCache.Invalidate(beatmapSet); - } + }); private double calculateLength(IBeatmap b) { From 57b2f8189c4bdd3dd91bf00b035c3810ec44cb19 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 17:40:47 +0900 Subject: [PATCH 419/803] Add back test workaround for `TestScenePlayerScoreSubmission` with updated explanation This reverts commit f88e2aa025e676ad2f3d82c459fd3b25420f1a24. --- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 501ad9bd28..96efca6b65 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -363,9 +363,11 @@ namespace osu.Game.Tests.Visual.Gameplay await AllowImportCompletion.WaitAsync().ConfigureAwait(false); - await base.ImportScore(score); - ImportedScore = score; + + // Calling base.ImportScore is omitted as it will fail for the test method which uses a custom ruleset. + // This can be resolved by doing something similar to what TestScenePlayerLocalScoreImport is doing, + // but requires a bit of restructuring. } } } From dd5b127fb5e3e742053ff17b3965b4ecabd1bcc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 17:51:49 +0900 Subject: [PATCH 420/803] Update various tests to enable NRT to avoid new inspection failures --- .../Visual/Editing/TestSceneComposeScreen.cs | 4 +-- .../Editing/TestSceneEditorBeatmapCreation.cs | 34 +++++++++---------- .../TestSceneMultiSpectatorScreen.cs | 21 ++++++------ .../Multiplayer/TestSceneMultiplayer.cs | 24 ++++++------- .../TestSceneMultiplayerParticipantsList.cs | 10 +++--- .../Online/TestSceneLeaderboardModSelector.cs | 4 +-- 6 files changed, 44 insertions(+), 53 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs index 6b5d9af7af..291630fa3a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using NUnit.Framework; @@ -24,7 +22,7 @@ namespace osu.Game.Tests.Visual.Editing [TestFixture] public class TestSceneComposeScreen : EditorClockTestScene { - private EditorBeatmap editorBeatmap; + private EditorBeatmap editorBeatmap = null!; [Cached] private EditorClipboard clipboard = new EditorClipboard(); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 2707682b4c..f565ca3ef4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.IO; using System.Linq; @@ -39,7 +37,9 @@ namespace osu.Game.Tests.Visual.Editing protected override bool IsolateSavingFromDatabase => false; [Resolved] - private BeatmapManager beatmapManager { get; set; } + private BeatmapManager beatmapManager { get; set; } = null!; + + private Guid currentBeatmapSetID => EditorBeatmap.BeatmapInfo.BeatmapSet?.ID ?? Guid.Empty; public override void SetUpSteps() { @@ -50,19 +50,19 @@ namespace osu.Game.Tests.Visual.Editing AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString()); } - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new DummyWorkingBeatmap(Audio, null); + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) => new DummyWorkingBeatmap(Audio, null); [Test] public void TestCreateNewBeatmap() { AddStep("save beatmap", () => Editor.Save()); - AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.Value.DeletePending == false); + AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == currentBeatmapSetID)?.Value.DeletePending == false); } [Test] public void TestExitWithoutSave() { - EditorBeatmap editorBeatmap = null; + EditorBeatmap editorBeatmap = null!; AddStep("store editor beatmap", () => editorBeatmap = EditorBeatmap); @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen()); AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); - AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == editorBeatmap.BeatmapInfo.BeatmapSet.ID)?.Value.DeletePending == true); + AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == editorBeatmap.BeatmapInfo.BeatmapSet.AsNonNull().ID)?.Value.DeletePending == true); } [Test] @@ -160,7 +160,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("new beatmap persisted", () => { var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == firstDifficultyName); - var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + var set = beatmapManager.QueryBeatmapSet(s => s.ID == currentBeatmapSetID); return beatmap != null && beatmap.DifficultyName == firstDifficultyName @@ -179,7 +179,7 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("wait for created", () => { - string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; return difficultyName != null && difficultyName != firstDifficultyName; }); @@ -195,7 +195,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("new beatmap persisted", () => { var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == secondDifficultyName); - var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + var set = beatmapManager.QueryBeatmapSet(s => s.ID == currentBeatmapSetID); return beatmap != null && beatmap.DifficultyName == secondDifficultyName @@ -246,7 +246,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("new beatmap persisted", () => { var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == originalDifficultyName); - var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + var set = beatmapManager.QueryBeatmapSet(s => s.ID == currentBeatmapSetID); return beatmap != null && beatmap.DifficultyName == originalDifficultyName @@ -262,7 +262,7 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("wait for created", () => { - string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; return difficultyName != null && difficultyName != originalDifficultyName; }); @@ -281,13 +281,13 @@ namespace osu.Game.Tests.Visual.Editing AddStep("save beatmap", () => Editor.Save()); - BeatmapInfo refetchedBeatmap = null; - Live refetchedBeatmapSet = null; + BeatmapInfo? refetchedBeatmap = null; + Live? refetchedBeatmapSet = null; AddStep("refetch from database", () => { refetchedBeatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == copyDifficultyName); - refetchedBeatmapSet = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + refetchedBeatmapSet = beatmapManager.QueryBeatmapSet(s => s.ID == currentBeatmapSetID); }); AddAssert("new beatmap persisted", () => @@ -323,7 +323,7 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("wait for created", () => { - string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; return difficultyName != null && difficultyName != "New Difficulty"; }); AddAssert("new difficulty has correct name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "New Difficulty (1)"); @@ -359,7 +359,7 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("wait for created", () => { - string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; return difficultyName != null && difficultyName != duplicate_difficulty_name; }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 877c986d61..7df68392cf 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -33,20 +31,21 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneMultiSpectatorScreen : MultiplayerTestScene { [Resolved] - private OsuGameBase game { get; set; } + private OsuGameBase game { get; set; } = null!; [Resolved] - private OsuConfigManager config { get; set; } + private OsuConfigManager config { get; set; } = null!; [Resolved] - private BeatmapManager beatmapManager { get; set; } + private BeatmapManager beatmapManager { get; set; } = null!; - private MultiSpectatorScreen spectatorScreen; + private MultiSpectatorScreen spectatorScreen = null!; private readonly List playingUsers = new List(); - private BeatmapSetInfo importedSet; - private BeatmapInfo importedBeatmap; + private BeatmapSetInfo importedSet = null!; + private BeatmapInfo importedBeatmap = null!; + private int importedBeatmapId; [BackgroundDependencyLoader] @@ -340,7 +339,7 @@ namespace osu.Game.Tests.Visual.Multiplayer sendFrames(getPlayerIds(count), 300); } - Player player = null; + Player? player = null; AddStep($"get {PLAYER_1_ID} player instance", () => player = getInstance(PLAYER_1_ID).ChildrenOfType().Single()); @@ -369,7 +368,7 @@ namespace osu.Game.Tests.Visual.Multiplayer b.Storyboard.GetLayer("Background").Add(sprite); }); - private void testLeadIn(Action applyToBeatmap = null) + private void testLeadIn(Action? applyToBeatmap = null) { start(PLAYER_1_ID); @@ -387,7 +386,7 @@ namespace osu.Game.Tests.Visual.Multiplayer assertRunning(PLAYER_1_ID); } - private void loadSpectateScreen(bool waitForPlayerLoad = true, Action applyToBeatmap = null) + private void loadSpectateScreen(bool waitForPlayerLoad = true, Action? applyToBeatmap = null) { AddStep("load screen", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index da48fb7332..a2793acba7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Diagnostics; using System.Linq; @@ -51,17 +49,17 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayer : ScreenTestScene { - private BeatmapManager beatmaps; - private RulesetStore rulesets; - private BeatmapSetInfo importedSet; + private BeatmapManager beatmaps = null!; + private RulesetStore rulesets = null!; + private BeatmapSetInfo importedSet = null!; - private TestMultiplayerComponents multiplayerComponents; + private TestMultiplayerComponents multiplayerComponents = null!; private TestMultiplayerClient multiplayerClient => multiplayerComponents.MultiplayerClient; private TestMultiplayerRoomManager roomManager => multiplayerComponents.RoomManager; [Resolved] - private OsuConfigManager config { get; set; } + private OsuConfigManager config { get; set; } = null!; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -146,7 +144,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void removeLastUser() { - APIUser lastUser = multiplayerClient.ServerRoom?.Users.Last().User; + APIUser? lastUser = multiplayerClient.ServerRoom?.Users.Last().User; if (lastUser == null || lastUser == multiplayerClient.LocalUser?.User) return; @@ -156,7 +154,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void kickLastUser() { - APIUser lastUser = multiplayerClient.ServerRoom?.Users.Last().User; + APIUser? lastUser = multiplayerClient.ServerRoom?.Users.Last().User; if (lastUser == null || lastUser == multiplayerClient.LocalUser?.User) return; @@ -351,7 +349,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("join room", () => InputManager.Key(Key.Enter)); - DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null; + DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "password"); AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().TriggerClick()); @@ -678,7 +676,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestGameplayExitFlow() { - Bindable holdDelay = null; + Bindable? holdDelay = null; AddStep("Set hold delay to zero", () => { @@ -709,7 +707,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for lounge", () => multiplayerComponents.CurrentScreen is Screens.OnlinePlay.Multiplayer.Multiplayer); AddStep("stop holding", () => InputManager.ReleaseKey(Key.Escape)); - AddStep("set hold delay to default", () => holdDelay.SetDefault()); + AddStep("set hold delay to default", () => holdDelay?.SetDefault()); } [Test] @@ -992,7 +990,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for ready button to be enabled", () => readyButton.Enabled.Value); MultiplayerUserState lastState = MultiplayerUserState.Idle; - MultiplayerRoomUser user = null; + MultiplayerRoomUser? user = null; AddStep("click ready button", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 7db18d1127..a70dfd78c5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -66,7 +64,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestRemoveUser() { - APIUser secondUser = null; + APIUser? secondUser = null; AddStep("add a user", () => { @@ -80,7 +78,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("remove host", () => MultiplayerClient.RemoveUser(API.LocalUser.Value)); - AddAssert("single panel is for second user", () => this.ChildrenOfType().Single().User.UserID == secondUser.Id); + AddAssert("single panel is for second user", () => this.ChildrenOfType().Single().User.UserID == secondUser?.Id); } [Test] @@ -368,7 +366,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void createNewParticipantsList() { - ParticipantsList participantsList = null; + ParticipantsList? participantsList = null; AddStep("create new list", () => Child = participantsList = new ParticipantsList { @@ -378,7 +376,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Size = new Vector2(380, 0.7f) }); - AddUntilStep("wait for list to load", () => participantsList.IsLoaded); + AddUntilStep("wait for list to load", () => participantsList?.IsLoaded == true); } private void checkProgressBarVisibility(bool visible) => diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs index 8ab8276b9c..10d9a5664e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Overlays.BeatmapSet; using System.Collections.Specialized; using System.Linq; @@ -29,7 +27,7 @@ namespace osu.Game.Tests.Visual.Online LeaderboardModSelector modSelector; FillFlowContainer selectedMods; - var ruleset = new Bindable(); + var ruleset = new Bindable(); Add(selectedMods = new FillFlowContainer { From b5c703b62cb409adb2dad3adba5e751af4f8a965 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 17:59:55 +0900 Subject: [PATCH 421/803] Remove unused using statement --- osu.Game/Beatmaps/BeatmapUpdater.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 978c6de35c..20fa0bc7c6 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -10,7 +10,6 @@ using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Rulesets.Objects; -using Realms; namespace osu.Game.Beatmaps { From ac216d94a81db5e896b13cebf997b0d00d50c986 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 18:15:15 +0900 Subject: [PATCH 422/803] Fix transaction not being disposed --- osu.Game.Tests/Database/RealmLiveTests.cs | 18 +++++++++++++ osu.Game/Database/RealmExtensions.cs | 32 ++++++++++++++++------- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index d15e038723..aec8c0b1e1 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -59,6 +59,24 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestFailedWritePerformsRollback() + { + RunTestWithRealm((realm, _) => + { + Assert.Throws(() => + { + realm.Write(r => + { + r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())); + throw new InvalidOperationException(); + }); + }); + + Assert.That(realm.Run(r => r.All()), Is.Empty); + }); + } + [Test] public void TestNestedWriteCalls() { diff --git a/osu.Game/Database/RealmExtensions.cs b/osu.Game/Database/RealmExtensions.cs index 2cd81b6af1..13c4defb83 100644 --- a/osu.Game/Database/RealmExtensions.cs +++ b/osu.Game/Database/RealmExtensions.cs @@ -20,12 +20,19 @@ namespace osu.Game.Database { Transaction? transaction = null; - if (!realm.IsInTransaction) - transaction = realm.BeginWrite(); + try + { + if (!realm.IsInTransaction) + transaction = realm.BeginWrite(); - function(realm); + function(realm); - transaction?.Commit(); + transaction?.Commit(); + } + finally + { + transaction?.Dispose(); + } } /// @@ -40,14 +47,21 @@ namespace osu.Game.Database { Transaction? transaction = null; - if (!realm.IsInTransaction) - transaction = realm.BeginWrite(); + try + { + if (!realm.IsInTransaction) + transaction = realm.BeginWrite(); - var result = function(realm); + var result = function(realm); - transaction?.Commit(); + transaction?.Commit(); - return result; + return result; + } + finally + { + transaction?.Dispose(); + } } /// From bf10f2db2ed2148ba47e6652c467e4c341a6d654 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 18:19:01 +0900 Subject: [PATCH 423/803] Add test coverage of nested rollback for good measure --- osu.Game.Tests/Database/RealmLiveTests.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index aec8c0b1e1..3615cebe6a 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -77,6 +77,27 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestFailedNestedWritePerformsRollback() + { + RunTestWithRealm((realm, _) => + { + Assert.Throws(() => + { + realm.Write(r => + { + realm.Write(_ => + { + r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())); + throw new InvalidOperationException(); + }); + }); + }); + + Assert.That(realm.Run(r => r.All()), Is.Empty); + }); + } + [Test] public void TestNestedWriteCalls() { From d88fd8a5b08201ffd44cd1349302bb850b69789c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 18:26:04 +0900 Subject: [PATCH 424/803] Allow searching for "skins" to find current skin setting --- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 741b6b5815..d23ef7e3e7 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -62,7 +62,8 @@ namespace osu.Game.Overlays.Settings.Sections { skinDropdown = new SkinSettingsDropdown { - LabelText = SkinSettingsStrings.CurrentSkin + LabelText = SkinSettingsStrings.CurrentSkin, + Keywords = new[] { @"skins" } }, new SettingsButton { From cf1da1dd18bc7d9e3824acae4c94b53f45eebd4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 22:38:54 +0900 Subject: [PATCH 425/803] Fix skins potentially being duplicated on batch import Resolves https://github.com/ppy/osu/discussions/19024#discussioncomment-3099200 --- osu.Game/Database/RealmArchiveModelImporter.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 76f6db1384..ce3d0652b3 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -258,15 +258,13 @@ namespace osu.Game.Database { cancellationToken.ThrowIfCancellationRequested(); - bool checkedExisting = false; - TModel? existing = null; + TModel? existing; if (batchImport && archive != null) { // this is a fast bail condition to improve large import performance. item.Hash = computeHashFast(archive); - checkedExisting = true; existing = CheckForExisting(item, realm); if (existing != null) @@ -311,8 +309,12 @@ namespace osu.Game.Database // TODO: we may want to run this outside of the transaction. Populate(item, archive, realm, cancellationToken); - if (!checkedExisting) - existing = CheckForExisting(item, realm); + // Populate() may have adjusted file content (see SkinImporter.updateSkinIniMetadata), so regardless of whether a fast check was done earlier, let's + // check for existing items a second time. + // + // If this is ever a performance issue, the fast-check hash can be compared and trigger a skip of this second check if it still matches. + // I don't think it is a huge deal doing a second indexed check, though. + existing = CheckForExisting(item, realm); if (existing != null) { From cd4755fbd9151bde922444827d63550cdfe7b9bd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 7 Jul 2022 18:06:32 +0300 Subject: [PATCH 426/803] Add test coverage for batch-import path --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 8b7fcae1a9..c3c10215a5 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -83,20 +83,20 @@ namespace osu.Game.Tests.Skins.IO #region Cases where imports should match existing [Test] - public Task TestImportTwiceWithSameMetadataAndFilename() => runSkinTest(async osu => + public Task TestImportTwiceWithSameMetadataAndFilename([Values] bool batchImport) => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk")); - var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk"), batchImport); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk"), batchImport); assertImportedOnce(import1, import2); }); [Test] - public Task TestImportTwiceWithNoMetadataSameDownloadFilename() => runSkinTest(async osu => + public Task TestImportTwiceWithNoMetadataSameDownloadFilename([Values] bool batchImport) => runSkinTest(async osu => { // if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety. - var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk")); - var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk"), batchImport); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk"), batchImport); assertImportedOnce(import1, import2); }); @@ -134,10 +134,10 @@ namespace osu.Game.Tests.Skins.IO }); [Test] - public Task TestSameMetadataNameSameFolderName() => runSkinTest(async osu => + public Task TestSameMetadataNameSameFolderName([Values] bool batchImport) => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1")); - var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1"), batchImport); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1"), batchImport); assertImportedOnce(import1, import2); assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", osu); @@ -357,10 +357,10 @@ namespace osu.Game.Tests.Skins.IO } } - private async Task> loadSkinIntoOsu(OsuGameBase osu, ImportTask import) + private async Task> loadSkinIntoOsu(OsuGameBase osu, ImportTask import, bool batchImport = false) { var skinManager = osu.Dependencies.Get(); - return await skinManager.Import(import); + return await skinManager.Import(import, batchImport); } } } From f500d5ade6d613376502ade4bee55fa179301092 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 01:06:39 +0900 Subject: [PATCH 427/803] Simplify error output when hub cannot connect Full call stack is useless in these cases. Before: ```csharp [network] 2022-07-07 16:05:31 [verbose]: OnlineMetadataClient connection error: System.Net.Http.HttpRequestException: Response status code does not indicate success: 403 (Forbidden). [network] 2022-07-07 16:05:31 [verbose]: at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode() [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.NegotiateAsync(Uri url, HttpClient httpClient, ILogger logger, CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.GetNegotiationResponseAsync(Uri uri, CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.SelectAndStartTransport(TransferFormat transferFormat, CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.StartAsyncCore(TransferFormat transferFormat, CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.StartAsync(TransferFormat transferFormat, CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionFactory.ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionFactory.ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncCore(CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncInner(CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsync(CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at osu.Game.Online.HubClientConnector.connect() in /Users/dean/Projects/osu/osu.Game/Online/HubClientConnector.cs:line 119 ``` After: ```csharp [network] 2022-07-07 16:06:59 [verbose]: OnlineMetadataClient connecting... [network] 2022-07-07 16:06:59 [verbose]: OnlineMetadataClient connect attempt failed: Response status code does not indicate success: 403 (Forbidden). ``` --- osu.Game/Online/HubClientConnector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index 61e9eaa8c0..01f0f3a902 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -144,7 +144,7 @@ namespace osu.Game.Online /// private async Task handleErrorAndDelay(Exception exception, CancellationToken cancellationToken) { - Logger.Log($"{clientName} connection error: {exception}", LoggingTarget.Network); + Logger.Log($"{clientName} connect attempt failed: {exception.Message}", LoggingTarget.Network); await Task.Delay(5000, cancellationToken).ConfigureAwait(false); } From b83073c2e91b482a035705faf4d7d397330215e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 01:37:43 +0900 Subject: [PATCH 428/803] Fix `SeasonalBackgroundLoader` triggering a background reload when not providing backgrounds --- osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index f2caf10e91..bfea3a722c 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -41,7 +41,11 @@ namespace osu.Game.Graphics.Backgrounds seasonalBackgroundMode.BindValueChanged(_ => SeasonalBackgroundChanged?.Invoke()); seasonalBackgrounds = sessionStatics.GetBindable(Static.SeasonalBackgrounds); - seasonalBackgrounds.BindValueChanged(_ => SeasonalBackgroundChanged?.Invoke()); + seasonalBackgrounds.BindValueChanged(response => + { + if (response.NewValue?.Backgrounds?.Count > 0) + SeasonalBackgroundChanged?.Invoke(); + }); apiState.BindTo(api.State); apiState.BindValueChanged(fetchSeasonalBackgrounds, true); From 789904ccd1cddc31fcf650f3793248f3533520c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 01:39:09 +0900 Subject: [PATCH 429/803] Avoid reloading background unnecessariyl when not yet loaded --- .../Backgrounds/BackgroundScreenDefault.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 95bcb2ab29..6f133bcb67 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -7,6 +7,7 @@ using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Logging; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -26,7 +27,7 @@ namespace osu.Game.Screens.Backgrounds private const int background_count = 7; private IBindable user; private Bindable skin; - private Bindable mode; + private Bindable source; private Bindable introSequence; private readonly SeasonalBackgroundLoader seasonalBackgroundLoader = new SeasonalBackgroundLoader(); @@ -45,14 +46,14 @@ namespace osu.Game.Screens.Backgrounds { user = api.LocalUser.GetBoundCopy(); skin = skinManager.CurrentSkin.GetBoundCopy(); - mode = config.GetBindable(OsuSetting.MenuBackgroundSource); + source = config.GetBindable(OsuSetting.MenuBackgroundSource); introSequence = config.GetBindable(OsuSetting.IntroSequence); AddInternal(seasonalBackgroundLoader); user.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); skin.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); - mode.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); + source.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); beatmap.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); introSequence.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Scheduler.AddOnce(loadNextIfRequired); @@ -62,7 +63,13 @@ namespace osu.Game.Screens.Backgrounds Next(); // helper function required for AddOnce usage. - void loadNextIfRequired() => Next(); + void loadNextIfRequired() + { + if (!IsLoaded) + return; + + Next(); + } } private ScheduledDelegate nextTask; @@ -80,6 +87,8 @@ namespace osu.Game.Screens.Backgrounds if (nextBackground == background) return false; + Logger.Log("🌅 Background change queued"); + cancellationTokenSource?.Cancel(); cancellationTokenSource = new CancellationTokenSource(); @@ -108,12 +117,12 @@ namespace osu.Game.Screens.Backgrounds if (newBackground == null && user.Value?.IsSupporter == true) { - switch (mode.Value) + switch (source.Value) { case BackgroundSource.Beatmap: case BackgroundSource.BeatmapWithStoryboard: { - if (mode.Value == BackgroundSource.BeatmapWithStoryboard && AllowStoryboardBackground) + if (source.Value == BackgroundSource.BeatmapWithStoryboard && AllowStoryboardBackground) newBackground = new BeatmapBackgroundWithStoryboard(beatmap.Value, getBackgroundTextureName()); newBackground ??= new BeatmapBackground(beatmap.Value, getBackgroundTextureName()); From 216150b52dab2cdf04df29fbf087221e144c64c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 02:00:02 +0900 Subject: [PATCH 430/803] Avoid always loading new background at `MainMenu` This was meant to be an optimisation to allow the background to load while the intro is playing, but as the current default intro loads a background itself, this was rarely the case and also counter-productive as it would bypass the equality check and start a second load sequence. --- osu.Game/Screens/Menu/MainMenu.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index ba63902b46..066a37055c 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -65,9 +65,7 @@ namespace osu.Game.Screens.Menu [Resolved(canBeNull: true)] private IDialogOverlay dialogOverlay { get; set; } - private BackgroundScreenDefault background; - - protected override BackgroundScreen CreateBackground() => background; + protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(); protected override bool PlayExitSound => false; @@ -148,7 +146,6 @@ namespace osu.Game.Screens.Menu Buttons.OnSettings = () => settings?.ToggleVisibility(); Buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility(); - LoadComponentAsync(background = new BackgroundScreenDefault()); preloadSongSelect(); } From 15d070668d6401a166abcac63750268c0ea4a8e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 02:01:51 +0900 Subject: [PATCH 431/803] Move intro screen background to base implementation and use colour fading --- osu.Game/Screens/Menu/IntroScreen.cs | 20 ++++++++++++++-- osu.Game/Screens/Menu/IntroTriangles.cs | 32 ++++++------------------- osu.Game/Screens/Menu/IntroWelcome.cs | 19 ++------------- 3 files changed, 27 insertions(+), 44 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index c81195bbd3..e4cf26f2ce 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -88,6 +88,11 @@ namespace osu.Game.Screens.Menu /// protected bool UsingThemedIntro { get; private set; } + protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(false) + { + Colour = Color4.Black + }; + protected IntroScreen([CanBeNull] Func createNextScreen = null) { this.createNextScreen = createNextScreen; @@ -201,6 +206,8 @@ namespace osu.Game.Screens.Menu { this.FadeIn(300); + ApplyToBackground(b => b.FadeColour(Color4.Black)); + double fadeOutTime = exit_delay; var track = musicController.CurrentTrack; @@ -243,13 +250,22 @@ namespace osu.Game.Screens.Menu base.OnResuming(e); } + private bool backgroundFaded; + + protected void FadeInBackground(float fadeInTime) + { + backgroundFaded = true; + ApplyToBackground(b => b.FadeColour(Color4.White, fadeInTime)); + } + public override void OnSuspending(ScreenTransitionEvent e) { base.OnSuspending(e); initialBeatmap = null; - } - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); + if (!backgroundFaded) + FadeInBackground(200); + } protected virtual void StartTrack() { diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index ad098ae8df..7a5d970ef6 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -8,19 +8,18 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; using osu.Framework.Logging; -using osu.Framework.Utils; +using osu.Framework.Screens; using osu.Framework.Timing; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; -using osu.Game.Screens.Backgrounds; using osuTK; using osuTK.Graphics; @@ -32,16 +31,9 @@ namespace osu.Game.Screens.Menu protected override string BeatmapFile => "triangles.osz"; - protected override BackgroundScreen CreateBackground() => background = new BackgroundScreenDefault(false) - { - Alpha = 0, - }; - [Resolved] private AudioManager audio { get; set; } - private BackgroundScreenDefault background; - private Sample welcome; private DecoupleableInterpolatingFramedClock decoupledClock; @@ -75,7 +67,7 @@ namespace osu.Game.Screens.Menu if (UsingThemedIntro) decoupledClock.ChangeSource(Track); - LoadComponentAsync(intro = new TrianglesIntroSequence(logo, background) + LoadComponentAsync(intro = new TrianglesIntroSequence(logo, () => FadeInBackground(0)) { RelativeSizeAxes = Axes.Both, Clock = decoupledClock, @@ -95,19 +87,10 @@ namespace osu.Game.Screens.Menu { base.OnSuspending(e); - // ensure the background is shown, even if the TriangleIntroSequence failed to do so. - background.ApplyToBackground(b => b.Show()); - // important as there is a clock attached to a track which will likely be disposed before returning to this screen. intro.Expire(); } - public override void OnResuming(ScreenTransitionEvent e) - { - base.OnResuming(e); - background.FadeOut(100); - } - protected override void StartTrack() { decoupledClock.Start(); @@ -116,7 +99,7 @@ namespace osu.Game.Screens.Menu private class TrianglesIntroSequence : CompositeDrawable { private readonly OsuLogo logo; - private readonly BackgroundScreenDefault background; + private readonly Action showBackgroundAction; private OsuSpriteText welcomeText; private RulesetFlow rulesets; @@ -128,10 +111,10 @@ namespace osu.Game.Screens.Menu public Action LoadMenu; - public TrianglesIntroSequence(OsuLogo logo, BackgroundScreenDefault background) + public TrianglesIntroSequence(OsuLogo logo, Action showBackgroundAction) { this.logo = logo; - this.background = background; + this.showBackgroundAction = showBackgroundAction; } [Resolved] @@ -205,7 +188,6 @@ namespace osu.Game.Screens.Menu rulesets.Hide(); lazerLogo.Hide(); - background.ApplyToBackground(b => b.Hide()); using (BeginAbsoluteSequence(0)) { @@ -267,7 +249,7 @@ namespace osu.Game.Screens.Menu logo.FadeIn(); - background.ApplyToBackground(b => b.Show()); + showBackgroundAction(); game.Add(new GameWideFlash()); diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 031c8d7902..9e56a3a0b7 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -5,11 +5,9 @@ using System; using JetBrains.Annotations; -using osuTK; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -17,8 +15,8 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Online.API; -using osu.Game.Screens.Backgrounds; using osu.Game.Skinning; +using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Menu @@ -35,13 +33,6 @@ namespace osu.Game.Screens.Menu private ISample pianoReverb; protected override string SeeyaSampleName => "Intro/Welcome/seeya"; - protected override BackgroundScreen CreateBackground() => background = new BackgroundScreenDefault(false) - { - Alpha = 0, - }; - - private BackgroundScreenDefault background; - public IntroWelcome([CanBeNull] Func createNextScreen = null) : base(createNextScreen) { @@ -100,7 +91,7 @@ namespace osu.Game.Screens.Menu logo.ScaleTo(1); logo.FadeIn(fade_in_time); - background.FadeIn(fade_in_time); + FadeInBackground(fade_in_time); LoadMenu(); }, delay_step_two); @@ -108,12 +99,6 @@ namespace osu.Game.Screens.Menu } } - public override void OnResuming(ScreenTransitionEvent e) - { - base.OnResuming(e); - background.FadeOut(100); - } - private class WelcomeIntroSequence : Container { private Drawable welcomeText; From c53dd4a70315775e603f6806322ffa7e9346c07e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 02:31:33 +0900 Subject: [PATCH 432/803] Fix editor saving not updating `BeatmapSetInfo`'s hash --- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 2 ++ osu.Game/Beatmaps/BeatmapManager.cs | 2 ++ osu.Game/Database/RealmArchiveModelImporter.cs | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index bcf02cd814..8e325838ff 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -40,6 +40,8 @@ namespace osu.Game.Tests.Visual.Editing SaveEditor(); + AddAssert("Hash updated", () => !string.IsNullOrEmpty(EditorBeatmap.BeatmapInfo.BeatmapSet?.Hash)); + AddAssert("Beatmap has correct metadata", () => EditorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && EditorBeatmap.BeatmapInfo.Metadata.Title == "title"); AddAssert("Beatmap has correct author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username == "author"); AddAssert("Beatmap has correct difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "difficulty"); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b1acf78ec6..30456afd2f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -316,6 +316,8 @@ namespace osu.Game.Beatmaps AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo)); + setInfo.Hash = beatmapImporter.ComputeHash(setInfo); + Realm.Write(r => { var liveBeatmapSet = r.Find(setInfo.ID); diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 76f6db1384..27cd565d90 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -386,7 +386,7 @@ namespace osu.Game.Database /// /// In the case of no matching files, a hash will be generated from the passed archive's . /// - protected string ComputeHash(TModel item) + public string ComputeHash(TModel item) { // for now, concatenate all hashable files in the set to create a unique hash. MemoryStream hashable = new MemoryStream(); From 07a08d28c635110b9cc25c1c4ed253c3dfa49e10 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 7 Jul 2022 23:31:04 +0300 Subject: [PATCH 433/803] Rename parameter and default to 0 --- osu.Game/Screens/Menu/IntroScreen.cs | 4 ++-- osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index e4cf26f2ce..25d577afb5 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -252,10 +252,10 @@ namespace osu.Game.Screens.Menu private bool backgroundFaded; - protected void FadeInBackground(float fadeInTime) + protected void FadeInBackground(float duration = 0) { + ApplyToBackground(b => b.FadeColour(Color4.White, duration)); backgroundFaded = true; - ApplyToBackground(b => b.FadeColour(Color4.White, fadeInTime)); } public override void OnSuspending(ScreenTransitionEvent e) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 7a5d970ef6..3cdf51a87c 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Menu if (UsingThemedIntro) decoupledClock.ChangeSource(Track); - LoadComponentAsync(intro = new TrianglesIntroSequence(logo, () => FadeInBackground(0)) + LoadComponentAsync(intro = new TrianglesIntroSequence(logo, () => FadeInBackground()) { RelativeSizeAxes = Axes.Both, Clock = decoupledClock, From a94fb62be380cdfc2e6af8475f1baf4ee881c482 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Jul 2022 01:39:43 +0300 Subject: [PATCH 434/803] Split collection toggle menu item to own class --- .../Collections/CollectionToggleMenuItem.cs | 20 +++++++++++++++++++ .../Carousel/DrawableCarouselBeatmap.cs | 16 +-------------- 2 files changed, 21 insertions(+), 15 deletions(-) create mode 100644 osu.Game/Collections/CollectionToggleMenuItem.cs diff --git a/osu.Game/Collections/CollectionToggleMenuItem.cs b/osu.Game/Collections/CollectionToggleMenuItem.cs new file mode 100644 index 0000000000..34f749c9f8 --- /dev/null +++ b/osu.Game/Collections/CollectionToggleMenuItem.cs @@ -0,0 +1,20 @@ +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Collections +{ + public class CollectionToggleMenuItem : ToggleMenuItem + { + public CollectionToggleMenuItem(BeatmapCollection collection, IBeatmapInfo beatmap) + : base(collection.Name.Value, MenuItemType.Standard, s => + { + if (s) + collection.BeatmapHashes.Add(beatmap.MD5Hash); + else + collection.BeatmapHashes.Remove(beatmap.MD5Hash); + }) + { + State.Value = collection.BeatmapHashes.Contains(beatmap.MD5Hash); + } + } +} diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 1b3cab20e8..a6532ee145 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -244,7 +244,7 @@ namespace osu.Game.Screens.Select.Carousel if (collectionManager != null) { - var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); + var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmapInfo)).Cast().ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); @@ -258,20 +258,6 @@ namespace osu.Game.Screens.Select.Carousel } } - private MenuItem createCollectionMenuItem(BeatmapCollection collection) - { - return new ToggleMenuItem(collection.Name.Value, MenuItemType.Standard, s => - { - if (s) - collection.BeatmapHashes.Add(beatmapInfo.MD5Hash); - else - collection.BeatmapHashes.Remove(beatmapInfo.MD5Hash); - }) - { - State = { Value = collection.BeatmapHashes.Contains(beatmapInfo.MD5Hash) } - }; - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 1d0f2e359a5925b4e672202266a99ff32f4e265d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Jul 2022 01:40:11 +0300 Subject: [PATCH 435/803] Add collection context menu to room playlist items --- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index a3a176477e..455e1f3481 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -15,11 +16,13 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; 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.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Collections; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -38,7 +41,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay { - public class DrawableRoomPlaylistItem : OsuRearrangeableListItem + public class DrawableRoomPlaylistItem : OsuRearrangeableListItem, IHasContextMenu { public const float HEIGHT = 50; @@ -90,6 +93,8 @@ namespace osu.Game.Screens.OnlinePlay private PanelBackground panelBackground; private FillFlowContainer mainFillFlow; + private BeatmapDownloadTracker downloadTracker; + [Resolved] private RulesetStore rulesets { get; set; } @@ -102,6 +107,12 @@ namespace osu.Game.Screens.OnlinePlay [Resolved] private BeatmapLookupCache beatmapLookupCache { get; set; } + [Resolved(CanBeNull = true)] + private CollectionManager collectionManager { get; set; } + + [Resolved(CanBeNull = true)] + private ManageCollectionsDialog manageCollectionsDialog { get; set; } + protected override bool ShouldBeConsideredForInput(Drawable child) => AllowReordering || AllowDeletion || !AllowSelection || SelectedItem.Value == Model; public DrawableRoomPlaylistItem(PlaylistItem item) @@ -304,6 +315,15 @@ namespace osu.Game.Screens.OnlinePlay difficultyIconContainer.FadeInFromZero(500, Easing.OutQuint); mainFillFlow.FadeInFromZero(500, Easing.OutQuint); + + downloadTracker?.RemoveAndDisposeImmediately(); + + if (beatmap != null) + { + Debug.Assert(beatmap.BeatmapSet != null); + downloadTracker = new BeatmapDownloadTracker(beatmap.BeatmapSet); + AddInternal(downloadTracker); + } } protected override Drawable CreateContent() @@ -433,7 +453,7 @@ namespace osu.Game.Screens.OnlinePlay } } }, - } + }, }; } @@ -470,6 +490,30 @@ namespace osu.Game.Screens.OnlinePlay return true; } + public MenuItem[] ContextMenuItems + { + get + { + List items = new List(); + + if (beatmap != null && collectionManager != null) + { + if (downloadTracker.State.Value == DownloadState.LocallyAvailable) + { + var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + } + else + items.Add(new OsuMenuItem("Download to add to collection") { Action = { Disabled = true } }); + } + + return items.ToArray(); + } + } + public class PlaylistEditButton : GrayButton { public PlaylistEditButton() From 7b08501eafb31c76cec7842a4f87e35f28c81cee Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Jul 2022 01:42:11 +0300 Subject: [PATCH 436/803] Cover online-play room screens with context menu containers --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 227 ++++++++-------- .../Playlists/PlaylistsRoomSubScreen.cs | 247 +++++++++--------- 2 files changed, 242 insertions(+), 232 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 869548c948..4eb16a854b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -15,6 +15,7 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Graphics.Cursor; using osu.Game.Online; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -81,134 +82,138 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Horizontal = 5, Vertical = 10 }, - Child = new GridContainer + Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + Child = new GridContainer { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 10), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 10), - new Dimension(), - }, - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] { - // Participants column - new GridContainer + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] + // Participants column + new GridContainer { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] { new ParticipantsListHeader() }, - new Drawable[] + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - new ParticipantsList - { - RelativeSizeAxes = Axes.Both - }, - } - } - }, - // Spacer - null, - // Beatmap column - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] { new OverlinedHeader("Beatmap") }, - new Drawable[] - { - addItemButton = new AddItemButton - { - RelativeSizeAxes = Axes.X, - Height = 40, - Text = "Add item", - Action = () => OpenSongSelection() - }, + new Dimension(GridSizeMode.AutoSize) }, - null, - new Drawable[] + Content = new[] { - new MultiplayerPlaylist + new Drawable[] { new ParticipantsListHeader() }, + new Drawable[] { - RelativeSizeAxes = Axes.Both, - RequestEdit = item => OpenSongSelection(item.ID) - } - }, - new[] - { - UserModsSection = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 10 }, - Alpha = 0, - Children = new Drawable[] + new ParticipantsList { - new OverlinedHeader("Extra mods"), - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Children = new Drawable[] - { - new UserModSelectButton - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Width = 90, - Text = "Select", - Action = ShowUserModSelect, - }, - new ModDisplay - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Current = UserMods, - Scale = new Vector2(0.8f), - }, - } - }, + RelativeSizeAxes = Axes.Both + }, + } + } + }, + // Spacer + null, + // Beatmap column + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { new OverlinedHeader("Beatmap") }, + new Drawable[] + { + addItemButton = new AddItemButton + { + RelativeSizeAxes = Axes.X, + Height = 40, + Text = "Add item", + Action = () => OpenSongSelection() + }, + }, + null, + new Drawable[] + { + new MultiplayerPlaylist + { + RelativeSizeAxes = Axes.Both, + RequestEdit = item => OpenSongSelection(item.ID) } }, + new[] + { + UserModsSection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 10 }, + Alpha = 0, + Children = new Drawable[] + { + new OverlinedHeader("Extra mods"), + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new UserModSelectButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 90, + Text = "Select", + Action = ShowUserModSelect, + }, + new ModDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Current = UserMods, + Scale = new Vector2(0.8f), + }, + } + }, + } + }, + }, }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, 5), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + } }, - RowDimensions = new[] + // Spacer + null, + // Main right column + new GridContainer { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Absolute, 5), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - } - }, - // Spacer - null, - // Main right column - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] { new OverlinedHeader("Chat") }, - new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } } + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { new OverlinedHeader("Chat") }, + new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } } + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + } }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - } - }, + } } } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 8a9c4db6ad..228ecd4bf3 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Screens; +using osu.Game.Graphics.Cursor; using osu.Game.Input; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; @@ -75,151 +76,155 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Horizontal = 5, Vertical = 10 }, - Child = new GridContainer + Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + Child = new GridContainer { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 10), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 10), - new Dimension(), - }, - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] { - // Playlist items column - new Container + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 5 }, - Child = new GridContainer + // Playlist items column + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 5 }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { new OverlinedPlaylistHeader(), }, + new Drawable[] + { + new DrawableRoomPlaylist + { + RelativeSizeAxes = Axes.Both, + Items = { BindTarget = Room.Playlist }, + SelectedItem = { BindTarget = SelectedItem }, + AllowSelection = true, + AllowShowingResults = true, + RequestResults = item => + { + Debug.Assert(RoomId.Value != null); + ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false)); + } + } + }, + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + } + } + }, + // Spacer + null, + // Middle column (mods and leaderboard) + new GridContainer { RelativeSizeAxes = Axes.Both, Content = new[] { - new Drawable[] { new OverlinedPlaylistHeader(), }, + new[] + { + UserModsSection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Margin = new MarginPadding { Bottom = 10 }, + Children = new Drawable[] + { + new OverlinedHeader("Extra mods"), + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new UserModSelectButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 90, + Text = "Select", + Action = ShowUserModSelect, + }, + new ModDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Current = UserMods, + Scale = new Vector2(0.8f), + }, + } + } + } + }, + }, new Drawable[] { - new DrawableRoomPlaylist + progressSection = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Items = { BindTarget = Room.Playlist }, - SelectedItem = { BindTarget = SelectedItem }, - AllowSelection = true, - AllowShowingResults = true, - RequestResults = item => + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Margin = new MarginPadding { Bottom = 10 }, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Debug.Assert(RoomId.Value != null); - ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false)); + new OverlinedHeader("Progress"), + new RoomLocalUserInfo(), } - } + }, }, + new Drawable[] + { + new OverlinedHeader("Leaderboard") + }, + new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, }, + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + } + }, + // Spacer + null, + // Main right column + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { new OverlinedHeader("Chat") }, + new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } } }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(), } - } - }, - // Spacer - null, - // Middle column (mods and leaderboard) - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new[] - { - UserModsSection = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0, - Margin = new MarginPadding { Bottom = 10 }, - Children = new Drawable[] - { - new OverlinedHeader("Extra mods"), - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Children = new Drawable[] - { - new UserModSelectButton - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Width = 90, - Text = "Select", - Action = ShowUserModSelect, - }, - new ModDisplay - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Current = UserMods, - Scale = new Vector2(0.8f), - }, - } - } - } - }, - }, - new Drawable[] - { - progressSection = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0, - Margin = new MarginPadding { Bottom = 10 }, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new OverlinedHeader("Progress"), - new RoomLocalUserInfo(), - } - }, - }, - new Drawable[] - { - new OverlinedHeader("Leaderboard") - }, - new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, }, }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - } - }, - // Spacer - null, - // Main right column - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] { new OverlinedHeader("Chat") }, - new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } } - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - } }, }, - }, + } } }; From 67fa15f231d543b342bdd8be22c53b85ce8c8b1a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Jul 2022 01:42:55 +0300 Subject: [PATCH 437/803] Remove no longer required context menu container in `ParticipantsList` --- .../TestSceneMultiplayerParticipantsList.cs | 15 ++++++++++----- .../Participants/ParticipantsList.cs | 19 +++++++------------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 7db18d1127..7a64e75644 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; @@ -370,12 +371,16 @@ namespace osu.Game.Tests.Visual.Multiplayer { ParticipantsList participantsList = null; - AddStep("create new list", () => Child = participantsList = new ParticipantsList + AddStep("create new list", () => Child = new OsuContextMenuContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Size = new Vector2(380, 0.7f) + RelativeSizeAxes = Axes.Both, + 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); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs index cda86c74bf..7c93c6084e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Cursor; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants @@ -24,20 +23,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants [BackgroundDependencyLoader] private void load() { - InternalChild = new OsuContextMenuContainer + InternalChild = new OsuScrollContainer { RelativeSizeAxes = Axes.Both, - Child = new OsuScrollContainer + ScrollbarVisible = false, + Child = panels = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = panels = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 2) - } + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 2) } }; } From 63a06afab220c116928299a18582e4117c7be3e2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Jul 2022 01:59:27 +0300 Subject: [PATCH 438/803] Add missing license header --- osu.Game/Collections/CollectionToggleMenuItem.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Collections/CollectionToggleMenuItem.cs b/osu.Game/Collections/CollectionToggleMenuItem.cs index 34f749c9f8..fbc935d19a 100644 --- a/osu.Game/Collections/CollectionToggleMenuItem.cs +++ b/osu.Game/Collections/CollectionToggleMenuItem.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; From 1e159eb328ffa3b1aac1dba9217d5c722e344052 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 10:43:50 +0900 Subject: [PATCH 439/803] Add back fade to black duration Co-authored-by: Salman Ahmed --- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 25d577afb5..c1621ce78f 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -206,7 +206,7 @@ namespace osu.Game.Screens.Menu { this.FadeIn(300); - ApplyToBackground(b => b.FadeColour(Color4.Black)); + ApplyToBackground(b => b.FadeColour(Color4.Black, 100)); double fadeOutTime = exit_delay; From 89f1c75f7ad0cdea5e23240eeb4580c1cc0a066b Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Thu, 7 Jul 2022 21:57:18 -0500 Subject: [PATCH 440/803] Update mod icon colors --- osu.Game/Rulesets/UI/ModIcon.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 0f21a497b0..f32c298571 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -127,33 +127,33 @@ namespace osu.Game.Rulesets.UI { default: case ModType.DifficultyIncrease: - backgroundColour = colours.Yellow; - highlightedColour = colours.YellowLight; + backgroundColour = colours.Red1; + highlightedColour = colours.Red0; break; case ModType.DifficultyReduction: - backgroundColour = colours.Green; - highlightedColour = colours.GreenLight; + backgroundColour = colours.Lime1; + highlightedColour = colours.Lime0; break; case ModType.Automation: - backgroundColour = colours.Blue; - highlightedColour = colours.BlueLight; + backgroundColour = colours.Blue1; + highlightedColour = colours.Blue0; break; case ModType.Conversion: - backgroundColour = colours.Purple; - highlightedColour = colours.PurpleLight; + backgroundColour = colours.Purple1; + highlightedColour = colours.Purple0; break; case ModType.Fun: - backgroundColour = colours.Pink; - highlightedColour = colours.PinkLight; + backgroundColour = colours.Pink1; + highlightedColour = colours.Pink0; break; case ModType.System: - backgroundColour = colours.Gray6; - highlightedColour = colours.Gray7; + backgroundColour = colours.Gray7; + highlightedColour = colours.Gray8; modIcon.Colour = colours.Yellow; break; } From 84dcd042f42267acae6271996235c76eda670521 Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Thu, 7 Jul 2022 20:30:31 -0700 Subject: [PATCH 441/803] Protect duration calculations against unstable fps --- .../Skinning/Default/DefaultFollowCircle.cs | 2 -- .../Skinning/Legacy/LegacyFollowCircle.cs | 8 +++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 7dd45c295d..8bb36f8c39 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -1,13 +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.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Logging; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK.Graphics; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index dc44c86de0..26d3f053ff 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -1,17 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Transforms; -using osu.Framework.Logging; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -67,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy double maxScaleDuration = parentObject.HitStateUpdateTime - Time.Current; double realScaleDuration = scale_duration; - if (tracking.NewValue && maxScaleDuration < realScaleDuration) + if (tracking.NewValue && maxScaleDuration < realScaleDuration && maxScaleDuration >= 0) realScaleDuration = maxScaleDuration; double realFadeDuration = fade_duration * realScaleDuration / fade_duration; @@ -77,6 +73,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void onHitObjectApplied(DrawableHitObject drawableObject) { + ClearTransformsAfter(double.MinValue); + this.ScaleTo(1f) .FadeOut(); } From 26d88aa3263c677c28e92844a8a5d4d8b40ba043 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 8 Jul 2022 14:29:15 +0900 Subject: [PATCH 442/803] Fix intermittent MusicController tests --- osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 14b2593fa7..720e32a242 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -24,10 +24,11 @@ namespace osu.Game.Tests.Visual.Menus public void TestMusicPlayAction() { AddStep("ensure playing something", () => Game.MusicController.EnsurePlayingSomething()); + AddUntilStep("music playing", () => Game.MusicController.IsPlaying); AddStep("toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); - AddAssert("music paused", () => !Game.MusicController.IsPlaying && Game.MusicController.UserPauseRequested); + AddUntilStep("music paused", () => !Game.MusicController.IsPlaying && Game.MusicController.UserPauseRequested); AddStep("toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); - AddAssert("music resumed", () => Game.MusicController.IsPlaying && !Game.MusicController.UserPauseRequested); + AddUntilStep("music resumed", () => Game.MusicController.IsPlaying && !Game.MusicController.UserPauseRequested); } [Test] From 32c77ddf717842f33a143310ce951d9ffbd014da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 15:07:30 +0900 Subject: [PATCH 443/803] Avoid triggering `SeasonalBackgroundChanged` unless actually required --- .../Backgrounds/SeasonalBackgroundLoader.cs | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index bfea3a722c..99af95b5fe 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -38,19 +38,21 @@ namespace osu.Game.Graphics.Backgrounds private void load(OsuConfigManager config, SessionStatics sessionStatics) { seasonalBackgroundMode = config.GetBindable(OsuSetting.SeasonalBackgroundMode); - seasonalBackgroundMode.BindValueChanged(_ => SeasonalBackgroundChanged?.Invoke()); + seasonalBackgroundMode.BindValueChanged(_ => triggerSeasonalBackgroundChanged()); seasonalBackgrounds = sessionStatics.GetBindable(Static.SeasonalBackgrounds); - seasonalBackgrounds.BindValueChanged(response => - { - if (response.NewValue?.Backgrounds?.Count > 0) - SeasonalBackgroundChanged?.Invoke(); - }); + seasonalBackgrounds.BindValueChanged(_ => triggerSeasonalBackgroundChanged()); apiState.BindTo(api.State); apiState.BindValueChanged(fetchSeasonalBackgrounds, true); } + private void triggerSeasonalBackgroundChanged() + { + if (shouldShowSeasonal) + SeasonalBackgroundChanged?.Invoke(); + } + private void fetchSeasonalBackgrounds(ValueChangedEvent stateChanged) { if (seasonalBackgrounds.Value != null || stateChanged.NewValue != APIState.Online) @@ -68,15 +70,10 @@ namespace osu.Game.Graphics.Backgrounds public SeasonalBackground LoadNextBackground() { - if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Never - || (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Sometimes && !isInSeason)) - { + if (!shouldShowSeasonal) return null; - } - var backgrounds = seasonalBackgrounds.Value?.Backgrounds; - if (backgrounds == null || !backgrounds.Any()) - return null; + var backgrounds = seasonalBackgrounds.Value.Backgrounds; current = (current + 1) % backgrounds.Count; string url = backgrounds[current].Url; @@ -84,6 +81,20 @@ namespace osu.Game.Graphics.Backgrounds return new SeasonalBackground(url); } + private bool shouldShowSeasonal + { + get + { + if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Never) + return false; + + if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Sometimes && !isInSeason) + return false; + + return seasonalBackgrounds.Value?.Backgrounds?.Any() == true; + } + } + private bool isInSeason => seasonalBackgrounds.Value != null && DateTimeOffset.Now < seasonalBackgrounds.Value.EndDate; } From eab3eba70e9c97c79f8ed67ee16e2734ef2f1cff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 15:09:16 +0900 Subject: [PATCH 444/803] Move event handlers to `LoadComplete` --- .../Backgrounds/BackgroundScreenDefault.cs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 6f133bcb67..c794c768c6 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -51,25 +51,24 @@ namespace osu.Game.Screens.Backgrounds AddInternal(seasonalBackgroundLoader); - user.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); - skin.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); - source.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); - beatmap.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); - introSequence.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); - seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Scheduler.AddOnce(loadNextIfRequired); - + // Load first background asynchronously as part of BDL load. currentDisplay = RNG.Next(0, background_count); - Next(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + user.ValueChanged += _ => Scheduler.AddOnce(next); + skin.ValueChanged += _ => Scheduler.AddOnce(next); + source.ValueChanged += _ => Scheduler.AddOnce(next); + beatmap.ValueChanged += _ => Scheduler.AddOnce(next); + introSequence.ValueChanged += _ => Scheduler.AddOnce(next); + seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Scheduler.AddOnce(next); // helper function required for AddOnce usage. - void loadNextIfRequired() - { - if (!IsLoaded) - return; - - Next(); - } + void next() => Next(); } private ScheduledDelegate nextTask; From e64b2b1682602020a3bd68f3af3f45c44f95d832 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 15:45:01 +0900 Subject: [PATCH 445/803] Assert that test is run headless when required --- osu.Game/Tests/Visual/PlayerTestScene.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 036ac8a639..5b4d4d5c2c 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Development; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -26,8 +27,12 @@ namespace osu.Game.Tests.Visual protected virtual bool HasCustomSteps => false; /// - /// WARNING: ONLY WORKS IF RUN HEADLESS because reasons. + /// Import the beatmap to the database before starting gameplay. Handy for testing local score storage and the likes. /// + /// + /// Only works under headless operation currently due to realm isolation difficulties. + /// If this is ever needed to change, consideration needs to be given to the fact that BeatmapManager is attached to the global realm. + /// protected virtual bool ImportBeatmapToDatabase => false; protected TestPlayer Player; @@ -77,6 +82,8 @@ namespace osu.Game.Tests.Visual if (ImportBeatmapToDatabase) { + Debug.Assert(DebugUtils.IsNUnitRunning, $@"Importing beatmaps in {nameof(PlayerTestScene)} requires headless environment."); + Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null); var imported = beatmaps.Import(beatmap.BeatmapInfo.BeatmapSet); From 7ced84b7ef1ab6735ef42ab2c9a8f066008b8d8f Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Fri, 8 Jul 2022 03:23:58 -0500 Subject: [PATCH 446/803] Replace switch statement with ForModType In order to make `highlightedColour` dependent on the mod type color, the color is converted to an `osu.Framework.Graphics.Colour4` and calls `Lighten`. --- osu.Game/Rulesets/UI/ModIcon.cs | 38 +++++---------------------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index f32c298571..308122b71c 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -123,40 +123,12 @@ namespace osu.Game.Rulesets.UI modAcronym.FadeOut(); } - switch (value.Type) - { - default: - case ModType.DifficultyIncrease: - backgroundColour = colours.Red1; - highlightedColour = colours.Red0; - break; + Color4 typeColour = colours.ForModType(value.Type); + backgroundColour = typeColour; + highlightedColour = ((Colour4)typeColour).Lighten(.2f); - case ModType.DifficultyReduction: - backgroundColour = colours.Lime1; - highlightedColour = colours.Lime0; - break; - - case ModType.Automation: - backgroundColour = colours.Blue1; - highlightedColour = colours.Blue0; - break; - - case ModType.Conversion: - backgroundColour = colours.Purple1; - highlightedColour = colours.Purple0; - break; - - case ModType.Fun: - backgroundColour = colours.Pink1; - highlightedColour = colours.Pink0; - break; - - case ModType.System: - backgroundColour = colours.Gray7; - highlightedColour = colours.Gray8; - modIcon.Colour = colours.Yellow; - break; - } + if (value.Type == ModType.System) + modIcon.Colour = colours.Yellow; updateColour(); } From e4ebab92c6792253adf14d5aa0dd08b3c6bca44b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 18:36:46 +0900 Subject: [PATCH 447/803] Rename lots of weird variables --- osu.Game/Screens/Play/FailOverlay.cs | 1 + osu.Game/Screens/Play/SaveFailedScoreButton.cs | 15 ++++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index 75b06f859d..2af5102369 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -21,6 +21,7 @@ namespace osu.Game.Screens.Play public class FailOverlay : GameplayMenuOverlay { public Func> SaveReplay; + public override string Header => "failed"; public override string Description => "you're dead, try again?"; diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index 9a30e23ddd..29d2d0df24 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -20,8 +20,8 @@ namespace osu.Game.Screens.Play { public class SaveFailedScoreButton : CompositeDrawable { - public Func> SaveReplay; - private Task saveReplayAsync; + public Func> ImportFailedScore; + private Task saveFailedScoreTask; private ScoreInfo score; private ScheduledDelegate saveScoreDelegate; @@ -31,10 +31,10 @@ namespace osu.Game.Screens.Play private DownloadButton button; private ShakeContainer shakeContainer; - public SaveFailedScoreButton(Func> sr) + public SaveFailedScoreButton(Func> requestImportFailedScore) { Size = new Vector2(50, 30); - SaveReplay = sr; + ImportFailedScore = requestImportFailedScore; } [BackgroundDependencyLoader(true)] @@ -89,17 +89,18 @@ namespace osu.Game.Screens.Play private void saveScore() { State.Value = ImportState.Importing; - saveReplayAsync = Task.Run(SaveReplay); + + saveFailedScoreTask = Task.Run(ImportFailedScore); saveScoreDelegate = new ScheduledDelegate(() => { - if (saveReplayAsync?.IsCompleted != true) + if (saveFailedScoreTask?.IsCompleted != true) // If the asynchronous preparation has not completed, keep repeating this delegate. return; saveScoreDelegate?.Cancel(); - score = saveReplayAsync.GetAwaiter().GetResult(); + score = saveFailedScoreTask.GetAwaiter().GetResult(); State.Value = score != null ? ImportState.Imported : ImportState.Failed; }, Time.Current, 50); From 13e16530a6d90c4fa341a2b3a3b7bec6e055eac4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 18:44:50 +0900 Subject: [PATCH 448/803] Revert most changes to `PlayerTestScene` --- osu.Game/Tests/Visual/PlayerTestScene.cs | 32 ------------------------ 1 file changed, 32 deletions(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 5b4d4d5c2c..a9decbae57 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -4,16 +4,12 @@ #nullable disable using System; -using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Development; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; -using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -26,15 +22,6 @@ namespace osu.Game.Tests.Visual /// protected virtual bool HasCustomSteps => false; - /// - /// Import the beatmap to the database before starting gameplay. Handy for testing local score storage and the likes. - /// - /// - /// Only works under headless operation currently due to realm isolation difficulties. - /// If this is ever needed to change, consideration needs to be given to the fact that BeatmapManager is attached to the global realm. - /// - protected virtual bool ImportBeatmapToDatabase => false; - protected TestPlayer Player; protected OsuConfigManager LocalConfig; @@ -70,9 +57,6 @@ namespace osu.Game.Tests.Visual protected virtual bool Autoplay => false; - [Resolved] - private BeatmapManager beatmaps { get; set; } - protected void LoadPlayer() { var ruleset = CreatePlayerRuleset(); @@ -80,22 +64,6 @@ namespace osu.Game.Tests.Visual var beatmap = CreateBeatmap(ruleset.RulesetInfo); - if (ImportBeatmapToDatabase) - { - Debug.Assert(DebugUtils.IsNUnitRunning, $@"Importing beatmaps in {nameof(PlayerTestScene)} requires headless environment."); - - Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null); - - var imported = beatmaps.Import(beatmap.BeatmapInfo.BeatmapSet); - - Debug.Assert(imported != null); - - beatmap.BeatmapInfo = null; - beatmap.Difficulty = null; - - beatmap.BeatmapInfo = imported.Value.Detach().Beatmaps.First(); - } - Beatmap.Value = CreateWorkingBeatmap(beatmap); SelectedMods.Value = Array.Empty(); From 3c8f06403cbe814f3f356e24f3bf055e2b275807 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 18:59:08 +0900 Subject: [PATCH 449/803] Make test work properly --- .../TestScenePlayerLocalScoreImport.cs | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 61f0483c86..a75d527a69 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -3,22 +3,51 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Extensions; +using osu.Framework.Platform; using osu.Framework.Screens; -using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Gameplay { - [HeadlessTest] // Importing rulesets doesn't work in interactive flows. public class TestScenePlayerLocalScoreImport : PlayerTestScene { - private Ruleset? customRuleset; + private BeatmapManager beatmaps = null!; + private RulesetStore rulesets = null!; - protected override bool ImportBeatmapToDatabase => true; + private BeatmapSetInfo? importedSet; + + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, Scheduler)); + Dependencies.Cache(Realm); + } + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("import beatmap", () => + { + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); + importedSet = beatmaps.GetAllUsableBeatmapSets().First(); + }); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => beatmaps.GetWorkingBeatmap(importedSet?.Beatmaps.First()).Beatmap; + + private Ruleset? customRuleset; protected override Ruleset CreatePlayerRuleset() => customRuleset ?? new OsuRuleset(); @@ -66,7 +95,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); - AddUntilStep("score in database", () => Realm.Run(r => r.All().Count() == 1)); + AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); } } } From ac3cdf103a61bb3dced7b49c6c9f5dd0c357537c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 19:17:51 +0900 Subject: [PATCH 450/803] Fix test not failing against `master` --- .../TestScenePlayerLocalScoreImport.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index a75d527a69..96b7da9c9c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Tests.Resources; @@ -75,15 +76,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestScoreStoredLocallyCustomRuleset() { - Ruleset createCustomRuleset() => new OsuRuleset - { - RulesetInfo = - { - Name = "custom", - ShortName = "custom", - OnlineID = -1 - } - }; + Ruleset createCustomRuleset() => new CustomRuleset(); AddStep("import custom ruleset", () => Realm.Write(r => r.Add(createCustomRuleset().RulesetInfo))); AddStep("set custom ruleset", () => customRuleset = createCustomRuleset()); @@ -97,5 +90,15 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); } + + private class CustomRuleset : OsuRuleset, ILegacyRuleset + { + public override string Description => "custom"; + public override string ShortName => "custom"; + + public new int LegacyID => -1; + + public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); + } } } From a38c6704c2c44ba77d88e1e263ea327082c37274 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 8 Jul 2022 21:31:35 +0900 Subject: [PATCH 451/803] Use ContinueWith, Check is Task empty --- .../Screens/Play/SaveFailedScoreButton.cs | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index 29d2d0df24..98f81863d5 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Threading; using osu.Game.Scoring; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -24,8 +23,6 @@ namespace osu.Game.Screens.Play private Task saveFailedScoreTask; private ScoreInfo score; - private ScheduledDelegate saveScoreDelegate; - protected readonly Bindable State = new Bindable(); private DownloadButton button; @@ -88,24 +85,19 @@ namespace osu.Game.Screens.Play private void saveScore() { + if (saveFailedScoreTask != null) + { + return; + } + State.Value = ImportState.Importing; saveFailedScoreTask = Task.Run(ImportFailedScore); - - saveScoreDelegate = new ScheduledDelegate(() => + saveFailedScoreTask.ContinueWith(s => Schedule(() => { - if (saveFailedScoreTask?.IsCompleted != true) - // If the asynchronous preparation has not completed, keep repeating this delegate. - return; - - saveScoreDelegate?.Cancel(); - - score = saveFailedScoreTask.GetAwaiter().GetResult(); - + score = s.GetAwaiter().GetResult(); State.Value = score != null ? ImportState.Imported : ImportState.Failed; - }, Time.Current, 50); - - Scheduler.Add(saveScoreDelegate); + })); } private void updateTooltip(ValueChangedEvent state) From 91f471ebe0747356b0f0224d703520510d9d035a Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 8 Jul 2022 21:42:34 +0900 Subject: [PATCH 452/803] disabled button instead of shake removed `ShakeContainer` --- osu.Game/Screens/Play/SaveFailedScoreButton.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index 98f81863d5..bbe26ca7da 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Scoring; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osuTK; @@ -26,7 +25,6 @@ namespace osu.Game.Screens.Play protected readonly Bindable State = new Bindable(); private DownloadButton button; - private ShakeContainer shakeContainer; public SaveFailedScoreButton(Func> requestImportFailedScore) { @@ -37,13 +35,9 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader(true)] private void load(OsuGame game) { - InternalChild = shakeContainer = new ShakeContainer + InternalChild = button = new DownloadButton { RelativeSizeAxes = Axes.Both, - Child = button = new DownloadButton - { - RelativeSizeAxes = Axes.Both, - } }; button.Action = () => @@ -55,7 +49,6 @@ namespace osu.Game.Screens.Play break; case ImportState.Importing: - shakeContainer.Shake(); break; default: @@ -106,18 +99,22 @@ namespace osu.Game.Screens.Play { case ImportState.Imported: button.TooltipText = @"Watch replay"; + button.Enabled.Value = true; break; case ImportState.Importing: button.TooltipText = @"Importing score"; + button.Enabled.Value = false; break; case ImportState.Failed: button.TooltipText = @"Import failed, click button to re-import"; + button.Enabled.Value = true; break; default: button.TooltipText = @"Save score"; + button.Enabled.Value = true; break; } } From d2406242ae12c124aaa9e9f0c35c65e7fb90b7c1 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sat, 9 Jul 2022 00:25:11 +0900 Subject: [PATCH 453/803] rename updateTooltip to updateState --- osu.Game/Screens/Play/SaveFailedScoreButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index bbe26ca7da..256228a1cb 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Play break; } }, true); - State.BindValueChanged(updateTooltip, true); + State.BindValueChanged(updateState, true); } private void saveScore() @@ -93,7 +93,7 @@ namespace osu.Game.Screens.Play })); } - private void updateTooltip(ValueChangedEvent state) + private void updateState(ValueChangedEvent state) { switch (state.NewValue) { From a606d545c193d2da6ac187b71fa0d461144d7468 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Fri, 8 Jul 2022 12:00:07 -0400 Subject: [PATCH 454/803] update new usage of CalculatePossibleMovementBounds --- .../Utils/OsuHitObjectGenerationUtils_Reposition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 436d71fda2..a9ae313a31 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -214,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Utils RotateSlider(slider, workingObject.RotationOriginal + MathF.PI - getSliderRotation(slider)); } - possibleMovementBounds = calculatePossibleMovementBounds(slider); + possibleMovementBounds = CalculatePossibleMovementBounds(slider); } var previousPosition = workingObject.PositionModified; From 8c7aabccb074070961b1d84a129277add1126eb2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Jul 2022 19:01:21 +0300 Subject: [PATCH 455/803] Fix custom legacy ID in test scene not overriding base value --- .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 96b7da9c9c..dc0a14ad9b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override string Description => "custom"; public override string ShortName => "custom"; - public new int LegacyID => -1; + int ILegacyRuleset.LegacyID => -1; public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); } From 667b1d795d6a7f9a58b9123ec397c60127a9de3a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Jul 2022 19:02:46 +0300 Subject: [PATCH 456/803] Ensure score has custom ruleset --- .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index dc0a14ad9b..3011515b62 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Beatmaps; @@ -83,6 +84,8 @@ namespace osu.Game.Tests.Visual.Gameplay CreateTest(); + AddAssert("score has custom ruleset", () => Player.Score.ScoreInfo.Ruleset.Equals(customRuleset.AsNonNull().RulesetInfo)); + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); From 66f314915dc3f2ed4a5d47857af5ee133b72d255 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 9 Jul 2022 06:01:22 +0900 Subject: [PATCH 457/803] Fix crash on mobile releases when attempting to read any file --- osu.Game/IO/LineBufferedReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/LineBufferedReader.cs b/osu.Game/IO/LineBufferedReader.cs index 93e554b43d..da1cdba73b 100644 --- a/osu.Game/IO/LineBufferedReader.cs +++ b/osu.Game/IO/LineBufferedReader.cs @@ -19,7 +19,7 @@ namespace osu.Game.IO public LineBufferedReader(Stream stream, bool leaveOpen = false) { - streamReader = new StreamReader(stream, Encoding.UTF8, true, -1, leaveOpen); + streamReader = new StreamReader(stream, Encoding.UTF8, true, 1024, leaveOpen); } /// From 086388ec4ea2723fbd7e40719d9fa517503631c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sat, 9 Jul 2022 21:06:47 +0800 Subject: [PATCH 458/803] Remove nullable disable annotation in the benchmark project. --- osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs | 2 -- osu.Game.Benchmarks/BenchmarkMod.cs | 2 -- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 2 -- osu.Game.Benchmarks/BenchmarkRuleset.cs | 2 -- osu.Game.Benchmarks/BenchmarkTest.cs | 2 -- osu.Game.Benchmarks/Program.cs | 2 -- 6 files changed, 12 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs index 07ffda4030..1d207d04c7 100644 --- a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs +++ b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.IO; using BenchmarkDotNet.Attributes; using osu.Framework.IO.Stores; diff --git a/osu.Game.Benchmarks/BenchmarkMod.cs b/osu.Game.Benchmarks/BenchmarkMod.cs index a1d92d9a67..c5375e9f09 100644 --- a/osu.Game.Benchmarks/BenchmarkMod.cs +++ b/osu.Game.Benchmarks/BenchmarkMod.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using BenchmarkDotNet.Attributes; using osu.Game.Rulesets.Osu.Mods; diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index 5ffda6504e..2277f4e85c 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using System.Threading; using BenchmarkDotNet.Attributes; diff --git a/osu.Game.Benchmarks/BenchmarkRuleset.cs b/osu.Game.Benchmarks/BenchmarkRuleset.cs index de8cb13773..2835ec9499 100644 --- a/osu.Game.Benchmarks/BenchmarkRuleset.cs +++ b/osu.Game.Benchmarks/BenchmarkRuleset.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Engines; using osu.Game.Online.API; diff --git a/osu.Game.Benchmarks/BenchmarkTest.cs b/osu.Game.Benchmarks/BenchmarkTest.cs index 140696e4a4..34f5edd084 100644 --- a/osu.Game.Benchmarks/BenchmarkTest.cs +++ b/osu.Game.Benchmarks/BenchmarkTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using NUnit.Framework; diff --git a/osu.Game.Benchmarks/Program.cs b/osu.Game.Benchmarks/Program.cs index 603d8aa1b9..439ced53ab 100644 --- a/osu.Game.Benchmarks/Program.cs +++ b/osu.Game.Benchmarks/Program.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using BenchmarkDotNet.Configs; using BenchmarkDotNet.Running; From 8fcc33936c799b49ac4c3350ff84757a1c5d32a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sat, 9 Jul 2022 21:07:47 +0800 Subject: [PATCH 459/803] Mark the parameter as non-nullable. --- osu.Game.Benchmarks/BenchmarkMod.cs | 2 +- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 6 +++--- osu.Game.Benchmarks/BenchmarkRuleset.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkMod.cs b/osu.Game.Benchmarks/BenchmarkMod.cs index c5375e9f09..994300df36 100644 --- a/osu.Game.Benchmarks/BenchmarkMod.cs +++ b/osu.Game.Benchmarks/BenchmarkMod.cs @@ -9,7 +9,7 @@ namespace osu.Game.Benchmarks { public class BenchmarkMod : BenchmarkTest { - private OsuModDoubleTime mod; + private OsuModDoubleTime mod = null!; [Params(1, 10, 100)] public int Times { get; set; } diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index 2277f4e85c..082eb5e51b 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -15,9 +15,9 @@ namespace osu.Game.Benchmarks { public class BenchmarkRealmReads : BenchmarkTest { - private TemporaryNativeStorage storage; - private RealmAccess realm; - private UpdateThread updateThread; + private TemporaryNativeStorage storage = null!; + private RealmAccess realm = null!; + private UpdateThread updateThread = null!; [Params(1, 100, 1000)] public int ReadsPerFetch { get; set; } diff --git a/osu.Game.Benchmarks/BenchmarkRuleset.cs b/osu.Game.Benchmarks/BenchmarkRuleset.cs index 2835ec9499..7d318e043b 100644 --- a/osu.Game.Benchmarks/BenchmarkRuleset.cs +++ b/osu.Game.Benchmarks/BenchmarkRuleset.cs @@ -11,9 +11,9 @@ namespace osu.Game.Benchmarks { public class BenchmarkRuleset : BenchmarkTest { - private OsuRuleset ruleset; - private APIMod apiModDoubleTime; - private APIMod apiModDifficultyAdjust; + private OsuRuleset ruleset = null!; + private APIMod apiModDoubleTime = null!; + private APIMod apiModDifficultyAdjust = null!; public override void SetUp() { From 378be99fe1cc9d2608ed4e0901e8bfd3164518e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sat, 9 Jul 2022 21:12:35 +0800 Subject: [PATCH 460/803] Remove un-need null check. --- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index 082eb5e51b..1df77320d2 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -133,9 +133,9 @@ namespace osu.Game.Benchmarks [GlobalCleanup] public void Cleanup() { - realm?.Dispose(); - storage?.Dispose(); - updateThread?.Exit(); + realm.Dispose(); + storage.Dispose(); + updateThread.Exit(); } } } From 5fcc4bf713937e1edbcae416532b3a29f43844dd Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 9 Jul 2022 12:10:18 -0700 Subject: [PATCH 461/803] Add failing sample playback disabled check --- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index 8e325838ff..ff985de70e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -24,6 +24,7 @@ namespace osu.Game.Tests.Visual.Editing public void TestCantExitWithoutSaving() { AddRepeatStep("Exit", () => InputManager.Key(Key.Escape), 10); + AddAssert("Sample playback disabled", () => Editor.SamplePlaybackDisabled.Value); AddAssert("Editor is still active screen", () => Game.ScreenStack.CurrentScreen is Editor); } From 834bb1f187a268f25be9b031cfc2319b7c56e13c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 9 Jul 2022 12:14:39 -0700 Subject: [PATCH 462/803] Fix editor playing object samples while paused after cancelling exit --- osu.Game/Screens/Edit/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 6ec9ff4e89..2b763415cd 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -921,7 +921,7 @@ namespace osu.Game.Screens.Edit private void cancelExit() { - samplePlaybackDisabled.Value = false; + updateSampleDisabledState(); loader?.CancelPendingDifficultySwitch(); } From 52aef09cd6528bc8541f3d77305053dbce3da769 Mon Sep 17 00:00:00 2001 From: Ludio235 <94078268+Ludio235@users.noreply.github.com> Date: Sun, 10 Jul 2022 02:05:40 +0000 Subject: [PATCH 463/803] Update PlaylistsRoomSettingsOverlay.cs --- .../OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 1e565e298e..9c6a2a5e0b 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -140,9 +140,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists }, new Section("Duration") { - Child = DurationField = new DurationDropdown + Child = new Container { RelativeSizeAxes = Axes.X, + Height = 40, + Child = DurationField = new DurationDropdown + { + RelativeSizeAxes = Axes.X + } } }, new Section("Allowed attempts (across all playlist items)") From 6443338251e5c392ae71b3d4ee5410ddc6b36463 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Sun, 10 Jul 2022 01:22:22 -0400 Subject: [PATCH 464/803] use cursor position instead of destination for dampLength calculation the destination vector is clamped within playfield borders, we want dampLength to be based on distance from the cursor. --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 1cb5bf31a4..17bbba4ba7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -69,24 +69,24 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - easeTo(circle, destination); + easeTo(circle, destination, cursorPos); break; case DrawableSlider slider: if (!slider.HeadCircle.Result.HasResult) - easeTo(slider, destination); + easeTo(slider, destination, cursorPos); else - easeTo(slider, destination - slider.Ball.DrawPosition); + easeTo(slider, destination - slider.Ball.DrawPosition, cursorPos); break; } } } - private void easeTo(DrawableHitObject hitObject, Vector2 destination) + private void easeTo(DrawableHitObject hitObject, Vector2 destination, Vector2 cursorPos) { - double dampLength = Vector2.Distance(hitObject.Position, destination) / (0.04 * RepulsionStrength.Value + 0.04); + double dampLength = Vector2.Distance(hitObject.Position, cursorPos) / (0.04 * RepulsionStrength.Value + 0.04); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); From 8116a4b6f6a2779d74172c168afeee2f593d0bed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 10 Jul 2022 23:53:06 +0900 Subject: [PATCH 465/803] Fix multiplayer spectator crash due to track potentially not being loaded in time --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index bae25dc9f8..f5af110372 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -314,6 +314,9 @@ namespace osu.Game.Screens.OnlinePlay.Match public override void OnSuspending(ScreenTransitionEvent e) { + // Should be a noop in most cases, but let's ensure beyond doubt that the beatmap is in a correct state. + updateWorkingBeatmap(); + onLeaving(); base.OnSuspending(e); } From 8b6665cb5b211c06e24f9d3e9bf210480bc08760 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 02:51:54 +0900 Subject: [PATCH 466/803] Ensure initial beatmap processing is done inside the import transaction --- osu.Game/Database/RealmArchiveModelImporter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 6cea92f1d9..aa7fac07a8 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -338,11 +338,11 @@ namespace osu.Game.Database // import to store realm.Add(item); + PostImport(item, realm); + transaction.Commit(); } - PostImport(item, realm); - LogForModel(item, @"Import successfully completed!"); } catch (Exception e) @@ -479,7 +479,7 @@ namespace osu.Game.Database } /// - /// Perform any final actions after the import has been committed to the database. + /// Perform any final actions before the import has been committed to the database. /// /// The model prepared for import. /// The current realm context. From 0434c1091472e1582aa144e9f1ec77059acad4e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 02:57:44 +0900 Subject: [PATCH 467/803] Use global `WorkingBeatmap` in `PlayerArea` for the time being --- .../Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 5bae4f9ea5..302d04b531 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -53,9 +53,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [CanBeNull] public Score Score { get; private set; } - [Resolved] - private BeatmapManager beatmapManager { get; set; } - private readonly BindableDouble volumeAdjustment = new BindableDouble(); private readonly Container gameplayContent; private readonly LoadingLayer loadingLayer; @@ -84,6 +81,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate GameplayClock.Source = masterClock; } + [Resolved] + private IBindable beatmap { get; set; } + public void LoadScore([NotNull] Score score) { if (Score != null) @@ -91,7 +91,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Score = score; - gameplayContent.Child = new PlayerIsolationContainer(beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.BeatmapInfo), Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) + gameplayContent.Child = new PlayerIsolationContainer(beatmap.Value, Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) { RelativeSizeAxes = Axes.Both, Child = stack = new OsuScreenStack From 48911b956af8bbb1835fbd73de06572846b4f2bf Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Sun, 10 Jul 2022 17:07:21 -0700 Subject: [PATCH 468/803] Remove ClearTransformsAfter call A bit weird only having one call on its own; probably deserves an entire PR dedicated to adding ClearTransformsAfter calls --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index 26d3f053ff..f18c4529ab 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -73,8 +73,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void onHitObjectApplied(DrawableHitObject drawableObject) { - ClearTransformsAfter(double.MinValue); - this.ScaleTo(1f) .FadeOut(); } From 958c0fb390ea04ac37f11e6547b8bb95ce9b31a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 15:01:16 +0900 Subject: [PATCH 469/803] Remove `Appveyor.TestLogger` --- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 1 - .../osu.Game.Rulesets.Pippidon.Tests.csproj | 1 - .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 1 - .../osu.Game.Rulesets.Pippidon.Tests.csproj | 1 - .../osu.Game.Rulesets.Catch.Tests.csproj | 1 - .../osu.Game.Rulesets.Mania.Tests.csproj | 1 - osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 1 - .../osu.Game.Rulesets.Taiko.Tests.csproj | 1 - osu.Game.Tests/osu.Game.Tests.csproj | 1 - osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 1 - 10 files changed, 10 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 bc285dbe11..011a37cbdc 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 @@ -9,7 +9,6 @@ false - 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 718ada1905..c04f6132f3 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 @@ -9,7 +9,6 @@ false - 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 6b9c3f4d63..529054fd4f 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 @@ -9,7 +9,6 @@ false - 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 718ada1905..c04f6132f3 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 @@ -9,7 +9,6 @@ false - 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 b957ade952..3ac1491946 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 @@ -1,7 +1,6 @@  - 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 d3b4b378c0..d07df75864 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 @@ -1,7 +1,6 @@  - 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 2c0d3fd937..402cf3ad41 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 @@ -1,7 +1,6 @@  - 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 ce468d399b..51d4bbc630 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 @@ -1,7 +1,6 @@  - diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index a1eef4ce47..02fd6ccd16 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -1,7 +1,6 @@  - diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 6fd53d923b..5512b26863 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -4,7 +4,6 @@ osu.Game.Tournament.Tests.TournamentTestRunner - From 22a51fdc50f8444034049eca370c0aaaf19fc83f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 15:35:00 +0900 Subject: [PATCH 470/803] Add support for a drawings screen video background --- osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 32da4d1b36..0df6386dae 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -97,6 +97,11 @@ namespace osu.Game.Tournament.Screens.Drawings FillMode = FillMode.Fill, Texture = textures.Get(@"Backgrounds/Drawings/background.png") }, + new TourneyVideo("drawings") + { + Loop = true, + RelativeSizeAxes = Axes.Both, + }, // Visualiser new VisualiserContainer { From 10d6027c8921937c2b8e100239174d87b0bee188 Mon Sep 17 00:00:00 2001 From: Andrew Hong <35881688+novialriptide@users.noreply.github.com> Date: Thu, 7 Jul 2022 23:16:06 -0400 Subject: [PATCH 471/803] Assign missing UserID to RealmUser --- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Scoring/ScoreImporter.cs | 28 +++++++++++++++++++++++++++- osu.Game/Scoring/ScoreManager.cs | 5 +++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a16252ac89..4b5c9c0815 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -272,7 +272,7 @@ namespace osu.Game dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, difficultyCache, LocalConfig)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, API, difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true)); diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index f59ffc7c94..7494914625 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -8,11 +8,15 @@ using System.Threading; using Newtonsoft.Json; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Game.Models; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Rulesets; using osu.Game.Scoring.Legacy; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using Realms; namespace osu.Game.Scoring @@ -26,11 +30,14 @@ namespace osu.Game.Scoring private readonly RulesetStore rulesets; private readonly Func beatmaps; - public ScoreImporter(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm) + private readonly IAPIProvider api; + + public ScoreImporter(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api) : base(storage, realm) { this.rulesets = rulesets; this.beatmaps = beatmaps; + this.api = api; } protected override ScoreInfo? CreateModel(ArchiveReader archive) @@ -68,5 +75,24 @@ namespace osu.Game.Scoring if (string.IsNullOrEmpty(model.StatisticsJson)) model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics); } + + protected override void PostImport(ScoreInfo model, Realm realm) + { + base.PostImport(model, realm); + + var userRequest = new GetUserRequest(model.User.Username); + api.Perform(userRequest); + APIUser userReq = userRequest.Response; + + if (!(userReq is null)) { + Logger.Log($"Assignning UserID to RealmUser"); + var user = new RealmUser + { + OnlineID = userReq.Id, + Username = model.User.Username + }; + model.RealmUser = user; + } + } } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 6ee1d11f83..9aed8904e6 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -21,6 +21,7 @@ using osu.Game.IO.Archives; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; +using osu.Game.Online.API; namespace osu.Game.Scoring { @@ -31,7 +32,7 @@ namespace osu.Game.Scoring private readonly OsuConfigManager configManager; private readonly ScoreImporter scoreImporter; - public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler, + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler, IAPIProvider api, BeatmapDifficultyCache difficultyCache = null, OsuConfigManager configManager = null) : base(storage, realm) { @@ -39,7 +40,7 @@ namespace osu.Game.Scoring this.difficultyCache = difficultyCache; this.configManager = configManager; - scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm) + scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api) { PostNotification = obj => PostNotification?.Invoke(obj) }; From 56896e8b415a7b9622a99561a98d48d001d52602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrew=20Hong=20=28=ED=99=8D=EC=A4=80=EC=9B=90=29?= <35881688+novialriptide@users.noreply.github.com> Date: Mon, 11 Jul 2022 02:20:39 -0400 Subject: [PATCH 472/803] Move PostImport() --- osu.Game/Database/RealmArchiveModelImporter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 6cea92f1d9..c143c51c6d 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -338,11 +338,11 @@ namespace osu.Game.Database // import to store realm.Add(item); + PostImport(item, realm); + transaction.Commit(); } - PostImport(item, realm); - LogForModel(item, @"Import successfully completed!"); } catch (Exception e) From 6220650ea3228930ebd27a46b9e4c8c3824abc73 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 11 Jul 2022 02:29:56 -0700 Subject: [PATCH 473/803] Fix dialog overlay not loading in time for headless test --- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index ff985de70e..d7e9cc1bc0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -6,11 +6,13 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Overlays; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Select; @@ -23,6 +25,7 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestCantExitWithoutSaving() { + AddUntilStep("Wait for dialog overlay load", () => ((Drawable)Game.Dependencies.Get()).IsLoaded); AddRepeatStep("Exit", () => InputManager.Key(Key.Escape), 10); AddAssert("Sample playback disabled", () => Editor.SamplePlaybackDisabled.Value); AddAssert("Editor is still active screen", () => Game.ScreenStack.CurrentScreen is Editor); From 44d2e001ed4d80674beb281e32a69241294364fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 20:15:29 +0900 Subject: [PATCH 474/803] Update various dependencies --- osu.Desktop/osu.Desktop.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game/osu.Game.csproj | 14 +++++++------- osu.iOS.props | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index a4f9e2671b..c67017f175 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -24,7 +24,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 402cf3ad41..4349d25cb3 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 @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 02fd6ccd16..7615b3e8be 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 06b022ea44..1251ab800b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,12 +20,12 @@ - + - - - - + + + + @@ -38,8 +38,8 @@ - - + + diff --git a/osu.iOS.props b/osu.iOS.props index 9085205bbc..c38bb548bf 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -85,7 +85,7 @@ - + From 00c7101f540329333442390719113c3fedb25628 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 20:36:05 +0900 Subject: [PATCH 475/803] Remove `DrawingsScreen` world map completely --- .../Screens/Drawings/DrawingsScreen.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 0df6386dae..85afb9b2d0 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -12,8 +12,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Graphics; @@ -26,7 +24,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Drawings { - public class DrawingsScreen : TournamentScreen + public class DrawingsScreen : TournamentScreen, IProvideVideo { private const string results_filename = "drawings_results.txt"; @@ -45,7 +43,7 @@ namespace osu.Game.Tournament.Screens.Drawings public ITeamList TeamList; [BackgroundDependencyLoader] - private void load(TextureStore textures, Storage storage) + private void load(Storage storage) { RelativeSizeAxes = Axes.Both; @@ -91,12 +89,6 @@ namespace osu.Game.Tournament.Screens.Drawings RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new Sprite - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fill, - Texture = textures.Get(@"Backgrounds/Drawings/background.png") - }, new TourneyVideo("drawings") { Loop = true, From 73e924479fb6db7b5e57413ef08663c068805f3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 20:42:04 +0900 Subject: [PATCH 476/803] Find video by recursive check rather than marker interface Seems a lot more reliable, and allows falling back to the "main" video in cases which didn't support this previously. A next step may be to allow every screen to support a video based on its screen name, rather than specifying the local `TourneyVideo` every time. --- osu.Game.Tournament/Components/TourneyVideo.cs | 2 ++ .../Screens/Drawings/DrawingsScreen.cs | 2 +- .../Screens/Editors/TournamentEditorScreen.cs | 2 +- .../Screens/Gameplay/GameplayScreen.cs | 2 +- osu.Game.Tournament/Screens/IProvideVideo.cs | 14 -------------- osu.Game.Tournament/Screens/Ladder/LadderScreen.cs | 2 +- .../Screens/Schedule/ScheduleScreen.cs | 2 +- osu.Game.Tournament/Screens/Setup/SetupScreen.cs | 2 +- .../Screens/Showcase/ShowcaseScreen.cs | 2 +- .../Screens/TeamIntro/SeedingScreen.cs | 2 +- .../Screens/TeamIntro/TeamIntroScreen.cs | 2 +- .../Screens/TeamWin/TeamWinScreen.cs | 2 +- osu.Game.Tournament/TournamentSceneManager.cs | 3 ++- 13 files changed, 14 insertions(+), 25 deletions(-) delete mode 100644 osu.Game.Tournament/Screens/IProvideVideo.cs diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index c6bbb54f9a..2e79998e66 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -22,6 +22,8 @@ namespace osu.Game.Tournament.Components private Video video; private ManualClock manualClock; + public bool VideoAvailable => video != null; + public TourneyVideo(string filename, bool drawFallbackGradient = false) { this.filename = filename; diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 85afb9b2d0..5ac25f97b5 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -24,7 +24,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Drawings { - public class DrawingsScreen : TournamentScreen, IProvideVideo + public class DrawingsScreen : TournamentScreen { private const string results_filename = "drawings_results.txt"; diff --git a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs index 8af5bbe513..0fefe6f780 100644 --- a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs @@ -20,7 +20,7 @@ using osuTK; namespace osu.Game.Tournament.Screens.Editors { - public abstract class TournamentEditorScreen : TournamentScreen, IProvideVideo + public abstract class TournamentEditorScreen : TournamentScreen where TDrawable : Drawable, IModelBacked where TModel : class, new() { diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index 86b2c2a4e9..54ae4c0366 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -21,7 +21,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Gameplay { - public class GameplayScreen : BeatmapInfoScreen, IProvideVideo + public class GameplayScreen : BeatmapInfoScreen { private readonly BindableBool warmup = new BindableBool(); diff --git a/osu.Game.Tournament/Screens/IProvideVideo.cs b/osu.Game.Tournament/Screens/IProvideVideo.cs deleted file mode 100644 index aa67a5211f..0000000000 --- a/osu.Game.Tournament/Screens/IProvideVideo.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -namespace osu.Game.Tournament.Screens -{ - /// - /// Marker interface for a screen which provides its own local video background. - /// - public interface IProvideVideo - { - } -} diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index 23bfa84afc..7ad7e76a1f 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Ladder { - public class LadderScreen : TournamentScreen, IProvideVideo + public class LadderScreen : TournamentScreen { protected Container MatchesContainer; private Container paths; diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index 7a11e26794..0827cbae69 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Schedule { - public class ScheduleScreen : TournamentScreen // IProvidesVideo + public class ScheduleScreen : TournamentScreen { private readonly Bindable currentMatch = new Bindable(); private Container mainContainer; diff --git a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs index 42eff3565f..f472541d59 100644 --- a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs @@ -19,7 +19,7 @@ using osuTK; namespace osu.Game.Tournament.Screens.Setup { - public class SetupScreen : TournamentScreen, IProvideVideo + public class SetupScreen : TournamentScreen { private FillFlowContainer fillFlow; diff --git a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs index 082aa99b0e..a7a175ceba 100644 --- a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs +++ b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs @@ -14,7 +14,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Showcase { - public class ShowcaseScreen : BeatmapInfoScreen // IProvideVideo + public class ShowcaseScreen : BeatmapInfoScreen { [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index 925c697346..719e0384d3 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -19,7 +19,7 @@ using osuTK; namespace osu.Game.Tournament.Screens.TeamIntro { - public class SeedingScreen : TournamentMatchScreen, IProvideVideo + public class SeedingScreen : TournamentMatchScreen { private Container mainContainer; diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs index 98dfaa7487..08c9a7a897 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs @@ -13,7 +13,7 @@ using osuTK; namespace osu.Game.Tournament.Screens.TeamIntro { - public class TeamIntroScreen : TournamentMatchScreen, IProvideVideo + public class TeamIntroScreen : TournamentMatchScreen { private Container mainContainer; diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index 50207547cd..07557674e8 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Tournament.Screens.TeamWin { - public class TeamWinScreen : TournamentMatchScreen, IProvideVideo + public class TeamWinScreen : TournamentMatchScreen { private Container mainContainer; diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 296b259d72..a12dbb4740 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -186,7 +187,7 @@ namespace osu.Game.Tournament var lastScreen = currentScreen; currentScreen = target; - if (currentScreen is IProvideVideo) + if (currentScreen.ChildrenOfType().FirstOrDefault()?.VideoAvailable == true) { video.FadeOut(200); From 09bfca4e4ad59da7574c8212c08f729b404aef10 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 11 Jul 2022 21:45:39 +0300 Subject: [PATCH 477/803] Fix build failing on tests --- .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 2 +- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs | 2 +- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 3011515b62..bfc06c0ee0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay { Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, Scheduler)); + Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 1b9b59676b..ef0c7d7d4d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.SongSelect dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, Scheduler)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); return dependencies; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 8f890b2383..05b5c5c0cd 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, Scheduler)); + Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index cee917f6cf..e59914f69a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Realm, Scheduler)); + dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); return dependencies; From 4f009419b8d8402550a40664ae5dead774a6bd2c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 11 Jul 2022 21:46:42 +0300 Subject: [PATCH 478/803] Simplify population logic and match code style --- osu.Game/Scoring/ScoreImporter.cs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 7494914625..53dd511d57 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -8,7 +8,6 @@ using System.Threading; using Newtonsoft.Json; using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Game.Models; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO.Archives; @@ -80,19 +79,12 @@ namespace osu.Game.Scoring { base.PostImport(model, realm); - var userRequest = new GetUserRequest(model.User.Username); - api.Perform(userRequest); - APIUser userReq = userRequest.Response; + var userRequest = new GetUserRequest(model.RealmUser.Username); - if (!(userReq is null)) { - Logger.Log($"Assignning UserID to RealmUser"); - var user = new RealmUser - { - OnlineID = userReq.Id, - Username = model.User.Username - }; - model.RealmUser = user; - } + api.Perform(userRequest); + + if (userRequest.Response is APIUser user) + model.RealmUser.OnlineID = user.Id; } } } From 54fe84350cfba996d83e81b150bd19df0e63d0e5 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Mon, 11 Jul 2022 17:23:32 -0400 Subject: [PATCH 479/803] reciprocate mod incompatibility --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 4c9418726c..a3f6448457 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Automation; public override string Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel) }; public bool PerformFail() => false; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 5a08df3803..84906f6eed 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override string Description => "Everything rotates. EVERYTHING."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised), typeof(OsuModRepel) }; private float theta; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 3fba2cefd2..8acd4fc422 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override string Description => "They just won't stay still..."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel) }; private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles private const int wiggle_strength = 10; // Higher = stronger wiggles From 28278e2554246fa49764457bfdb09f33799f9289 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Mon, 11 Jul 2022 17:27:25 -0400 Subject: [PATCH 480/803] enable NRT again --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 17bbba4ba7..211987ee32 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -1,9 +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 disable - using System; +using System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Utils; using osu.Game.Configuration; @@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised) }; - private IFrameStableClock gameplayClock; + private IFrameStableClock? gameplayClock; [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f) @@ -86,6 +85,8 @@ namespace osu.Game.Rulesets.Osu.Mods private void easeTo(DrawableHitObject hitObject, Vector2 destination, Vector2 cursorPos) { + Debug.Assert(gameplayClock != null); + double dampLength = Vector2.Distance(hitObject.Position, cursorPos) / (0.04 * RepulsionStrength.Value + 0.04); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); From 76be9a829c3df4a39c1070203dab99e13216e4a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 12:59:55 +0900 Subject: [PATCH 481/803] Fix mutation after disposal in `TeamEditorScreen` --- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 11db37c8b7..111893d18c 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -298,10 +298,10 @@ namespace osu.Game.Tournament.Screens.Editors }, true); } - private void updatePanel() + private void updatePanel() => Scheduler.AddOnce(() => { drawableContainer.Child = new UserGridPanel(user.ToAPIUser()) { Width = 300 }; - } + }); } } } From bae314a254b1c93aac01f9889bb0c34b8ce443b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 13:03:43 +0900 Subject: [PATCH 482/803] Add background on `SetupScreen` to hide video --- .../Screens/Setup/SetupScreen.cs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs index f472541d59..2b2dce3664 100644 --- a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs @@ -9,6 +9,8 @@ using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.API; using osu.Game.Overlays; @@ -48,13 +50,21 @@ namespace osu.Game.Tournament.Screens.Setup { windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); - InternalChild = fillFlow = new FillFlowContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding(10), - Spacing = new Vector2(10), + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f), + }, + fillFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(10), + Spacing = new Vector2(10), + } }; api.LocalUser.BindValueChanged(_ => Schedule(reload)); @@ -74,7 +84,8 @@ namespace osu.Game.Tournament.Screens.Setup Action = () => sceneManager?.SetScreen(new StablePathSelectScreen()), Value = fileBasedIpc?.IPCStorage?.GetFullPath(string.Empty) ?? "Not found", Failing = fileBasedIpc?.IPCStorage == null, - Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation." + Description = + "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation." }, new ActionableInfo { From 90fecbc9c7435ff5c2f3077b8fdebb96ff3d6c52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 14:32:39 +0900 Subject: [PATCH 483/803] Add test showing all mod icons for reference --- .../Visual/UserInterface/TestSceneModIcon.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs index 0a0415789a..ce9aa682d1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.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. -#nullable disable - using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; @@ -13,10 +13,24 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneModIcon : OsuTestScene { + [Test] + public void TestShowAllMods() + { + AddStep("create mod icons", () => + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Full, + ChildrenEnumerable = Ruleset.Value.CreateInstance().CreateAllMods().Select(m => new ModIcon(m)), + }; + }); + } + [Test] public void TestChangeModType() { - ModIcon icon = null; + ModIcon icon = null!; AddStep("create mod icon", () => Child = icon = new ModIcon(new OsuModDoubleTime())); AddStep("change mod", () => icon.Mod = new OsuModEasy()); @@ -25,7 +39,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestInterfaceModType() { - ModIcon icon = null; + ModIcon icon = null!; var ruleset = new OsuRuleset(); From 8dbe24fd7c05d8f30cf500b8d77dfa17094c2320 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 14:36:50 +0900 Subject: [PATCH 484/803] Simplify colour assigning logic and remove system mod colour for now --- osu.Game/Rulesets/UI/ModIcon.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 308122b71c..b1f355a789 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osuTK; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Localisation; namespace osu.Game.Rulesets.UI @@ -53,7 +54,6 @@ namespace osu.Game.Rulesets.UI private OsuColour colours { get; set; } private Color4 backgroundColour; - private Color4 highlightedColour; /// /// Construct a new instance. @@ -123,19 +123,13 @@ namespace osu.Game.Rulesets.UI modAcronym.FadeOut(); } - Color4 typeColour = colours.ForModType(value.Type); - backgroundColour = typeColour; - highlightedColour = ((Colour4)typeColour).Lighten(.2f); - - if (value.Type == ModType.System) - modIcon.Colour = colours.Yellow; - + backgroundColour = colours.ForModType(value.Type); updateColour(); } private void updateColour() { - background.Colour = Selected.Value ? highlightedColour : backgroundColour; + background.Colour = Selected.Value ? backgroundColour.Lighten(0.2f) : backgroundColour; } } } From b52ea161336ce148e1d511dbb9111289ad9f2972 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 15:10:59 +0900 Subject: [PATCH 485/803] Show basic error message when score submission fails --- 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 1cc0d1853d..c916791eaa 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -168,7 +168,7 @@ namespace osu.Game.Screens.Play request.Failure += e => { - Logger.Error(e, "Failed to submit score"); + Logger.Error(e, $"Failed to submit score ({e.Message})"); scoreSubmissionSource.SetResult(false); }; From fa626a82b3448b8da0130cadff09e240e236ba39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 15:18:52 +0900 Subject: [PATCH 486/803] Add missed incompatilibity rules --- osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index d562c37541..490b5b7a9d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModAutoplay : ModAutoplay { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray(); public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) => new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" }); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 2cf8c278ca..5c1de83972 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset, IApplicableToPlayer { public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAlternate) }).ToArray(); /// /// How early before a hitobject's start time to trigger a hit. From cafe30fc4dc0d21dc885dd224568b88f6b4f545a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 14:54:45 +0900 Subject: [PATCH 487/803] Fix potential crash during shutdown sequence if intro playback was aborted Fixes one of the audio related `ObjectDisposedException`s (https://sentry.ppy.sh/organizations/ppy/issues/92/events/12f282f048cb4a4fae85810e8a70b68d/?project=2&query=is%3Aunresolved&sort=freq&statsPeriod=7d). Ran into this while testing locally. See `IntroScreen.ensureEventuallyArrivingAtMenu` for the related cause of this happening (forced continuing to next screen if the intro doesn't load in time). --- osu.Game/Screens/Menu/IntroTriangles.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 3cdf51a87c..c228faae58 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -72,9 +72,16 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.Both, Clock = decoupledClock, LoadMenu = LoadMenu - }, t => + }, _ => { - AddInternal(t); + AddInternal(intro); + + // There is a chance that the intro timed out before being displayed, and this scheduled callback could + // happen during the outro rather than intro. In such a scenario the game may already be in a disposing state + // which will trigger errors during attempted audio playback. + if (DidLoadMenu) + return; + if (!UsingThemedIntro) welcome?.Play(); From 10a14f39ed3ead07664dcf24edc0b70dc469c717 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 15:42:20 +0900 Subject: [PATCH 488/803] Show an error message on startup when attempting to run on an unsupported version of windows A lot of sentry error reports are coming from realm / EF failures due to the host operating system being too old. Let's give the user some proper feedback rather than a silent crash and error report hitting our logging. --- osu.Desktop/Program.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 712f300671..da2a580086 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -14,6 +14,7 @@ using osu.Framework.Platform; using osu.Game; using osu.Game.IPC; using osu.Game.Tournament; +using SDL2; using Squirrel; namespace osu.Desktop @@ -29,7 +30,19 @@ namespace osu.Desktop { // run Squirrel first, as the app may exit after these run if (OperatingSystem.IsWindows()) + { + var windowsVersion = Environment.OSVersion.Version; + + if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 1)) + { + SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, + "Your operating system is too old to run osu!", + "This version of osu! requires at least Windows 8 to run.\nPlease upgrade your operating system or consider using an older version of osu!.", IntPtr.Zero); + return; + } + setupSquirrel(); + } // Back up the cwd before DesktopGameHost changes it string cwd = Environment.CurrentDirectory; From a36f7867250558ceebcb947ee489564401601b81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 16:16:46 +0900 Subject: [PATCH 489/803] Change minimum version to Windows 8.1 instead of Windows 8 --- osu.Desktop/Program.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index da2a580086..66b361cb73 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -33,7 +33,9 @@ namespace osu.Desktop { var windowsVersion = Environment.OSVersion.Version; - if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 1)) + // While .NET 6 still supports Windows 7 and above, we are limited by realm currently, as they choose to only support 8.1 and higher. + // See https://www.mongodb.com/docs/realm/sdk/dotnet/#supported-platforms + if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2)) { SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, "Your operating system is too old to run osu!", From cad18ebc5844277bad61c50cac8a40c429c5da80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 16:46:19 +0900 Subject: [PATCH 490/803] Reword comment to better explain what we are guarding against --- osu.Game/Screens/Menu/IntroTriangles.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index c228faae58..6ad0350e43 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -77,8 +77,9 @@ namespace osu.Game.Screens.Menu AddInternal(intro); // There is a chance that the intro timed out before being displayed, and this scheduled callback could - // happen during the outro rather than intro. In such a scenario the game may already be in a disposing state - // which will trigger errors during attempted audio playback. + // happen during the outro rather than intro. + // In such a scenario, we don't want to play the intro sample, nor attempt to start the intro track + // (that may have already been since disposed by MusicController). if (DidLoadMenu) return; From 1bef2d7b3946d6856da6574aa1cbaed244547755 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 04:04:21 +0900 Subject: [PATCH 491/803] Add and consume `SoloScoreInfo` --- osu.Game/Online/API/APIAccess.cs | 2 +- .../Responses/APIScoreWithPosition.cs | 4 +- .../Requests/Responses/APIScoresCollection.cs | 2 +- .../API/Requests/Responses/SoloScoreInfo.cs | 124 ++++++++++++++++++ .../BeatmapSet/Scores/ScoresContainer.cs | 2 +- 5 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 088dc56701..43cea7fb97 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -38,7 +38,7 @@ namespace osu.Game.Online.API public string WebsiteRootUrl { get; } - public int APIVersion => 20220217; // We may want to pull this from the game version eventually. + public int APIVersion => 20220705; // We may want to pull this from the game version eventually. public Exception LastLoginError { get; private set; } diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs index 8bd54f889d..494826f534 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs @@ -16,11 +16,11 @@ namespace osu.Game.Online.API.Requests.Responses public int? Position; [JsonProperty(@"score")] - public APIScore Score; + public SoloScoreInfo Score; public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) { - var score = Score.CreateScoreInfo(rulesets, beatmap); + var score = Score.ToScoreInfo(rulesets, beatmap); score.Position = Position; return score; } diff --git a/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs b/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs index 9c8a38c63a..38c67d92f4 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs @@ -11,7 +11,7 @@ namespace osu.Game.Online.API.Requests.Responses public class APIScoresCollection { [JsonProperty(@"scores")] - public List Scores; + public List Scores; [JsonProperty(@"userScore")] public APIScoreWithPosition UserScore; diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs new file mode 100644 index 0000000000..71479d2867 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -0,0 +1,124 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; + +namespace osu.Game.Online.API.Requests.Responses +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + [Serializable] + public class SoloScoreInfo : IHasOnlineID + { + [JsonIgnore] + public long id { get; set; } + + public int user_id { get; set; } + + public int beatmap_id { get; set; } + + public int ruleset_id { get; set; } + + public int? build_id { get; set; } + + public bool passed { get; set; } + + public int total_score { get; set; } + + public double accuracy { get; set; } + + public APIUser user { get; set; } + + // TODO: probably want to update this column to match user stats (short)? + public int max_combo { get; set; } + + [JsonConverter(typeof(StringEnumConverter))] + public ScoreRank rank { get; set; } + + public DateTimeOffset? started_at { get; set; } + + public DateTimeOffset? ended_at { get; set; } + + public List mods { get; set; } = new List(); + + [JsonProperty("statistics")] + public Dictionary Statistics { get; set; } = new Dictionary(); + + public override string ToString() => $"score_id: {id} user_id: {user_id}"; + + [JsonIgnore] + public DateTimeOffset created_at { get; set; } + + [JsonIgnore] + public DateTimeOffset updated_at { get; set; } + + [JsonIgnore] + public DateTimeOffset? deleted_at { get; set; } + + /// + /// Create a from an API score instance. + /// + /// A ruleset store, used to populate a ruleset instance in the returned score. + /// An optional beatmap, copied into the returned score (for cases where the API does not populate the beatmap). + /// + public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) + { + var ruleset = rulesets.GetRuleset(ruleset_id) ?? throw new InvalidOperationException($"Ruleset with ID of {ruleset_id} not found locally"); + + var rulesetInstance = ruleset.CreateInstance(); + + var modInstances = mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); + + // all API scores provided by this class are considered to be legacy. + modInstances = modInstances.Append(rulesetInstance.CreateMod()).ToArray(); + + var scoreInfo = new ScoreInfo + { + User = user ?? new APIUser { Id = user_id }, + BeatmapInfo = beatmap ?? new BeatmapInfo { OnlineID = beatmap_id }, + Passed = passed, + TotalScore = total_score, + Accuracy = accuracy, + MaxCombo = max_combo, + Rank = rank, + Statistics = Statistics, + OnlineID = OnlineID, + Date = ended_at ?? DateTimeOffset.Now, + // PP = + Hash = "online", // TODO: temporary? + Ruleset = ruleset, + Mods = modInstances, + }; + + return scoreInfo; + } + + public ScoreInfo CreateScoreInfo(Mod[] mods) => new ScoreInfo + { + OnlineID = id, + User = new APIUser { Id = user_id }, + BeatmapInfo = new BeatmapInfo { OnlineID = beatmap_id }, + Ruleset = new RulesetInfo { OnlineID = ruleset_id }, + Passed = passed, + TotalScore = total_score, + Accuracy = accuracy, + MaxCombo = max_combo, + Rank = rank, + Mods = mods, + Statistics = Statistics + }; + + public long OnlineID => id; + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index c2e54d0d7b..98202ba7c0 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores scoreTable.Show(); var userScore = value.UserScore; - var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets, beatmapInfo); + var userScoreInfo = userScore?.Score.ToScoreInfo(rulesets, beatmapInfo); topScoresContainer.Add(new DrawableTopScore(topScore)); From 900e0ace8ed24edf99b47748b00217e3dfe642ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 16:59:56 +0900 Subject: [PATCH 492/803] Standardise naming and enable NRT --- .../API/Requests/Responses/SoloScoreInfo.cs | 124 ++++++++++-------- 1 file changed, 71 insertions(+), 53 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 71479d2867..5dce2db6ed 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -1,10 +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 disable using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -17,54 +15,73 @@ using osu.Game.Scoring; namespace osu.Game.Online.API.Requests.Responses { - [SuppressMessage("ReSharper", "InconsistentNaming")] [Serializable] public class SoloScoreInfo : IHasOnlineID { [JsonIgnore] - public long id { get; set; } + [JsonProperty(" ")] + public long ID { get; set; } - public int user_id { get; set; } + [JsonProperty("beatmap_id")] + public int BeatmapID { get; set; } - public int beatmap_id { get; set; } + [JsonProperty("ruleset_id")] + public int RulesetID { get; set; } - public int ruleset_id { get; set; } + [JsonProperty("build_id")] + public int? BuildID { get; set; } - public int? build_id { get; set; } + [JsonProperty("passed")] + public bool Passed { get; set; } - public bool passed { get; set; } + [JsonProperty("total_score")] + public int TotalScore { get; set; } - public int total_score { get; set; } + [JsonProperty("accuracy")] + public double Accuracy { get; set; } - public double accuracy { get; set; } + [JsonProperty("user_id")] + public int UserID { get; set; } - public APIUser user { get; set; } + /// + /// User may be provided via an osu-web response, but will not be present from raw database json. + /// + [JsonProperty("user")] + public APIUser? User { get; set; } // TODO: probably want to update this column to match user stats (short)? - public int max_combo { get; set; } + [JsonProperty("max_combo")] + public int MaxCombo { get; set; } [JsonConverter(typeof(StringEnumConverter))] - public ScoreRank rank { get; set; } + [JsonProperty("rank")] + public ScoreRank Rank { get; set; } - public DateTimeOffset? started_at { get; set; } + [JsonProperty("started_at")] + public DateTimeOffset? StartedAt { get; set; } - public DateTimeOffset? ended_at { get; set; } + [JsonProperty("ended_at")] + public DateTimeOffset? EndedAt { get; set; } - public List mods { get; set; } = new List(); + [JsonProperty("mods")] + public List Mods { get; set; } = new List(); + + [JsonIgnore] + [JsonProperty("created_at")] + public DateTimeOffset CreatedAt { get; set; } + + [JsonIgnore] + [JsonProperty("updated_at")] + public DateTimeOffset UpdatedAt { get; set; } + + [JsonIgnore] + [JsonProperty("deleted_at")] + public DateTimeOffset? DeletedAt { get; set; } [JsonProperty("statistics")] public Dictionary Statistics { get; set; } = new Dictionary(); - public override string ToString() => $"score_id: {id} user_id: {user_id}"; - - [JsonIgnore] - public DateTimeOffset created_at { get; set; } - - [JsonIgnore] - public DateTimeOffset updated_at { get; set; } - - [JsonIgnore] - public DateTimeOffset? deleted_at { get; set; } + public override string ToString() => $"score_id: {ID} user_id: {UserID}"; /// /// Create a from an API score instance. @@ -72,53 +89,54 @@ namespace osu.Game.Online.API.Requests.Responses /// A ruleset store, used to populate a ruleset instance in the returned score. /// An optional beatmap, copied into the returned score (for cases where the API does not populate the beatmap). /// - public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) + public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo? beatmap = null) { - var ruleset = rulesets.GetRuleset(ruleset_id) ?? throw new InvalidOperationException($"Ruleset with ID of {ruleset_id} not found locally"); + var ruleset = rulesets.GetRuleset(RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {RulesetID} not found locally"); var rulesetInstance = ruleset.CreateInstance(); - var modInstances = mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); + var modInstances = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); // all API scores provided by this class are considered to be legacy. modInstances = modInstances.Append(rulesetInstance.CreateMod()).ToArray(); var scoreInfo = new ScoreInfo { - User = user ?? new APIUser { Id = user_id }, - BeatmapInfo = beatmap ?? new BeatmapInfo { OnlineID = beatmap_id }, - Passed = passed, - TotalScore = total_score, - Accuracy = accuracy, - MaxCombo = max_combo, - Rank = rank, - Statistics = Statistics, OnlineID = OnlineID, - Date = ended_at ?? DateTimeOffset.Now, - // PP = - Hash = "online", // TODO: temporary? + User = User ?? new APIUser { Id = UserID }, + BeatmapInfo = beatmap ?? new BeatmapInfo { OnlineID = BeatmapID }, Ruleset = ruleset, + Passed = Passed, + TotalScore = TotalScore, + Accuracy = Accuracy, + MaxCombo = MaxCombo, + Rank = Rank, + Statistics = Statistics, + Date = EndedAt ?? DateTimeOffset.Now, + Hash = "online", // TODO: temporary? Mods = modInstances, }; return scoreInfo; } - public ScoreInfo CreateScoreInfo(Mod[] mods) => new ScoreInfo + public ScoreInfo ToScoreInfo(Mod[] mods) => new ScoreInfo { - OnlineID = id, - User = new APIUser { Id = user_id }, - BeatmapInfo = new BeatmapInfo { OnlineID = beatmap_id }, - Ruleset = new RulesetInfo { OnlineID = ruleset_id }, - Passed = passed, - TotalScore = total_score, - Accuracy = accuracy, - MaxCombo = max_combo, - Rank = rank, + OnlineID = ID, + User = new APIUser { Id = UserID }, + BeatmapInfo = new BeatmapInfo { OnlineID = BeatmapID }, + Ruleset = new RulesetInfo { OnlineID = RulesetID }, + Passed = Passed, + TotalScore = TotalScore, + Accuracy = Accuracy, + MaxCombo = MaxCombo, + Rank = Rank, + Statistics = Statistics, + Date = EndedAt ?? DateTimeOffset.Now, + Hash = "online", // TODO: temporary? Mods = mods, - Statistics = Statistics }; - public long OnlineID => id; + public long OnlineID => ID; } } From f956955d4d66a6955cafdf066d1c4034b46c64ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 17:14:31 +0900 Subject: [PATCH 493/803] Combine `ScoreInfo` construction helper methods --- .../API/Requests/Responses/SoloScoreInfo.cs | 33 ++++++++----------- .../BeatmapSet/Scores/ScoresContainer.cs | 2 +- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 2 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 5dce2db6ed..71c6c3adab 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -89,40 +89,33 @@ namespace osu.Game.Online.API.Requests.Responses /// A ruleset store, used to populate a ruleset instance in the returned score. /// An optional beatmap, copied into the returned score (for cases where the API does not populate the beatmap). /// - public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo? beatmap = null) + public ScoreInfo ToScoreInfo(RulesetStore rulesets, BeatmapInfo? beatmap = null) { var ruleset = rulesets.GetRuleset(RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {RulesetID} not found locally"); var rulesetInstance = ruleset.CreateInstance(); - var modInstances = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); + var mods = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); // all API scores provided by this class are considered to be legacy. - modInstances = modInstances.Append(rulesetInstance.CreateMod()).ToArray(); + mods = mods.Append(rulesetInstance.CreateMod()).ToArray(); - var scoreInfo = new ScoreInfo - { - OnlineID = OnlineID, - User = User ?? new APIUser { Id = UserID }, - BeatmapInfo = beatmap ?? new BeatmapInfo { OnlineID = BeatmapID }, - Ruleset = ruleset, - Passed = Passed, - TotalScore = TotalScore, - Accuracy = Accuracy, - MaxCombo = MaxCombo, - Rank = Rank, - Statistics = Statistics, - Date = EndedAt ?? DateTimeOffset.Now, - Hash = "online", // TODO: temporary? - Mods = modInstances, - }; + var scoreInfo = ToScoreInfo(mods); + + scoreInfo.Ruleset = ruleset; + if (beatmap != null) scoreInfo.BeatmapInfo = beatmap; return scoreInfo; } + /// + /// Create a from an API score instance. + /// + /// The mod instances, resolved from a ruleset. + /// public ScoreInfo ToScoreInfo(Mod[] mods) => new ScoreInfo { - OnlineID = ID, + OnlineID = OnlineID, User = new APIUser { Id = UserID }, BeatmapInfo = new BeatmapInfo { OnlineID = BeatmapID }, Ruleset = new RulesetInfo { OnlineID = RulesetID }, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 98202ba7c0..e50fc356eb 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -87,7 +87,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores MD5Hash = apiBeatmap.MD5Hash }; - scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets, beatmapInfo)).ToArray(), loadCancellationSource.Token) + scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.ToScoreInfo(rulesets, beatmapInfo)).ToArray(), loadCancellationSource.Token) .ContinueWith(task => Schedule(() => { if (loadCancellationSource.IsCancellationRequested) diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 4813cd65db..4312c9528f 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Ranking return null; getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset); - getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineID).Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value.BeatmapInfo))); + getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineID).Select(s => s.ToScoreInfo(rulesets, Beatmap.Value.BeatmapInfo))); return getScoreRequest; } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 43eaff56b3..b497943dfa 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), cancellationToken) + scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), cancellationToken) .ContinueWith(task => Schedule(() => { if (cancellationToken.IsCancellationRequested) From 12a56e36bd69d5ad5a4a90db476cab75f5eefacb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 17:46:58 +0900 Subject: [PATCH 494/803] Fix `ID` mapping and move osu-web additions to region to identify them clearly --- .../API/Requests/Responses/SoloScoreInfo.cs | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 71c6c3adab..dea0de4608 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -18,9 +18,8 @@ namespace osu.Game.Online.API.Requests.Responses [Serializable] public class SoloScoreInfo : IHasOnlineID { - [JsonIgnore] - [JsonProperty(" ")] - public long ID { get; set; } + [JsonProperty("replay")] + public bool HasReplay { get; set; } [JsonProperty("beatmap_id")] public int BeatmapID { get; set; } @@ -43,12 +42,6 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("user_id")] public int UserID { get; set; } - /// - /// User may be provided via an osu-web response, but will not be present from raw database json. - /// - [JsonProperty("user")] - public APIUser? User { get; set; } - // TODO: probably want to update this column to match user stats (short)? [JsonProperty("max_combo")] public int MaxCombo { get; set; } @@ -81,6 +74,19 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("statistics")] public Dictionary Statistics { get; set; } = new Dictionary(); + #region osu-web API additions (not stored to database). + + [JsonProperty("id")] + public long? ID { get; set; } + + [JsonProperty("user")] + public APIUser? User { get; set; } + + [JsonProperty("pp")] + public double? PP { get; set; } + + #endregion + public override string ToString() => $"score_id: {ID} user_id: {UserID}"; /// @@ -116,7 +122,7 @@ namespace osu.Game.Online.API.Requests.Responses public ScoreInfo ToScoreInfo(Mod[] mods) => new ScoreInfo { OnlineID = OnlineID, - User = new APIUser { Id = UserID }, + User = User ?? new APIUser { Id = UserID }, BeatmapInfo = new BeatmapInfo { OnlineID = BeatmapID }, Ruleset = new RulesetInfo { OnlineID = RulesetID }, Passed = Passed, @@ -127,9 +133,11 @@ namespace osu.Game.Online.API.Requests.Responses Statistics = Statistics, Date = EndedAt ?? DateTimeOffset.Now, Hash = "online", // TODO: temporary? + HasReplay = HasReplay, Mods = mods, + PP = PP, }; - public long OnlineID => ID; + public long OnlineID => ID ?? -1; } } From 0fe3bac1739b2f5961489292a5702783d39dad38 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 17:51:20 +0900 Subject: [PATCH 495/803] Store mods to array and update test scenes --- .../Visual/Online/TestSceneScoresContainer.cs | 49 ++++++++++--------- .../API/Requests/Responses/SoloScoreInfo.cs | 2 +- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 16a34e996f..b772a28194 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -18,6 +18,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Users; using osuTK.Graphics; @@ -146,12 +147,12 @@ namespace osu.Game.Tests.Visual.Online { var scores = new APIScoresCollection { - Scores = new List + Scores = new List { - new APIScore + new SoloScoreInfo { - Date = DateTimeOffset.Now, - OnlineID = onlineID++, + EndedAt = DateTimeOffset.Now, + ID = onlineID++, User = new APIUser { Id = 6602580, @@ -175,10 +176,10 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234567890, Accuracy = 1, }, - new APIScore + new SoloScoreInfo { - Date = DateTimeOffset.Now, - OnlineID = onlineID++, + EndedAt = DateTimeOffset.Now, + ID = onlineID++, User = new APIUser { Id = 4608074, @@ -201,10 +202,10 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234789, Accuracy = 0.9997, }, - new APIScore + new SoloScoreInfo { - Date = DateTimeOffset.Now, - OnlineID = onlineID++, + EndedAt = DateTimeOffset.Now, + ID = onlineID++, User = new APIUser { Id = 1014222, @@ -226,10 +227,10 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 12345678, Accuracy = 0.9854, }, - new APIScore + new SoloScoreInfo { - Date = DateTimeOffset.Now, - OnlineID = onlineID++, + EndedAt = DateTimeOffset.Now, + ID = onlineID++, User = new APIUser { Id = 1541390, @@ -250,10 +251,10 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234567, Accuracy = 0.8765, }, - new APIScore + new SoloScoreInfo { - Date = DateTimeOffset.Now, - OnlineID = onlineID++, + EndedAt = DateTimeOffset.Now, + ID = onlineID++, User = new APIUser { Id = 7151382, @@ -275,12 +276,12 @@ namespace osu.Game.Tests.Visual.Online foreach (var s in scores.Scores) { - s.Statistics = new Dictionary + s.Statistics = new Dictionary { - { "count_300", RNG.Next(2000) }, - { "count_100", RNG.Next(2000) }, - { "count_50", RNG.Next(2000) }, - { "count_miss", RNG.Next(2000) } + { HitResult.Great, RNG.Next(2000) }, + { HitResult.Good, RNG.Next(2000) }, + { HitResult.Meh, RNG.Next(2000) }, + { HitResult.Miss, RNG.Next(2000) } }; } @@ -289,10 +290,10 @@ namespace osu.Game.Tests.Visual.Online private APIScoreWithPosition createUserBest() => new APIScoreWithPosition { - Score = new APIScore + Score = new SoloScoreInfo { - Date = DateTimeOffset.Now, - OnlineID = onlineID++, + EndedAt = DateTimeOffset.Now, + ID = onlineID++, User = new APIUser { Id = 7151382, diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index dea0de4608..b70da194a5 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -57,7 +57,7 @@ namespace osu.Game.Online.API.Requests.Responses public DateTimeOffset? EndedAt { get; set; } [JsonProperty("mods")] - public List Mods { get; set; } = new List(); + public APIMod[] Mods { get; set; } = Array.Empty(); [JsonIgnore] [JsonProperty("created_at")] From 363e23c2518b9c8bf21f99beec65ddbbc894e062 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 12 Jul 2022 18:47:43 +0900 Subject: [PATCH 496/803] Use correct HitResult in test --- osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index b772a28194..be03328caa 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -279,7 +279,7 @@ namespace osu.Game.Tests.Visual.Online s.Statistics = new Dictionary { { HitResult.Great, RNG.Next(2000) }, - { HitResult.Good, RNG.Next(2000) }, + { HitResult.Ok, RNG.Next(2000) }, { HitResult.Meh, RNG.Next(2000) }, { HitResult.Miss, RNG.Next(2000) } }; From b96734e31a9184b2a636b5435d122d7d5b3d32df Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 12 Jul 2022 08:43:48 -0400 Subject: [PATCH 497/803] fix mod incompatibility between repel and relax --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 5c1de83972..2cf8c278ca 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset, IApplicableToPlayer { public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAlternate) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate) }).ToArray(); /// /// How early before a hitobject's start time to trigger a hit. From c04658584285fb302d8df96e56279ba1fe59a806 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 12 Jul 2022 18:29:17 +0300 Subject: [PATCH 498/803] Fix unsupported OS message stating Windows 8 to be supported --- osu.Desktop/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 66b361cb73..cebbcb40b7 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -39,7 +39,7 @@ namespace osu.Desktop { SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, "Your operating system is too old to run osu!", - "This version of osu! requires at least Windows 8 to run.\nPlease upgrade your operating system or consider using an older version of osu!.", IntPtr.Zero); + "This version of osu! requires at least Windows 8.1 to run.\nPlease upgrade your operating system or consider using an older version of osu!.", IntPtr.Zero); return; } From f90f93a43cb8110f1b4555af4416ff165cf0bdde Mon Sep 17 00:00:00 2001 From: James <51536154+tsunyoku@users.noreply.github.com> Date: Tue, 12 Jul 2022 23:07:10 +0100 Subject: [PATCH 499/803] abstract OsuModAlternate into InputBlockingMod --- .../Mods/InputBlockingMod.cs | 106 ++++++++++++++++++ osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 97 +--------------- 2 files changed, 112 insertions(+), 91 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs new file mode 100644 index 0000000000..b86e06ec5e --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs @@ -0,0 +1,106 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; +using osu.Game.Utils; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public abstract class InputBlockingMod : Mod, IApplicableToDrawableRuleset + { + public override double ScoreMultiplier => 1.0; + public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) }; + public override ModType Type => ModType.Conversion; + + protected const double FLASH_DURATION = 1000; + + /// + /// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods). + /// + /// + /// This is different from in that the periods here end strictly at the first object after the break, rather than the break's end time. + /// + protected PeriodTracker NonGameplayPeriods; + + protected OsuAction? LastActionPressed; + protected DrawableRuleset Ruleset; + + protected IFrameStableClock GameplayClock; + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + Ruleset = drawableRuleset; + drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); + + var periods = new List(); + + if (drawableRuleset.Objects.Any()) + { + periods.Add(new Period(int.MinValue, getValidJudgementTime(Ruleset.Objects.First()) - 1)); + + foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks) + periods.Add(new Period(b.StartTime, getValidJudgementTime(Ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1)); + + static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh); + } + + NonGameplayPeriods = new PeriodTracker(periods); + + GameplayClock = drawableRuleset.FrameStableClock; + } + + protected virtual bool CheckCorrectAction(OsuAction action) + { + if (NonGameplayPeriods.IsInAny(GameplayClock.CurrentTime)) + { + LastActionPressed = null; + return true; + } + + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + break; + + // Any action which is not left or right button should be ignored. + default: + return true; + } + + return false; + } + + private class InputInterceptor : Component, IKeyBindingHandler + { + private readonly InputBlockingMod mod; + + public InputInterceptor(InputBlockingMod mod) + { + this.mod = mod; + } + + public bool OnPressed(KeyBindingPressEvent e) + // if the pressed action is incorrect, block it from reaching gameplay. + => !mod.CheckCorrectAction(e.Action); + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 622d2df432..2ad9789799 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -3,117 +3,32 @@ #nullable disable -using System; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Beatmaps.Timing; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; -using osu.Game.Utils; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModAlternate : Mod, IApplicableToDrawableRuleset + public class OsuModAlternate : InputBlockingMod { public override string Name => @"Alternate"; public override string Acronym => @"AL"; public override string Description => @"Don't use the same key twice in a row!"; - public override double ScoreMultiplier => 1.0; - public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) }; - public override ModType Type => ModType.Conversion; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; - private const double flash_duration = 1000; - - /// - /// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods). - /// - /// - /// This is different from in that the periods here end strictly at the first object after the break, rather than the break's end time. - /// - private PeriodTracker nonGameplayPeriods; - - private OsuAction? lastActionPressed; - private DrawableRuleset ruleset; - - private IFrameStableClock gameplayClock; - - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + protected override bool CheckCorrectAction(OsuAction action) { - ruleset = drawableRuleset; - drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); - - var periods = new List(); - - if (drawableRuleset.Objects.Any()) - { - periods.Add(new Period(int.MinValue, getValidJudgementTime(ruleset.Objects.First()) - 1)); - - foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks) - periods.Add(new Period(b.StartTime, getValidJudgementTime(ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1)); - - static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh); - } - - nonGameplayPeriods = new PeriodTracker(periods); - - gameplayClock = drawableRuleset.FrameStableClock; - } - - private bool checkCorrectAction(OsuAction action) - { - if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime)) - { - lastActionPressed = null; + if (base.CheckCorrectAction(action)) return true; - } - switch (action) - { - case OsuAction.LeftButton: - case OsuAction.RightButton: - break; - - // Any action which is not left or right button should be ignored. - default: - return true; - } - - if (lastActionPressed != action) + if (LastActionPressed != action) { // User alternated correctly. - lastActionPressed = action; + LastActionPressed = action; return true; } - ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint); + Ruleset.Cursor.FlashColour(Colour4.Red, FLASH_DURATION, Easing.OutQuint); return false; } - - private class InputInterceptor : Component, IKeyBindingHandler - { - private readonly OsuModAlternate mod; - - public InputInterceptor(OsuModAlternate mod) - { - this.mod = mod; - } - - public bool OnPressed(KeyBindingPressEvent e) - // if the pressed action is incorrect, block it from reaching gameplay. - => !mod.checkCorrectAction(e.Action); - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - } } } From c05263c3c39e5828d320ba5d11bac9b2ec1436d1 Mon Sep 17 00:00:00 2001 From: James <51536154+tsunyoku@users.noreply.github.com> Date: Tue, 12 Jul 2022 23:07:26 +0100 Subject: [PATCH 500/803] add Single Tap mod --- osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs | 38 +++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs new file mode 100644 index 0000000000..22211b70c1 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.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. + +#nullable disable + +using osu.Framework.Graphics; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModSingleTap : InputBlockingMod + { + public override string Name => @"Single Tap"; + public override string Acronym => @"ST"; + public override string Description => @"You must only use one key!"; + + protected override bool CheckCorrectAction(OsuAction action) + { + if (base.CheckCorrectAction(action)) + return true; + + if (LastActionPressed == null) + { + // First keypress, store the expected action. + LastActionPressed = action; + return true; + } + + if (LastActionPressed == action) + { + // User singletapped correctly. + return true; + } + + Ruleset.Cursor.FlashColour(Colour4.Red, FLASH_DURATION, Easing.OutQuint); + return false; + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index ba0ef9ec3a..302194e91a 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Osu new OsuModClassic(), new OsuModRandom(), new OsuModMirror(), - new OsuModAlternate(), + new MultiMod(new OsuModAlternate(), new OsuModSingleTap()) }; case ModType.Automation: From 20d2b8619327632c76fc8fc7b654a0b2a3e12c27 Mon Sep 17 00:00:00 2001 From: James <51536154+tsunyoku@users.noreply.github.com> Date: Tue, 12 Jul 2022 23:07:41 +0100 Subject: [PATCH 501/803] make Single Tap incompatible with Autoplay, Cinema and Relax --- osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index 490b5b7a9d..c4de77b8a3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModAutoplay : ModAutoplay { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray(); public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) => new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" }); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs index 656cf95e77..704b922ee5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModCinema : ModCinema { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray(); public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) => new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" }); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 2cf8c278ca..2030156f2e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset, IApplicableToPlayer { public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray(); /// /// How early before a hitobject's start time to trigger a hit. From 886efbcbdfb06cde32eedcd277dca18b11a874bf Mon Sep 17 00:00:00 2001 From: James <51536154+tsunyoku@users.noreply.github.com> Date: Tue, 12 Jul 2022 23:08:00 +0100 Subject: [PATCH 502/803] add test scene for Single Tap mod --- .../Mods/TestSceneOsuModSingleTap.cs | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs new file mode 100644 index 0000000000..0fe35fac0b --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs @@ -0,0 +1,177 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModSingleTap : OsuModTestScene + { + [Test] + public void TestInputSingular() => CreateModTest(new ModTestData + { + Mod = new OsuModSingleTap(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 2, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 500, + Position = new Vector2(100), + }, + new HitCircle + { + StartTime = 1000, + Position = new Vector2(200, 100), + }, + new HitCircle + { + StartTime = 1500, + Position = new Vector2(300, 100), + }, + new HitCircle + { + StartTime = 2000, + Position = new Vector2(400, 100), + }, + }, + }, + ReplayFrames = new List + { + new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(100)), + new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.LeftButton), + } + }); + + [Test] + public void TestInputAlternating() => CreateModTest(new ModTestData + { + Mod = new OsuModSingleTap(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 500, + Position = new Vector2(100), + }, + new HitCircle + { + StartTime = 1000, + Position = new Vector2(200, 100), + }, + }, + }, + ReplayFrames = new List + { + new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(100)), + new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.RightButton), + new OsuReplayFrame(1001, new Vector2(200, 100)), + new OsuReplayFrame(1500, new Vector2(300, 100), OsuAction.LeftButton), + new OsuReplayFrame(1501, new Vector2(300, 100)), + new OsuReplayFrame(2000, new Vector2(400, 100), OsuAction.RightButton), + new OsuReplayFrame(2001, new Vector2(400, 100)), + } + }); + + /// + /// Ensures singletapping is reset before the first hitobject after intro. + /// + [Test] + public void TestInputAlternatingAtIntro() => CreateModTest(new ModTestData + { + Mod = new OsuModSingleTap(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 1, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 1000, + Position = new Vector2(100), + }, + }, + }, + ReplayFrames = new List + { + // first press during intro. + new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(200)), + // press different key at hitobject and ensure it has been hit. + new OsuReplayFrame(1000, new Vector2(100), OsuAction.RightButton), + } + }); + + /// + /// Ensures singletapping is reset before the first hitobject after a break. + /// + [Test] + public void TestInputAlternatingWithBreak() => CreateModTest(new ModTestData + { + Mod = new OsuModSingleTap(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2, + Autoplay = false, + Beatmap = new Beatmap + { + Breaks = new List + { + new BreakPeriod(500, 2000), + }, + HitObjects = new List + { + new HitCircle + { + StartTime = 500, + Position = new Vector2(100), + }, + new HitCircle + { + StartTime = 2500, + Position = new Vector2(500, 100), + }, + new HitCircle + { + StartTime = 3000, + Position = new Vector2(500, 100), + }, + } + }, + ReplayFrames = new List + { + // first press to start singletap lock. + new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(100)), + // press different key after break but before hit object. + new OsuReplayFrame(2250, new Vector2(300, 100), OsuAction.RightButton), + new OsuReplayFrame(2251, new Vector2(300, 100)), + // press same key at second hitobject and ensure it has been hit. + new OsuReplayFrame(2500, new Vector2(500, 100), OsuAction.LeftButton), + new OsuReplayFrame(2501, new Vector2(500, 100)), + // press different key at third hitobject and ensure it has been missed. + new OsuReplayFrame(3000, new Vector2(500, 100), OsuAction.RightButton), + new OsuReplayFrame(3001, new Vector2(500, 100)), + } + }); + } +} From e9b0a3e4fafeb42d3dc45afc07b8afc5fc7a8d75 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Wed, 13 Jul 2022 07:35:53 +0100 Subject: [PATCH 503/803] make alternate and singletap incompatible with eachother --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 3 +++ osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 2ad9789799..cc4591562e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -3,6 +3,8 @@ #nullable disable +using System; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; @@ -14,6 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Acronym => @"AL"; public override string Description => @"Don't use the same key twice in a row!"; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray(); protected override bool CheckCorrectAction(OsuAction action) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs index 22211b70c1..b2b6e11f48 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs @@ -3,6 +3,8 @@ #nullable disable +using System; +using System.Linq; using osu.Framework.Graphics; namespace osu.Game.Rulesets.Osu.Mods @@ -12,6 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Name => @"Single Tap"; public override string Acronym => @"ST"; public override string Description => @"You must only use one key!"; + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray(); protected override bool CheckCorrectAction(OsuAction action) { From 6755a771b4adab14885aa9e2cf3fc1ffcaaf59a5 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Wed, 13 Jul 2022 07:49:08 +0100 Subject: [PATCH 504/803] make Cinema incompatible with InputBlockingMod --- osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs index b86e06ec5e..ee6f072c4b 100644 --- a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public abstract class InputBlockingMod : Mod, IApplicableToDrawableRuleset { public override double ScoreMultiplier => 1.0; - public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) }; + public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(OsuModCinema) }; public override ModType Type => ModType.Conversion; protected const double FLASH_DURATION = 1000; From 27ef7fc78eda72a56fda70003f5f60e7d51430b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 16:22:50 +0900 Subject: [PATCH 505/803] Add log output for custom storage usage Sometimes I am not sure where my osu! is reading files from. This should help somewhat. ```csharp /Users/dean/Projects/osu/osu.Desktop/bin/Debug/net6.0/osu! [runtime] 2022-07-13 07:22:03 [verbose]: Starting legacy IPC provider... [runtime] 2022-07-13 07:22:03 [verbose]: Attempting to use custom storage location /Users/dean/Games/osu-lazer-2 [runtime] 2022-07-13 07:22:03 [verbose]: Storage successfully changed to /Users/dean/Games/osu-lazer-2. [runtime] 2022-07-13 07:22:05 [verbose]: GL Initialized ``` --- osu.Game/IO/OsuStorage.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 89bdd09f0d..368ac56850 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -94,6 +94,8 @@ namespace osu.Game.IO error = OsuStorageError.None; Storage lastStorage = UnderlyingStorage; + Logger.Log($"Attempting to use custom storage location {CustomStoragePath}"); + try { Storage userStorage = host.GetStorage(CustomStoragePath); @@ -102,6 +104,7 @@ namespace osu.Game.IO error = OsuStorageError.AccessibleButEmpty; ChangeTargetStorage(userStorage); + Logger.Log($"Storage successfully changed to {CustomStoragePath}."); } catch { @@ -109,6 +112,9 @@ namespace osu.Game.IO ChangeTargetStorage(lastStorage); } + if (error != OsuStorageError.None) + Logger.Log($"Custom storage location could not be used ({error})."); + return error == OsuStorageError.None; } From 8820ea4006b1bd2119071a51fa5d1c786ff26014 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 16:36:43 +0900 Subject: [PATCH 506/803] Add last played date to `BeatmapInfo` --- osu.Game/Beatmaps/BeatmapInfo.cs | 5 +++++ osu.Game/Database/RealmAccess.cs | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 346bf86818..3b851e05c0 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -110,6 +110,11 @@ namespace osu.Game.Beatmaps public bool SamplesMatchPlaybackRate { get; set; } = true; + /// + /// The time at which this beatmap was last played by the local user. + /// + public DateTimeOffset? LastPlayed { get; set; } + /// /// The ratio of distance travelled per time unit. /// Generally used to decouple the spacing between hit objects from the enforced "velocity" of the beatmap (see ). diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 8cf57b802b..02b5a51f1f 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -58,8 +58,9 @@ namespace osu.Game.Database /// 12 2021-11-24 Add Status to RealmBeatmapSet. /// 13 2022-01-13 Final migration of beatmaps and scores to realm (multiple new storage fields). /// 14 2022-03-01 Added BeatmapUserSettings to BeatmapInfo. + /// 15 2022-07-13 Added LastPlayed to BeatmapInfo. /// - private const int schema_version = 14; + private const int schema_version = 15; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. From 4b96d74b0cd7d800b0e6cdbe9caffc00a8e3ec6c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 16:43:39 +0900 Subject: [PATCH 507/803] Add test coverage of `LastPlayed` updating --- .../Gameplay/TestScenePlayerLocalScoreImport.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index bfc06c0ee0..5ec9e88728 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -59,6 +60,20 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool AllowFail => false; + [Test] + public void TestLastPlayedUpdated() + { + DateTimeOffset? getLastPlayed() => Realm.Run(r => r.Find(Beatmap.Value.BeatmapInfo.ID)?.LastPlayed); + + AddStep("set no custom ruleset", () => customRuleset = null); + AddAssert("last played is null", () => getLastPlayed() == null); + + CreateTest(); + + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + AddUntilStep("wait for last played to update", () => getLastPlayed() != null); + } + [Test] public void TestScoreStoredLocally() { From ab3ec80159f6b20e9eb0c470287fb1562267f971 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 16:36:55 +0900 Subject: [PATCH 508/803] Update `LastPlayed` on gameplay starting in a `SubmittingPlayer` --- osu.Game/Screens/Play/SubmittingPlayer.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index c916791eaa..f3bc0ea798 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -11,6 +11,8 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Scoring; @@ -117,6 +119,23 @@ namespace osu.Game.Screens.Play await submitScore(score).ConfigureAwait(false); } + [Resolved] + private RealmAccess realm { get; set; } + + protected override void StartGameplay() + { + base.StartGameplay(); + + // User expectation is that last played should be updated when entering the gameplay loop + // from multiplayer / playlists / solo, even when using autoplay mod. + realm.WriteAsync(r => + { + var realmBeatmap = r.Find(Beatmap.Value.BeatmapInfo.ID); + if (realmBeatmap != null) + realmBeatmap.LastPlayed = DateTimeOffset.Now; + }); + } + public override bool OnExiting(ScreenExitEvent e) { bool exiting = base.OnExiting(e); From fc274629f8e574ac70dcc0895e6762b67a4a418a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 16:37:05 +0900 Subject: [PATCH 509/803] Add "last played" sort mode to song select Note that this will consider the most recent play of any beatmap in beatmap set groups for now, similar to other sort methods. --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 3 +++ osu.Game/Screens/Select/Filter/SortMode.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 36ec536780..94d911692c 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -81,6 +81,9 @@ namespace osu.Game.Screens.Select.Carousel case SortMode.DateAdded: return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded); + case SortMode.LastPlayed: + return -compareUsingAggregateMax(otherSet, b => (b.LastPlayed ?? DateTimeOffset.MinValue).ToUnixTimeSeconds()); + case SortMode.BPM: return compareUsingAggregateMax(otherSet, b => b.BPM); diff --git a/osu.Game/Screens/Select/Filter/SortMode.cs b/osu.Game/Screens/Select/Filter/SortMode.cs index 48f774393e..4227114618 100644 --- a/osu.Game/Screens/Select/Filter/SortMode.cs +++ b/osu.Game/Screens/Select/Filter/SortMode.cs @@ -23,6 +23,9 @@ namespace osu.Game.Screens.Select.Filter [Description("Date Added")] DateAdded, + [Description("Last Played")] + LastPlayed, + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingDifficulty))] Difficulty, From 11c8a2c16e1e79737308731a519e44b0c978cd67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 17:34:33 +0900 Subject: [PATCH 510/803] Disable tournament client "save changes" button when there's no changes to save --- osu.Game.Tournament/SaveChangesOverlay.cs | 101 ++++++++++++++++++++++ osu.Game.Tournament/TournamentGame.cs | 37 +------- osu.Game.Tournament/TournamentGameBase.cs | 17 ++-- osu.Game.Tournament/TourneyButton.cs | 3 + 4 files changed, 117 insertions(+), 41 deletions(-) create mode 100644 osu.Game.Tournament/SaveChangesOverlay.cs diff --git a/osu.Game.Tournament/SaveChangesOverlay.cs b/osu.Game.Tournament/SaveChangesOverlay.cs new file mode 100644 index 0000000000..03a3f2d3a4 --- /dev/null +++ b/osu.Game.Tournament/SaveChangesOverlay.cs @@ -0,0 +1,101 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Online.Multiplayer; +using osuTK; + +namespace osu.Game.Tournament +{ + internal class SaveChangesOverlay : CompositeDrawable + { + [Resolved] + private TournamentGame tournamentGame { get; set; } + + private string lastSerialisedLadder; + private readonly TourneyButton saveChangesButton; + + public SaveChangesOverlay() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = new Container + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Position = new Vector2(5), + CornerRadius = 10, + Masking = true, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = OsuColour.Gray(0.2f), + RelativeSizeAxes = Axes.Both, + }, + saveChangesButton = new TourneyButton + { + Text = "Save Changes", + Width = 140, + Height = 50, + Padding = new MarginPadding + { + Top = 10, + Left = 10, + }, + Margin = new MarginPadding + { + Right = 10, + Bottom = 10, + }, + Action = saveChanges, + }, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + scheduleNextCheck(); + } + + private async Task checkForChanges() + { + string serialisedLadder = await Task.Run(() => tournamentGame.GetSerialisedLadder()); + + // If a save hasn't been triggered by the user yet, populate the initial value + lastSerialisedLadder ??= serialisedLadder; + + if (lastSerialisedLadder != serialisedLadder && !saveChangesButton.Enabled.Value) + { + saveChangesButton.Enabled.Value = true; + saveChangesButton.Background + .FadeColour(saveChangesButton.BackgroundColour.Lighten(0.5f), 500, Easing.In).Then() + .FadeColour(saveChangesButton.BackgroundColour, 500, Easing.Out) + .Loop(); + } + + scheduleNextCheck(); + } + + private void scheduleNextCheck() => Scheduler.AddDelayed(() => checkForChanges().FireAndForget(), 1000); + + private void saveChanges() + { + tournamentGame.SaveChanges(); + lastSerialisedLadder = tournamentGame.GetSerialisedLadder(); + + saveChangesButton.Enabled.Value = false; + saveChangesButton.Background.FadeColour(saveChangesButton.BackgroundColour, 500); + } + } +} diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 537fbfc038..7d67bfa759 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -11,8 +11,6 @@ using osu.Framework.Configuration; 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.Input.Handlers.Mouse; using osu.Framework.Logging; using osu.Framework.Platform; @@ -20,11 +18,11 @@ using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Tournament.Models; -using osuTK; using osuTK.Graphics; namespace osu.Game.Tournament { + [Cached] public class TournamentGame : TournamentGameBase { public static ColourInfo GetTeamColour(TeamColour teamColour) => teamColour == TeamColour.Red ? COLOUR_RED : COLOUR_BLUE; @@ -78,40 +76,9 @@ namespace osu.Game.Tournament LoadComponentsAsync(new[] { - new Container + new SaveChangesOverlay { - CornerRadius = 10, Depth = float.MinValue, - Position = new Vector2(5), - Masking = true, - AutoSizeAxes = Axes.Both, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Children = new Drawable[] - { - new Box - { - Colour = OsuColour.Gray(0.2f), - RelativeSizeAxes = Axes.Both, - }, - new TourneyButton - { - Text = "Save Changes", - Width = 140, - Height = 50, - Padding = new MarginPadding - { - Top = 10, - Left = 10, - }, - Margin = new MarginPadding - { - Right = 10, - Bottom = 10, - }, - Action = SaveChanges, - }, - } }, heightWarning = new WarningBox("Please make the window wider") { diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 75c9f17d4c..f2a35ea5b3 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -295,7 +295,7 @@ namespace osu.Game.Tournament } } - protected virtual void SaveChanges() + public void SaveChanges() { if (!bracketLoadTaskCompletionSource.Task.IsCompletedSuccessfully) { @@ -311,7 +311,16 @@ namespace osu.Game.Tournament .ToList(); // Serialise before opening stream for writing, so if there's a failure it will leave the file in the previous state. - string serialisedLadder = JsonConvert.SerializeObject(ladder, + string serialisedLadder = GetSerialisedLadder(); + + using (var stream = storage.CreateFileSafely(BRACKET_FILENAME)) + using (var sw = new StreamWriter(stream)) + sw.Write(serialisedLadder); + } + + public string GetSerialisedLadder() + { + return JsonConvert.SerializeObject(ladder, new JsonSerializerSettings { Formatting = Formatting.Indented, @@ -319,10 +328,6 @@ namespace osu.Game.Tournament DefaultValueHandling = DefaultValueHandling.Ignore, Converters = new JsonConverter[] { new JsonPointConverter() } }); - - using (var stream = storage.CreateFileSafely(BRACKET_FILENAME)) - using (var sw = new StreamWriter(stream)) - sw.Write(serialisedLadder); } protected override UserInputManager CreateUserInputManager() => new TournamentInputManager(); diff --git a/osu.Game.Tournament/TourneyButton.cs b/osu.Game.Tournament/TourneyButton.cs index f5a82771f5..f1b14df783 100644 --- a/osu.Game.Tournament/TourneyButton.cs +++ b/osu.Game.Tournament/TourneyButton.cs @@ -3,12 +3,15 @@ #nullable disable +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.UserInterface; namespace osu.Game.Tournament { public class TourneyButton : OsuButton { + public new Box Background => base.Background; + public TourneyButton() : base(null) { From b9ad90ce54ecf92709de51df29642d38a0bc6783 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 17:57:45 +0900 Subject: [PATCH 511/803] Switch `TeamWinScreen` scheduling to `AddOnce` --- osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index 07557674e8..ac54ff58f5 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tournament.Screens.TeamWin private bool firstDisplay = true; - private void update() => Schedule(() => + private void update() => Scheduler.AddOnce(() => { var match = CurrentMatch.Value; From 4dff999ce665b20cd87b1fdb7064863fe8da6286 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 18:09:54 +0900 Subject: [PATCH 512/803] Fix potential null referenced in `SeedingScreen` Also ensure that any update operations only occur when the seeding screen is displayed. They were running in the background until now. --- osu.Game.Tournament/Models/SeedingBeatmap.cs | 4 +--- .../Screens/TeamIntro/SeedingScreen.cs | 13 ++++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament/Models/SeedingBeatmap.cs b/osu.Game.Tournament/Models/SeedingBeatmap.cs index 03beb7ca9a..fb0e20556c 100644 --- a/osu.Game.Tournament/Models/SeedingBeatmap.cs +++ b/osu.Game.Tournament/Models/SeedingBeatmap.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Newtonsoft.Json; using osu.Framework.Bindables; @@ -13,7 +11,7 @@ namespace osu.Game.Tournament.Models public int ID; [JsonProperty("BeatmapInfo")] - public TournamentBeatmap Beatmap; + public TournamentBeatmap? Beatmap; public long Score; diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index 719e0384d3..4970d520af 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro currentTeam.BindValueChanged(teamChanged, true); } - private void teamChanged(ValueChangedEvent team) + private void teamChanged(ValueChangedEvent team) => Scheduler.AddOnce(() => { if (team.NewValue == null) { @@ -78,7 +78,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro } showTeam(team.NewValue); - } + }); protected override void CurrentMatchChanged(ValueChangedEvent match) { @@ -120,8 +120,14 @@ namespace osu.Game.Tournament.Screens.TeamIntro foreach (var seeding in team.SeedingResults) { fill.Add(new ModRow(seeding.Mod.Value, seeding.Seed.Value)); + foreach (var beatmap in seeding.Beatmaps) + { + if (beatmap.Beatmap == null) + continue; + fill.Add(new BeatmapScoreRow(beatmap)); + } } } @@ -157,7 +163,8 @@ namespace osu.Game.Tournament.Screens.TeamIntro Children = new Drawable[] { new TournamentSpriteText { Text = beatmap.Score.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Width = 80 }, - new TournamentSpriteText { Text = "#" + beatmap.Seed.Value.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) }, + new TournamentSpriteText + { Text = "#" + beatmap.Seed.Value.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) }, } }, }; From 1516756d8bfa3de7a26966be4ad083b5b800768c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 18:10:27 +0900 Subject: [PATCH 513/803] Fix team name not updating on `TeamDisplay` immediately --- .../Components/TournamentSpriteTextWithBackground.cs | 3 ++- .../Screens/Gameplay/Components/TeamDisplay.cs | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs b/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs index 0fc3646585..b088670caa 100644 --- a/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs +++ b/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs @@ -12,7 +12,8 @@ namespace osu.Game.Tournament.Components { public class TournamentSpriteTextWithBackground : CompositeDrawable { - protected readonly TournamentSpriteText Text; + public readonly TournamentSpriteText Text; + protected readonly Box Background; public TournamentSpriteTextWithBackground(string text = "") diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs index bb187c9e67..1eceddd871 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs @@ -16,6 +16,10 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components { private readonly TeamScore score; + private readonly TournamentSpriteTextWithBackground teamText; + + private readonly Bindable teamName = new Bindable("???"); + private bool showScore; public bool ShowScore @@ -93,7 +97,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components } } }, - new TournamentSpriteTextWithBackground(team?.FullName.Value ?? "???") + teamText = new TournamentSpriteTextWithBackground { Scale = new Vector2(0.5f), Origin = anchor, @@ -113,6 +117,11 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components updateDisplay(); FinishTransforms(true); + + if (Team != null) + teamName.BindTo(Team.FullName); + + teamName.BindValueChanged(name => teamText.Text.Text = name.NewValue, true); } private void updateDisplay() From 5c6fa2341f8fe3bcfdc02df08652538fd82fd3d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 18:15:59 +0900 Subject: [PATCH 514/803] Fix `TeamScoreDisplay` not tracking team changes properly --- .../Screens/Gameplay/Components/TeamScoreDisplay.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs index ed11f097ed..5ee57e9271 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs @@ -42,6 +42,8 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components currentMatch.BindTo(ladder.CurrentMatch); currentMatch.BindValueChanged(matchChanged); + currentTeam.BindValueChanged(teamChanged); + updateMatch(); } @@ -67,7 +69,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components // team may change to same team, which means score is not in a good state. // thus we handle this manually. - teamChanged(currentTeam.Value); + currentTeam.TriggerChange(); } protected override bool OnMouseDown(MouseDownEvent e) @@ -88,11 +90,11 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components return base.OnMouseDown(e); } - private void teamChanged(TournamentTeam team) + private void teamChanged(ValueChangedEvent team) { InternalChildren = new Drawable[] { - teamDisplay = new TeamDisplay(team, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0), + teamDisplay = new TeamDisplay(team.NewValue, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0), }; } } From 214351a87e3022eed3e929cd866ef4e1511ac553 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 18:32:47 +0900 Subject: [PATCH 515/803] Ensure any changes are committed before changing `LadderEditorSettings`'s target match --- .../Screens/Ladder/Components/LadderEditorSettings.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index f0eda5672a..1fdf616e34 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -53,6 +53,9 @@ namespace osu.Game.Tournament.Screens.Ladder.Components editorInfo.Selected.ValueChanged += selection => { + // ensure any ongoing edits are committed out to the *current* selection before changing to a new one. + GetContainingInputManager().TriggerFocusContention(null); + roundDropdown.Current = selection.NewValue?.Round; losersCheckbox.Current = selection.NewValue?.Losers; dateTimeBox.Current = selection.NewValue?.Date; From 467f83b603dcdba943c3d2f9e8341eaa55127186 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 18:48:12 +0900 Subject: [PATCH 516/803] Add non-null assertion missing in `BeatmapScoreRow` --- osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index 4970d520af..9262cab098 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -135,6 +136,8 @@ namespace osu.Game.Tournament.Screens.TeamIntro { public BeatmapScoreRow(SeedingBeatmap beatmap) { + Debug.Assert(beatmap.Beatmap != null); + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; From 952d97c66e98de0f5e90cc285b329b2abf26463b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 19:02:21 +0900 Subject: [PATCH 517/803] Update comment regarding `LoadTrack` safety --- osu.Game/Screens/Edit/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 2b763415cd..48576b81e2 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -186,7 +186,7 @@ namespace osu.Game.Screens.Edit loadableBeatmap = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value); // required so we can get the track length in EditorClock. - // this is safe as nothing has yet got a reference to this new beatmap. + // this is ONLY safe because the track being provided is a `TrackVirtual` which we don't really care about disposing. loadableBeatmap.LoadTrack(); // this is a bit haphazard, but guards against setting the lease Beatmap bindable if From 6950223a7dd12a3ce84daf2ef48d9ee1e9a07784 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 19:18:38 +0900 Subject: [PATCH 518/803] Fix drawable mutation from disposal thread --- osu.Game/Screens/Select/FooterButtonMods.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 2732b5baa8..c938f58984 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Select Current.BindValueChanged(_ => updateMultiplierText(), true); } - private void updateMultiplierText() + private void updateMultiplierText() => Schedule(() => { double multiplier = Current.Value?.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier) ?? 1; @@ -85,6 +85,6 @@ namespace osu.Game.Screens.Select modDisplay.FadeIn(); else modDisplay.FadeOut(); - } + }); } } From c6b6f41b717be33f1595b9effe4a6a4f8845d038 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 19:40:57 +0900 Subject: [PATCH 519/803] Add test coverage of `AudioEquals` --- .../NonVisual/BeatmapSetInfoEqualityTest.cs | 44 +++++++++++++++++++ osu.Game.Tests/Resources/TestResources.cs | 1 + 2 files changed, 45 insertions(+) diff --git a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs index de23b012c1..c887105da6 100644 --- a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs +++ b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs @@ -4,9 +4,12 @@ #nullable disable using System; +using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Extensions; +using osu.Game.Models; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.NonVisual { @@ -23,6 +26,47 @@ namespace osu.Game.Tests.NonVisual Assert.IsTrue(ourInfo.MatchesOnlineID(otherInfo)); } + [Test] + public void TestAudioEqualityNoFile() + { + var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1); + var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1); + + Assert.AreNotEqual(beatmapSetA, beatmapSetB); + Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single())); + } + + [Test] + public void TestAudioEqualitySameHash() + { + var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1); + var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1); + + addAudioFile(beatmapSetA, "abc"); + addAudioFile(beatmapSetB, "abc"); + + Assert.AreNotEqual(beatmapSetA, beatmapSetB); + Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single())); + } + + [Test] + public void TestAudioEqualityDifferentHash() + { + var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1); + var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1); + + addAudioFile(beatmapSetA); + addAudioFile(beatmapSetB); + + Assert.AreNotEqual(beatmapSetA, beatmapSetB); + Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single())); + } + + private static void addAudioFile(BeatmapSetInfo beatmapSetInfo, string hash = null) + { + beatmapSetInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = hash ?? Guid.NewGuid().ToString() }, "audio.mp3")); + } + [Test] public void TestDatabasedWithDatabased() { diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 91d4eb70e8..41404b2636 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -134,6 +134,7 @@ namespace osu.Game.Tests.Resources DifficultyName = $"{version} {beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", StarRating = diff, Length = length, + BeatmapSet = beatmapSet, BPM = bpm, Hash = Guid.NewGuid().ToString().ComputeMD5Hash(), Ruleset = rulesetInfo, From 1cfdea911b7aec4ce7a354480b1818d61a5cbd33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 19:13:18 +0900 Subject: [PATCH 520/803] Fix audio and background file equality incorrectly comparing `BeatmapSet.Hash` --- osu.Game/Beatmaps/BeatmapInfo.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 346bf86818..531dc7deca 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using Newtonsoft.Json; @@ -151,14 +152,23 @@ namespace osu.Game.Beatmaps public bool AudioEquals(BeatmapInfo? other) => other != null && BeatmapSet != null && other.BeatmapSet != null - && BeatmapSet.Hash == other.BeatmapSet.Hash - && Metadata.AudioFile == other.Metadata.AudioFile; + && compareFiles(this, other, m => m.AudioFile); public bool BackgroundEquals(BeatmapInfo? other) => other != null && BeatmapSet != null && other.BeatmapSet != null - && BeatmapSet.Hash == other.BeatmapSet.Hash - && Metadata.BackgroundFile == other.Metadata.BackgroundFile; + && compareFiles(this, other, m => m.BackgroundFile); + + private static bool compareFiles(BeatmapInfo x, BeatmapInfo y, Func getFilename) + { + Debug.Assert(x.BeatmapSet != null); + Debug.Assert(y.BeatmapSet != null); + + string? fileHashX = x.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(x.BeatmapSet.Metadata))?.File.Hash; + string? fileHashY = y.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(y.BeatmapSet.Metadata))?.File.Hash; + + return fileHashX == fileHashY; + } IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata; IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet; From 2e86e7ccee2e802c01e17182f15145ec436988e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 19:31:53 +0900 Subject: [PATCH 521/803] Add extra steps to `TestExitWithoutSave` to guarantee track type --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index f565ca3ef4..6ad6f0b299 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Screens; @@ -103,6 +104,8 @@ namespace osu.Game.Tests.Visual.Editing */ public void TestAddAudioTrack() { + AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual); + AddAssert("switch track to real track", () => { var setup = Editor.ChildrenOfType().First(); @@ -131,6 +134,7 @@ namespace osu.Game.Tests.Visual.Editing } }); + AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual); AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000); } From 5e6b9b96b01944becc8ae25210fd4348a8d31d4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 22:02:46 +0900 Subject: [PATCH 522/803] Apply NRT to new `InputBlockingMod` class --- osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs index ee6f072c4b..40c621a4be 100644 --- a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -34,12 +32,13 @@ namespace osu.Game.Rulesets.Osu.Mods /// /// This is different from in that the periods here end strictly at the first object after the break, rather than the break's end time. /// - protected PeriodTracker NonGameplayPeriods; + protected PeriodTracker NonGameplayPeriods = null!; + + protected DrawableRuleset Ruleset = null!; + + protected IFrameStableClock GameplayClock = null!; protected OsuAction? LastActionPressed; - protected DrawableRuleset Ruleset; - - protected IFrameStableClock GameplayClock; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { From 33dd9562cc1fe03f3f0b625f6b62a9a630072044 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 22:04:57 +0900 Subject: [PATCH 523/803] Privatise some fields --- osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs index 40c621a4be..4541d33579 100644 --- a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs @@ -26,19 +26,19 @@ namespace osu.Game.Rulesets.Osu.Mods protected const double FLASH_DURATION = 1000; + protected DrawableRuleset Ruleset = null!; + + protected OsuAction? LastActionPressed; + /// /// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods). /// /// /// This is different from in that the periods here end strictly at the first object after the break, rather than the break's end time. /// - protected PeriodTracker NonGameplayPeriods = null!; + private PeriodTracker nonGameplayPeriods = null!; - protected DrawableRuleset Ruleset = null!; - - protected IFrameStableClock GameplayClock = null!; - - protected OsuAction? LastActionPressed; + private IFrameStableClock gameplayClock = null!; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -57,14 +57,14 @@ namespace osu.Game.Rulesets.Osu.Mods static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh); } - NonGameplayPeriods = new PeriodTracker(periods); + nonGameplayPeriods = new PeriodTracker(periods); - GameplayClock = drawableRuleset.FrameStableClock; + gameplayClock = drawableRuleset.FrameStableClock; } protected virtual bool CheckCorrectAction(OsuAction action) { - if (NonGameplayPeriods.IsInAny(GameplayClock.CurrentTime)) + if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime)) { LastActionPressed = null; return true; From be3187c3a44282e0e9f5ce0a705739b1e1c9fa76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 22:05:56 +0900 Subject: [PATCH 524/803] Remove remnant nullable disables --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs | 2 -- 3 files changed, 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs index 0fe35fac0b..1aed84be10 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using NUnit.Framework; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index cc4591562e..e1be8288e3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Graphics; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs index b2b6e11f48..2000adc7d9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Graphics; From 0da1bd393c48b4514071df474e57de6a90dc369b Mon Sep 17 00:00:00 2001 From: James <51536154+tsunyoku@users.noreply.github.com> Date: Wed, 13 Jul 2022 14:26:44 +0100 Subject: [PATCH 525/803] privatise checkCorrectAction, add abstract CheckValidNewAction function --- .../Mods/InputBlockingMod.cs | 13 +++++++++-- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 17 +------------- osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs | 23 +------------------ 3 files changed, 13 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs index 4541d33579..40d05b9475 100644 --- a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs @@ -62,7 +62,9 @@ namespace osu.Game.Rulesets.Osu.Mods gameplayClock = drawableRuleset.FrameStableClock; } - protected virtual bool CheckCorrectAction(OsuAction action) + protected abstract bool CheckValidNewAction(OsuAction action); + + private bool checkCorrectAction(OsuAction action) { if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime)) { @@ -81,6 +83,13 @@ namespace osu.Game.Rulesets.Osu.Mods return true; } + if (CheckValidNewAction(action)) + { + LastActionPressed = action; + return true; + } + + ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint); return false; } @@ -95,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Mods public bool OnPressed(KeyBindingPressEvent e) // if the pressed action is incorrect, block it from reaching gameplay. - => !mod.CheckCorrectAction(e.Action); + => !mod.checkCorrectAction(e.Action); public void OnReleased(KeyBindingReleaseEvent e) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index e1be8288e3..9bc401de67 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; namespace osu.Game.Rulesets.Osu.Mods @@ -16,20 +15,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override IconUsage? Icon => FontAwesome.Solid.Keyboard; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray(); - protected override bool CheckCorrectAction(OsuAction action) - { - if (base.CheckCorrectAction(action)) - return true; - - if (LastActionPressed != action) - { - // User alternated correctly. - LastActionPressed = action; - return true; - } - - Ruleset.Cursor.FlashColour(Colour4.Red, FLASH_DURATION, Easing.OutQuint); - return false; - } + protected override bool CheckValidNewAction(OsuAction action) => LastActionPressed != action; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs index 2000adc7d9..95b798c39e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using osu.Framework.Graphics; namespace osu.Game.Rulesets.Osu.Mods { @@ -14,26 +13,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"You must only use one key!"; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray(); - protected override bool CheckCorrectAction(OsuAction action) - { - if (base.CheckCorrectAction(action)) - return true; - - if (LastActionPressed == null) - { - // First keypress, store the expected action. - LastActionPressed = action; - return true; - } - - if (LastActionPressed == action) - { - // User singletapped correctly. - return true; - } - - Ruleset.Cursor.FlashColour(Colour4.Red, FLASH_DURATION, Easing.OutQuint); - return false; - } + protected override bool CheckValidNewAction(OsuAction action) => LastActionPressed == null || LastActionPressed == action; } } From af0300249590b2ef395375b567cfd8e2b88489e5 Mon Sep 17 00:00:00 2001 From: James <51536154+tsunyoku@users.noreply.github.com> Date: Wed, 13 Jul 2022 14:31:09 +0100 Subject: [PATCH 526/803] make flash duration and ruleset private --- osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs index 40d05b9475..e3bb5f17e9 100644 --- a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs @@ -24,9 +24,9 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(OsuModCinema) }; public override ModType Type => ModType.Conversion; - protected const double FLASH_DURATION = 1000; + private const double flash_duration = 1000; - protected DrawableRuleset Ruleset = null!; + private DrawableRuleset ruleset = null!; protected OsuAction? LastActionPressed; @@ -42,17 +42,17 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - Ruleset = drawableRuleset; + ruleset = drawableRuleset; drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); var periods = new List(); if (drawableRuleset.Objects.Any()) { - periods.Add(new Period(int.MinValue, getValidJudgementTime(Ruleset.Objects.First()) - 1)); + periods.Add(new Period(int.MinValue, getValidJudgementTime(ruleset.Objects.First()) - 1)); foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks) - periods.Add(new Period(b.StartTime, getValidJudgementTime(Ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1)); + periods.Add(new Period(b.StartTime, getValidJudgementTime(ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1)); static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh); } From 937692604edbc919e6a55d6499ca6a0e72bc69c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 22:37:20 +0900 Subject: [PATCH 527/803] Remove mention of autoplay mod for now --- 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 f3bc0ea798..ad63925b93 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Play base.StartGameplay(); // User expectation is that last played should be updated when entering the gameplay loop - // from multiplayer / playlists / solo, even when using autoplay mod. + // from multiplayer / playlists / solo. realm.WriteAsync(r => { var realmBeatmap = r.Find(Beatmap.Value.BeatmapInfo.ID); From 4d9494d3b317a50ec50ed045845bce55203fd560 Mon Sep 17 00:00:00 2001 From: James <51536154+tsunyoku@users.noreply.github.com> Date: Wed, 13 Jul 2022 14:42:45 +0100 Subject: [PATCH 528/803] change LastPressedAction to have a private setter --- osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs index e3bb5f17e9..daae67c3a0 100644 --- a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods private DrawableRuleset ruleset = null!; - protected OsuAction? LastActionPressed; + protected OsuAction? LastActionPressed { get; private set; } /// /// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods). From 0db1caf591e53b275cd14df4d83cd0ea663a9ca0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 23:15:25 +0900 Subject: [PATCH 529/803] Add language selection to first run overlay --- .../Overlays/FirstRunSetup/ScreenWelcome.cs | 137 +++++++++++++++++- 1 file changed, 135 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs index 20ff8f21c8..ca1c02d53e 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs @@ -1,14 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Configuration; +using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osu.Game.Localisation; +using osuTK; namespace osu.Game.Overlays.FirstRunSetup { @@ -26,7 +35,131 @@ namespace osu.Game.Overlays.FirstRunSetup RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y }, + new LanguageSelectionFlow + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } }; } + + private class LanguageSelectionFlow : FillFlowContainer + { + private Bindable frameworkLocale = null!; + + [BackgroundDependencyLoader] + private void load(FrameworkConfigManager frameworkConfig) + { + Direction = FillDirection.Full; + Spacing = new Vector2(5); + + ChildrenEnumerable = Enum.GetValues(typeof(Language)) + .Cast() + .Select(l => new LanguageButton(l) + { + Action = () => frameworkLocale.Value = l.ToCultureCode() + }); + + frameworkLocale = frameworkConfig.GetBindable(FrameworkSetting.Locale); + frameworkLocale.BindValueChanged(locale => + { + if (!LanguageExtensions.TryParseCultureCode(frameworkLocale.Value, out var language)) + language = Language.en; + + foreach (var c in Children.OfType()) + c.Selected = c.Language == language; + }, true); + } + + private class LanguageButton : OsuClickableContainer + { + public readonly Language Language; + + private Box backgroundBox = null!; + + private OsuSpriteText text = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + private bool selected; + + public bool Selected + { + get => selected; + set + { + if (selected == value) + return; + + selected = value; + + updateState(); + } + } + + public LanguageButton(Language language) + { + Language = language; + + Size = new Vector2(160, 50); + Masking = true; + CornerRadius = 10; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + backgroundBox = new Box + { + Alpha = 0, + Colour = colourProvider.Background5, + RelativeSizeAxes = Axes.Both, + }, + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = colourProvider.Light1, + Text = Language.GetDescription(), + } + }; + } + + protected override bool OnHover(HoverEvent e) + { + if (!selected) + updateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + if (!selected) + updateState(); + base.OnHoverLost(e); + } + + private void updateState() + { + const double duration = 1000; + + if (selected) + { + backgroundBox.FadeTo(1, duration, Easing.OutQuint); + text.FadeColour(colourProvider.Content1, duration, Easing.OutQuint); + text.ScaleTo(1.2f, duration, Easing.OutQuint); + } + else + { + backgroundBox.FadeTo(IsHovered ? 0.4f : 0, duration / 2, Easing.OutQuint); + text.ScaleTo(1, duration / 2, Easing.OutQuint); + text.FadeColour(colourProvider.Light1, duration / 2, Easing.OutQuint); + } + } + } + } } } From 3b554140db559d9c1a1eab8ad01d7f7aab2eb04c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 23:22:50 +0900 Subject: [PATCH 530/803] Use grid container to avoid layout changes when changing language --- .../Overlays/FirstRunSetup/ScreenWelcome.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs index ca1c02d53e..ec074bcd04 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs @@ -29,11 +29,27 @@ namespace osu.Game.Overlays.FirstRunSetup { Content.Children = new Drawable[] { - new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) + new GridContainer { - Text = FirstRunSetupOverlayStrings.WelcomeDescription, RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y + AutoSizeAxes = Axes.Y, + RowDimensions = new[] + { + // Avoid height changes when changing language. + new Dimension(GridSizeMode.AutoSize, minSize: 100), + }, + Content = new[] + { + new Drawable[] + { + new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) + { + Text = FirstRunSetupOverlayStrings.WelcomeDescription, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }, + }, + } }, new LanguageSelectionFlow { From 31e1e963642b9ea4ee5e2c06cd4ba4fbefa7b4b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 23:25:32 +0900 Subject: [PATCH 531/803] 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 8c15ed7949..caaa83bff4 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 1251ab800b..355fd5f458 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 c38bb548bf..fcf71f3ab0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 7ac04d04782837946bf6247434d8303d99e1760a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 00:58:32 +0900 Subject: [PATCH 532/803] Fix potential crash when exiting editor test mode --- osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs index 87e640badc..fd230a97bc 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs @@ -35,7 +35,13 @@ namespace osu.Game.Screens.Edit.GameplayTest ScoreProcessor.HasCompleted.BindValueChanged(completed => { if (completed.NewValue) - Scheduler.AddDelayed(this.Exit, RESULTS_DISPLAY_DELAY); + { + Scheduler.AddDelayed(() => + { + if (this.IsCurrentScreen()) + this.Exit(); + }, RESULTS_DISPLAY_DELAY); + } }); } From e2f2d5f79469f0a06ae7fd3e839f68d2f698fb29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 01:40:44 +0900 Subject: [PATCH 533/803] Rename last action to better represent that it is only captured actions --- osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs | 6 +++--- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs index daae67c3a0..a7aca8257b 100644 --- a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods private DrawableRuleset ruleset = null!; - protected OsuAction? LastActionPressed { get; private set; } + protected OsuAction? LastAcceptedAction { get; private set; } /// /// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods). @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Mods { if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime)) { - LastActionPressed = null; + LastAcceptedAction = null; return true; } @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (CheckValidNewAction(action)) { - LastActionPressed = action; + LastAcceptedAction = action; return true; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 9bc401de67..d88cb17e84 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -15,6 +15,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override IconUsage? Icon => FontAwesome.Solid.Keyboard; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray(); - protected override bool CheckValidNewAction(OsuAction action) => LastActionPressed != action; + protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction != action; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs index 95b798c39e..051ceb968c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs @@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"You must only use one key!"; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray(); - protected override bool CheckValidNewAction(OsuAction action) => LastActionPressed == null || LastActionPressed == action; + protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction == null || LastAcceptedAction == action; } } From 099a7e90d624ec69192aafd242334b109b0033f6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 02:19:23 +0300 Subject: [PATCH 534/803] Centralise creation of playlist in test scene --- .../TestSceneDrawableRoomPlaylist.cs | 107 ++++++++---------- 1 file changed, 46 insertions(+), 61 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 757dfff2b7..542762af97 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -245,40 +245,35 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestExpiredItems() { - AddStep("create playlist", () => + createPlaylist(p => { - Child = playlist = new TestPlaylist + p.Items.Clear(); + p.Items.AddRange(new[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500, 300), - Items = + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { - new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + ID = 0, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + Expired = true, + RequiredMods = new[] { - ID = 0, - RulesetID = new OsuRuleset().RulesetInfo.OnlineID, - Expired = true, - RequiredMods = new[] - { - new APIMod(new OsuModHardRock()), - new APIMod(new OsuModDoubleTime()), - new APIMod(new OsuModAutoplay()) - } - }, - new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + new APIMod(new OsuModHardRock()), + new APIMod(new OsuModDoubleTime()), + new APIMod(new OsuModAutoplay()) + } + }, + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + ID = 1, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + RequiredMods = new[] { - ID = 1, - RulesetID = new OsuRuleset().RulesetInfo.OnlineID, - RequiredMods = new[] - { - new APIMod(new OsuModHardRock()), - new APIMod(new OsuModDoubleTime()), - new APIMod(new OsuModAutoplay()) - } + new APIMod(new OsuModHardRock()), + new APIMod(new OsuModDoubleTime()), + new APIMod(new OsuModAutoplay()) } } - }; + }); }); AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); @@ -321,6 +316,29 @@ namespace osu.Game.Tests.Visual.Multiplayer => AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).Alpha > 0) == visible); + private void createPlaylistWithBeatmaps(Func> beatmaps) => createPlaylist(p => + { + int index = 0; + + p.Items.Clear(); + + foreach (var b in beatmaps()) + { + p.Items.Add(new PlaylistItem(b) + { + ID = index++, + OwnerID = 2, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + RequiredMods = new[] + { + new APIMod(new OsuModHardRock()), + new APIMod(new OsuModDoubleTime()), + new APIMod(new OsuModAutoplay()) + } + }); + } + }); + private void createPlaylist(Action setupPlaylist = null) { AddStep("create playlist", () => @@ -332,8 +350,6 @@ namespace osu.Game.Tests.Visual.Multiplayer Size = new Vector2(500, 300) }; - setupPlaylist?.Invoke(playlist); - for (int i = 0; i < 20; i++) { playlist.Items.Add(new PlaylistItem(i % 2 == 1 @@ -360,39 +376,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); } - }); - AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); - } - - private void createPlaylistWithBeatmaps(Func> beatmaps) - { - AddStep("create playlist", () => - { - Child = playlist = new TestPlaylist - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500, 300) - }; - - int index = 0; - - foreach (var b in beatmaps()) - { - playlist.Items.Add(new PlaylistItem(b) - { - ID = index++, - OwnerID = 2, - RulesetID = new OsuRuleset().RulesetInfo.OnlineID, - RequiredMods = new[] - { - new APIMod(new OsuModHardRock()), - new APIMod(new OsuModDoubleTime()), - new APIMod(new OsuModAutoplay()) - } - }); - } + setupPlaylist?.Invoke(playlist); }); AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); From 728487b7fbd58c743e9d94d271af6a68d66650a2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 02:31:37 +0300 Subject: [PATCH 535/803] Handle `GetBeatmapSetRequest` on test room requests handler Required for `BeatmapSetOverlay` lookups to work under dummy API. Not 100% sure about it, but works for now. --- .../OnlinePlay/TestRoomRequestsHandler.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index e7c83ca1f9..fa7ade2c07 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -136,6 +136,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay return true; case GetBeatmapsRequest getBeatmapsRequest: + { var result = new List(); foreach (int id in getBeatmapsRequest.BeatmapIds) @@ -154,6 +155,24 @@ namespace osu.Game.Tests.Visual.OnlinePlay getBeatmapsRequest.TriggerSuccess(new GetBeatmapsResponse { Beatmaps = result }); return true; + } + + case GetBeatmapSetRequest getBeatmapSetRequest: + { + var baseBeatmap = getBeatmapSetRequest.Type == BeatmapSetLookupType.BeatmapId + ? beatmapManager.QueryBeatmap(b => b.OnlineID == getBeatmapSetRequest.ID) + : beatmapManager.QueryBeatmap(b => b.BeatmapSet.OnlineID == getBeatmapSetRequest.ID); + + if (baseBeatmap == null) + { + baseBeatmap = new TestBeatmap(new RulesetInfo { OnlineID = 0 }).BeatmapInfo; + baseBeatmap.OnlineID = getBeatmapSetRequest.ID; + baseBeatmap.BeatmapSet!.OnlineID = getBeatmapSetRequest.ID; + } + + getBeatmapSetRequest.TriggerSuccess(OsuTestScene.CreateAPIBeatmapSet(baseBeatmap)); + return true; + } } return false; From 036e64382fced24200435df4c8a519086b0fd61a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 02:45:31 +0300 Subject: [PATCH 536/803] Add beatmap details menu item to playlist items --- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 455e1f3481..e68634f4c6 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -30,6 +30,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Chat; using osu.Game.Online.Rooms; +using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; @@ -107,6 +108,9 @@ namespace osu.Game.Screens.OnlinePlay [Resolved] private BeatmapLookupCache beatmapLookupCache { get; set; } + [Resolved(CanBeNull = true)] + private BeatmapSetOverlay beatmapOverlay { get; set; } + [Resolved(CanBeNull = true)] private CollectionManager collectionManager { get; set; } @@ -496,18 +500,16 @@ namespace osu.Game.Screens.OnlinePlay { List items = new List(); - if (beatmap != null && collectionManager != null) - { - if (downloadTracker.State.Value == DownloadState.LocallyAvailable) - { - var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); - if (manageCollectionsDialog != null) - collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + if (beatmapOverlay != null) + items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(Item.Beatmap.OnlineID))); - items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); - } - else - items.Add(new OsuMenuItem("Download to add to collection") { Action = { Disabled = true } }); + if (beatmap != null && collectionManager != null && downloadTracker.State.Value == DownloadState.LocallyAvailable) + { + var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); } return items.ToArray(); From 9ec4fbb86dfb8a4446733ca0ac676719cfeb724b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 02:45:38 +0300 Subject: [PATCH 537/803] Add test coverage for details item --- .../TestSceneDrawableRoomPlaylist.cs | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 542762af97..6d8aaa6f6f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -12,15 +12,19 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Database; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.UserInterface; using osu.Game.Models; using osu.Game.Online.API; using osu.Game.Online.Rooms; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -299,6 +303,25 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } + [Test] + public void TestContextMenuDetails() + { + OsuContextMenu contextMenu = null; + + createPlaylist(); + + moveToItem(0); + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddAssert("context menu open", () => (contextMenu = this.ChildrenOfType().SingleOrDefault())?.State == MenuState.Open); + + AddStep("click details", () => + { + InputManager.MoveMouseTo(contextMenu.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + AddAssert("beatmap overlay visible", () => this.ChildrenOfType().Single().State.Value == Visibility.Visible); + } + private void moveToItem(int index, Vector2? offset = null) => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(index), offset)); @@ -343,11 +366,29 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create playlist", () => { - Child = playlist = new TestPlaylist + BeatmapSetOverlay beatmapOverlay; + + Child = new DependencyProvidingContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500, 300) + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(BeatmapSetOverlay), beatmapOverlay = new BeatmapSetOverlay()), + }, + Children = new Drawable[] + { + new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + Child = playlist = new TestPlaylist + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500, 300), + }, + }, + beatmapOverlay, + }, }; for (int i = 0; i < 20; i++) From cb2f0b8c67c57e8231661424bcc232d91cbf4254 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 03:12:48 +0300 Subject: [PATCH 538/803] Add test coverage for collection items --- .../TestSceneDrawableRoomPlaylist.cs | 108 +++++++++++++++++- 1 file changed, 102 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 6d8aaa6f6f..ce36ae7827 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -17,6 +17,7 @@ using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Collections; using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; @@ -42,6 +43,10 @@ namespace osu.Game.Tests.Visual.Multiplayer private BeatmapManager manager; private RulesetStore rulesets; + private CollectionManager collections; + + private BeatmapSetOverlay beatmapOverlay; + private ManageCollectionsDialog manageCollectionsDialog; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -199,12 +204,15 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestDownloadButtonHiddenWhenBeatmapExists() { - var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; Live imported = null; - Debug.Assert(beatmap.BeatmapSet != null); + AddStep("import beatmap", () => + { + var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; - AddStep("import beatmap", () => imported = manager.Import(beatmap.BeatmapSet)); + Debug.Assert(beatmap.BeatmapSet != null); + imported = manager.Import(beatmap.BeatmapSet); + }); createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach())); @@ -310,6 +318,8 @@ namespace osu.Game.Tests.Visual.Multiplayer createPlaylist(); + AddAssert("beatmap overlay hidden", () => beatmapOverlay.State.Value == Visibility.Hidden); + moveToItem(0); AddStep("right click", () => InputManager.Click(MouseButton.Right)); AddAssert("context menu open", () => (contextMenu = this.ChildrenOfType().SingleOrDefault())?.State == MenuState.Open); @@ -319,7 +329,91 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.MoveMouseTo(contextMenu.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); - AddAssert("beatmap overlay visible", () => this.ChildrenOfType().Single().State.Value == Visibility.Visible); + AddAssert("beatmap overlay visible", () => beatmapOverlay.State.Value == Visibility.Visible); + } + + [Test] + public void TestContextMenuCollection() + { + OsuContextMenu contextMenu = null; + BeatmapInfo beatmap = null; + Live imported = null; + + AddStep("import beatmap", () => + { + beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; + + Debug.Assert(beatmap.BeatmapSet != null); + imported = manager.Import(beatmap.BeatmapSet); + }); + + createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach())); + + AddStep("add two collections", () => + { + collections.Collections.Clear(); + collections.Collections.AddRange(new[] + { + new BeatmapCollection { Name = { Value = "Collection #1" }, BeatmapHashes = { beatmap.MD5Hash } }, + new BeatmapCollection { Name = { Value = "Collection #2" } }, + }); + }); + + moveToItem(0); + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddAssert("context menu open", () => (contextMenu = this.ChildrenOfType().SingleOrDefault())?.State == MenuState.Open); + + AddStep("select collections", () => InputManager.MoveMouseTo(contextMenu.ChildrenOfType().ElementAt(1))); + AddAssert("collection 1 present and beatmap added", () => + { + var item = (ToggleMenuItem)contextMenu.Items[1].Items[0]; + return item.Text.Value == "Collection #1" && item.State.Value; + }); + AddAssert("collection 2 present", () => + { + var item = (ToggleMenuItem)contextMenu.Items[1].Items[1]; + return item.Text.Value == "Collection #2" && !item.State.Value; + }); + + AddStep("select second collection", () => + { + InputManager.MoveMouseTo(contextMenu.ChildrenOfType().ElementAt(1)); + InputManager.Click(MouseButton.Left); + }); + AddAssert("beatmap added to second collection", () => collections.Collections[1].BeatmapHashes.Contains(beatmap.MD5Hash)); + AddAssert("item state updated", () => ((ToggleMenuItem)contextMenu.Items[1].Items[1]).State.Value); + } + + [Test] + public void TestContextMenuManageCollections() + { + OsuContextMenu contextMenu = null; + Live imported = null; + + AddStep("import beatmap", () => + { + var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; + + Debug.Assert(beatmap.BeatmapSet != null); + imported = manager.Import(beatmap.BeatmapSet); + }); + + createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach())); + + AddStep("clear collections", () => collections.Collections.Clear()); + AddAssert("manage collections dialog hidden", () => manageCollectionsDialog.State.Value == Visibility.Hidden); + + moveToItem(0); + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddAssert("context menu open", () => (contextMenu = this.ChildrenOfType().SingleOrDefault())?.State == MenuState.Open); + + AddStep("select collections", () => InputManager.MoveMouseTo(contextMenu.ChildrenOfType().ElementAt(1))); + AddStep("click manage", () => + { + InputManager.MoveMouseTo(contextMenu.ChildrenOfType().ElementAt(1).ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + AddAssert("manage collections dialog open", () => manageCollectionsDialog.State.Value == Visibility.Visible); } private void moveToItem(int index, Vector2? offset = null) @@ -366,17 +460,18 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create playlist", () => { - BeatmapSetOverlay beatmapOverlay; - Child = new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, CachedDependencies = new (Type, object)[] { (typeof(BeatmapSetOverlay), beatmapOverlay = new BeatmapSetOverlay()), + (typeof(CollectionManager), collections = new CollectionManager(LocalStorage)), + (typeof(ManageCollectionsDialog), manageCollectionsDialog = new ManageCollectionsDialog()), }, Children = new Drawable[] { + collections, new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -388,6 +483,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }, }, beatmapOverlay, + manageCollectionsDialog, }, }; From 776d9551e2d93745653609fbfd6b4461a7cc8569 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 04:16:25 +0300 Subject: [PATCH 539/803] Disable "save changes" button by default --- osu.Game.Tournament/SaveChangesOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tournament/SaveChangesOverlay.cs b/osu.Game.Tournament/SaveChangesOverlay.cs index 03a3f2d3a4..c03afb2eab 100644 --- a/osu.Game.Tournament/SaveChangesOverlay.cs +++ b/osu.Game.Tournament/SaveChangesOverlay.cs @@ -57,6 +57,7 @@ namespace osu.Game.Tournament Bottom = 10, }, Action = saveChanges, + Enabled = { Value = false }, }, } }; From 24df8f6a0dfe41ec28a8562077ed94a7afba7754 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 04:33:07 +0300 Subject: [PATCH 540/803] Enable NRT on save changes button --- osu.Game.Tournament/SaveChangesOverlay.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament/SaveChangesOverlay.cs b/osu.Game.Tournament/SaveChangesOverlay.cs index c03afb2eab..b5e08fc005 100644 --- a/osu.Game.Tournament/SaveChangesOverlay.cs +++ b/osu.Game.Tournament/SaveChangesOverlay.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. -#nullable disable using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -17,9 +16,9 @@ namespace osu.Game.Tournament internal class SaveChangesOverlay : CompositeDrawable { [Resolved] - private TournamentGame tournamentGame { get; set; } + private TournamentGame tournamentGame { get; set; } = null!; - private string lastSerialisedLadder; + private string? lastSerialisedLadder; private readonly TourneyButton saveChangesButton; public SaveChangesOverlay() @@ -57,7 +56,7 @@ namespace osu.Game.Tournament Bottom = 10, }, Action = saveChanges, - Enabled = { Value = false }, + // Enabled = { Value = false }, }, } }; From a85a70c47249eb9685ea8072cc1885fccb3b5337 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 05:01:18 +0300 Subject: [PATCH 541/803] Fix potential nullref in `ContextMenuItems` --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index e68634f4c6..d89816d3c7 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -8,6 +8,7 @@ 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; using osu.Framework.Graphics; @@ -94,6 +95,7 @@ namespace osu.Game.Screens.OnlinePlay private PanelBackground panelBackground; private FillFlowContainer mainFillFlow; + [CanBeNull] private BeatmapDownloadTracker downloadTracker; [Resolved] @@ -503,7 +505,7 @@ namespace osu.Game.Screens.OnlinePlay if (beatmapOverlay != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(Item.Beatmap.OnlineID))); - if (beatmap != null && collectionManager != null && downloadTracker.State.Value == DownloadState.LocallyAvailable) + if (beatmap != null && collectionManager != null && downloadTracker?.State.Value == DownloadState.LocallyAvailable) { var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); if (manageCollectionsDialog != null) From f83d413b33d000a3cbdaf3783dc96df3478a1ca4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 06:05:03 +0300 Subject: [PATCH 542/803] Fix dialog overlay potentially pushing dialog while not loaded --- osu.Game/Overlays/DialogOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index a07cf1608d..7f2db9f03f 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -65,7 +65,7 @@ namespace osu.Game.Overlays dialogContainer.Add(dialog); Show(); - }, false); + }, !IsLoaded); } public override bool IsPresent => Scheduler.HasPendingTasks || dialogContainer.Children.Count > 0; From 3def8428aa368b13387da6c026270e77802f2a42 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 06:06:56 +0300 Subject: [PATCH 543/803] Make scheduling more legible --- osu.Game/Overlays/DialogOverlay.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 7f2db9f03f..259c6068e0 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -57,7 +57,12 @@ namespace osu.Game.Overlays // a DialogOverlay instance has finished loading. CurrentDialog = dialog; - Scheduler.Add(() => + if (IsLoaded) + Scheduler.Add(pushDialog, false); + else + Schedule(pushDialog); + + void pushDialog() { // if any existing dialog is being displayed, dismiss it before showing a new one. lastDialog?.Hide(); @@ -65,7 +70,7 @@ namespace osu.Game.Overlays dialogContainer.Add(dialog); Show(); - }, !IsLoaded); + } } public override bool IsPresent => Scheduler.HasPendingTasks || dialogContainer.Children.Count > 0; From be69514002b62a6d4fd0728923577a5a639d7664 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 06:21:03 +0300 Subject: [PATCH 544/803] Fix `CollectionManager` opening file multiple times across test scene --- .../Multiplayer/TestSceneDrawableRoomPlaylist.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index ce36ae7827..599a28f913 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -39,6 +39,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneDrawableRoomPlaylist : MultiplayerTestScene { + protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; + private TestPlaylist playlist; private BeatmapManager manager; @@ -54,6 +56,14 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); + + base.Content.AddRange(new Drawable[] + { + collections = new CollectionManager(LocalStorage), + Content + }); + + Dependencies.Cache(collections); } [Test] @@ -466,12 +476,10 @@ namespace osu.Game.Tests.Visual.Multiplayer CachedDependencies = new (Type, object)[] { (typeof(BeatmapSetOverlay), beatmapOverlay = new BeatmapSetOverlay()), - (typeof(CollectionManager), collections = new CollectionManager(LocalStorage)), (typeof(ManageCollectionsDialog), manageCollectionsDialog = new ManageCollectionsDialog()), }, Children = new Drawable[] { - collections, new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, From 12221235413416a956b5630ed32362c2e2321e3b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 07:04:46 +0300 Subject: [PATCH 545/803] Rename method and parameter --- osu.Game/Overlays/DialogOverlay.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 259c6068e0..83a563beba 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -66,7 +66,8 @@ namespace osu.Game.Overlays { // if any existing dialog is being displayed, dismiss it before showing a new one. lastDialog?.Hide(); - dialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue); + + dialog.State.ValueChanged += state => onDialogStateChanged(dialog, state.NewValue), true); dialogContainer.Add(dialog); Show(); @@ -77,9 +78,9 @@ namespace osu.Game.Overlays protected override bool BlockNonPositionalInput => true; - private void onDialogOnStateChanged(VisibilityContainer dialog, Visibility v) + private void onDialogStateChanged(VisibilityContainer dialog, Visibility newState) { - if (v != Visibility.Hidden) return; + if (newState != Visibility.Hidden) return; // handle the dialog being dismissed. dialog.Delay(PopupDialog.EXIT_DURATION).Expire(); From c59784c49f31265d96f53caf04cdfd5794b776fc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 07:06:03 +0300 Subject: [PATCH 546/803] Always schedule popup dialog push --- osu.Game/Overlays/DialogOverlay.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 83a563beba..5e082acb23 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -57,12 +57,7 @@ namespace osu.Game.Overlays // a DialogOverlay instance has finished loading. CurrentDialog = dialog; - if (IsLoaded) - Scheduler.Add(pushDialog, false); - else - Schedule(pushDialog); - - void pushDialog() + Schedule(() => { // if any existing dialog is being displayed, dismiss it before showing a new one. lastDialog?.Hide(); @@ -71,7 +66,7 @@ namespace osu.Game.Overlays dialogContainer.Add(dialog); Show(); - } + }); } public override bool IsPresent => Scheduler.HasPendingTasks || dialogContainer.Children.Count > 0; From b96faedbe6de41b984a4725c18d64b45e736fb4b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 07:06:57 +0300 Subject: [PATCH 547/803] Fix dialog overlay hiding early-pushed dialog on initial `PopOut` call --- osu.Game/Overlays/DialogOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 5e082acb23..6f88564119 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -98,7 +98,8 @@ namespace osu.Game.Overlays base.PopOut(); lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 100, Easing.InCubic); - if (CurrentDialog?.State.Value == Visibility.Visible) + // PopOut gets called initially, but we only want to hide dialog when we have been loaded and are present. + if (IsLoaded && CurrentDialog?.State.Value == Visibility.Visible) CurrentDialog.Hide(); } From dccd81dbc7dad3fdc308bff7aa2dd07b6ef92c9c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 07:07:32 +0300 Subject: [PATCH 548/803] Use `BindValueChanged` to handle changes between push time and schedule execution --- osu.Game/Overlays/DialogOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 6f88564119..2024eca707 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -62,7 +62,7 @@ namespace osu.Game.Overlays // if any existing dialog is being displayed, dismiss it before showing a new one. lastDialog?.Hide(); - dialog.State.ValueChanged += state => onDialogStateChanged(dialog, state.NewValue), true); + dialog.State.BindValueChanged(state => onDialogStateChanged(dialog, state.NewValue), true); dialogContainer.Add(dialog); Show(); From 227871e8dffa1a1be9ca3669f6e829fc0d0a163f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 14:00:33 +0900 Subject: [PATCH 549/803] Refactor hide logic a touch for better readability --- osu.Game/Overlays/DialogOverlay.cs | 48 ++++++++++++++++++------------ 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 2024eca707..dfc6a0010a 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -49,12 +49,11 @@ namespace osu.Game.Overlays public void Push(PopupDialog dialog) { - if (dialog == CurrentDialog || dialog.State.Value != Visibility.Visible) return; - - var lastDialog = CurrentDialog; + if (dialog == CurrentDialog || dialog.State.Value == Visibility.Hidden) return; // Immediately update the externally accessible property as this may be used for checks even before // a DialogOverlay instance has finished loading. + var lastDialog = CurrentDialog; CurrentDialog = dialog; Schedule(() => @@ -62,31 +61,42 @@ namespace osu.Game.Overlays // if any existing dialog is being displayed, dismiss it before showing a new one. lastDialog?.Hide(); - dialog.State.BindValueChanged(state => onDialogStateChanged(dialog, state.NewValue), true); - dialogContainer.Add(dialog); + // is the new dialog is hidden before added to the dialogContainer, bypass any further operations. + if (dialog.State.Value == Visibility.Hidden) + { + dismiss(); + return; + } + dialogContainer.Add(dialog); Show(); + + dialog.State.BindValueChanged(state => + { + if (state.NewValue != Visibility.Hidden) return; + + // Trigger the demise of the dialog as soon as it hides. + dialog.Delay(PopupDialog.EXIT_DURATION).Expire(); + + dismiss(); + }); }); + + void dismiss() + { + if (dialog != CurrentDialog) return; + + // Handle the case where the dialog is the currently displayed dialog. + // In this scenario, the overlay itself should also be hidden. + Hide(); + CurrentDialog = null; + } } public override bool IsPresent => Scheduler.HasPendingTasks || dialogContainer.Children.Count > 0; protected override bool BlockNonPositionalInput => true; - private void onDialogStateChanged(VisibilityContainer dialog, Visibility newState) - { - if (newState != Visibility.Hidden) return; - - // handle the dialog being dismissed. - dialog.Delay(PopupDialog.EXIT_DURATION).Expire(); - - if (dialog == CurrentDialog) - { - Hide(); - CurrentDialog = null; - } - } - protected override void PopIn() { base.PopIn(); From 5c6b4e498dfda0910beea0847cc19ff586d9ad07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 14:31:59 +0900 Subject: [PATCH 550/803] Protect against a potential early call to `LanguageButton.Selected` --- osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs index ec074bcd04..f195fa82c0 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs @@ -79,7 +79,7 @@ namespace osu.Game.Overlays.FirstRunSetup frameworkLocale = frameworkConfig.GetBindable(FrameworkSetting.Locale); frameworkLocale.BindValueChanged(locale => { - if (!LanguageExtensions.TryParseCultureCode(frameworkLocale.Value, out var language)) + if (!LanguageExtensions.TryParseCultureCode(locale.NewValue, out var language)) language = Language.en; foreach (var c in Children.OfType()) @@ -110,7 +110,8 @@ namespace osu.Game.Overlays.FirstRunSetup selected = value; - updateState(); + if (IsLoaded) + updateState(); } } @@ -144,6 +145,12 @@ namespace osu.Game.Overlays.FirstRunSetup }; } + protected override void LoadComplete() + { + base.LoadComplete(); + updateState(); + } + protected override bool OnHover(HoverEvent e) { if (!selected) From 5dff48a1e02a4ed260d6709e46d084b41fef38a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 14:40:25 +0900 Subject: [PATCH 551/803] Fix button selection animation not playing smoothly when new glyphs are loaded --- osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs index f195fa82c0..29aa23ebab 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Framework.Threading; using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -63,6 +64,8 @@ namespace osu.Game.Overlays.FirstRunSetup { private Bindable frameworkLocale = null!; + private ScheduledDelegate? updateSelectedDelegate; + [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfig) { @@ -82,11 +85,20 @@ namespace osu.Game.Overlays.FirstRunSetup if (!LanguageExtensions.TryParseCultureCode(locale.NewValue, out var language)) language = Language.en; - foreach (var c in Children.OfType()) - c.Selected = c.Language == language; + // Changing language may cause a short period of blocking the UI thread while the new glyphs are loaded. + // Scheduling ensures the button animation plays smoothly after any blocking operation completes. + // Note that a delay is required (the alternative would be a double-schedule; delay feels better). + updateSelectedDelegate?.Cancel(); + updateSelectedDelegate = Scheduler.AddDelayed(() => updateSelectedStates(language), 50); }, true); } + private void updateSelectedStates(Language language) + { + foreach (var c in Children.OfType()) + c.Selected = c.Language == language; + } + private class LanguageButton : OsuClickableContainer { public readonly Language Language; From 08396ba48631a23b04f0aeb4d58bbfa3ba35d519 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 14:47:25 +0900 Subject: [PATCH 552/803] Adjust colouring to avoid weird banding during transition period --- .../Overlays/FirstRunSetup/ScreenWelcome.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs index 29aa23ebab..cb1e96d2f2 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs @@ -179,19 +179,23 @@ namespace osu.Game.Overlays.FirstRunSetup private void updateState() { - const double duration = 1000; - if (selected) { - backgroundBox.FadeTo(1, duration, Easing.OutQuint); - text.FadeColour(colourProvider.Content1, duration, Easing.OutQuint); - text.ScaleTo(1.2f, duration, Easing.OutQuint); + const double selected_duration = 1000; + + backgroundBox.FadeTo(1, selected_duration, Easing.OutQuint); + backgroundBox.FadeColour(colourProvider.Background2, selected_duration, Easing.OutQuint); + text.FadeColour(colourProvider.Content1, selected_duration, Easing.OutQuint); + text.ScaleTo(1.2f, selected_duration, Easing.OutQuint); } else { - backgroundBox.FadeTo(IsHovered ? 0.4f : 0, duration / 2, Easing.OutQuint); - text.ScaleTo(1, duration / 2, Easing.OutQuint); - text.FadeColour(colourProvider.Light1, duration / 2, Easing.OutQuint); + const double duration = 500; + + backgroundBox.FadeTo(IsHovered ? 1 : 0, duration, Easing.OutQuint); + backgroundBox.FadeColour(colourProvider.Background5, duration, Easing.OutQuint); + text.FadeColour(colourProvider.Light1, duration, Easing.OutQuint); + text.ScaleTo(1, duration, Easing.OutQuint); } } } From ebe0cfefd87acbae75789d721c1ff612761f0029 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 17:04:00 +0900 Subject: [PATCH 553/803] Ensure that multiple `BeatmapSetInfo` already in realm don't cause import failures Really this shouldn't happen but I managed to make it happen. Until this comes up again in a way that matters, let's just fix the LINQ crash from `SingleOrDefault`. I've tested this to work as expected in the broken scenario. --- osu.Game/Beatmaps/BeatmapImporter.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 92f1fc17d5..3e4d01a9a3 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -80,9 +80,8 @@ namespace osu.Game.Beatmaps if (beatmapSet.OnlineID > 0) { - var existingSetWithSameOnlineID = realm.All().SingleOrDefault(b => b.OnlineID == beatmapSet.OnlineID); - - if (existingSetWithSameOnlineID != null) + // OnlineID should really be unique, but to avoid catastrophic failure let's iterate just to be sure. + foreach (var existingSetWithSameOnlineID in realm.All().Where(b => b.OnlineID == beatmapSet.OnlineID)) { existingSetWithSameOnlineID.DeletePending = true; existingSetWithSameOnlineID.OnlineID = -1; @@ -90,7 +89,7 @@ namespace osu.Game.Beatmaps foreach (var b in existingSetWithSameOnlineID.Beatmaps) b.OnlineID = -1; - LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineID ({beatmapSet.OnlineID}). It will be deleted."); + LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineID ({beatmapSet.OnlineID}). It will be disassociated and marked for deletion."); } } } From 66932f1af6979ddd254231b27a27b981eeef97d3 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Thu, 14 Jul 2022 16:52:45 -0700 Subject: [PATCH 554/803] Move shared followcircle code into abstract base class --- .../Skinning/Default/DefaultFollowCircle.cs | 50 +----------- .../Skinning/FollowCircle.cs | 79 +++++++++++++++++++ .../Skinning/Legacy/LegacyFollowCircle.cs | 72 +++-------------- 3 files changed, 92 insertions(+), 109 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 8bb36f8c39..254e220996 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -1,27 +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.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Default { - public class DefaultFollowCircle : CompositeDrawable + public class DefaultFollowCircle : FollowCircle { - [Resolved(canBeNull: true)] - private DrawableHitObject? parentObject { get; set; } - public DefaultFollowCircle() { - Alpha = 0f; - RelativeSizeAxes = Axes.Both; - InternalChild = new CircularContainer { RelativeSizeAxes = Axes.Both, @@ -38,28 +30,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default }; } - [BackgroundDependencyLoader] - private void load() - { - if (parentObject != null) - { - var slider = (DrawableSlider)parentObject; - slider.Tracking.BindValueChanged(trackingChanged, true); - } - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - if (parentObject != null) - { - parentObject.ApplyCustomUpdateState += updateStateTransforms; - updateStateTransforms(parentObject, parentObject.State.Value); - } - } - - private void trackingChanged(ValueChangedEvent tracking) + protected override void OnTrackingChanged(ValueChangedEvent tracking) { const float scale_duration = 300f; const float fade_duration = 300f; @@ -68,25 +39,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default .FadeTo(tracking.NewValue ? 1f : 0, fade_duration, Easing.OutQuint); } - private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) + protected override void OnSliderEnd() { - // see comment in LegacySliderBall.updateStateTransforms - if (drawableObject is not DrawableSlider) - return; - const float fade_duration = 450f; // intentionally pile on an extra FadeOut to make it happen much faster - using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) - this.FadeOut(fade_duration / 4, Easing.Out); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (parentObject != null) - parentObject.ApplyCustomUpdateState -= updateStateTransforms; + this.FadeOut(fade_duration / 4, Easing.Out); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs new file mode 100644 index 0000000000..e1e792b89e --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public abstract class FollowCircle : CompositeDrawable + { + [Resolved(canBeNull: true)] + protected DrawableHitObject? ParentObject { get; private set; } + + public FollowCircle() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + if (ParentObject != null) + { + var slider = (DrawableSlider)ParentObject; + slider.Tracking.BindValueChanged(OnTrackingChanged, true); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (ParentObject != null) + { + ParentObject.HitObjectApplied += onHitObjectApplied; + onHitObjectApplied(ParentObject); + + ParentObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(ParentObject, ParentObject.State.Value); + } + } + + private void onHitObjectApplied(DrawableHitObject drawableObject) + { + this.ScaleTo(1f) + .FadeOut(); + } + + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) + { + // Gets called by slider ticks, tails, etc., leading to duplicated + // animations which may negatively affect performance + if (drawableObject is not DrawableSlider) + return; + + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + OnSliderEnd(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (ParentObject != null) + { + ParentObject.HitObjectApplied -= onHitObjectApplied; + ParentObject.ApplyCustomUpdateState -= updateStateTransforms; + } + } + + protected abstract void OnTrackingChanged(ValueChangedEvent tracking); + + protected abstract void OnSliderEnd(); + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index f18c4529ab..38e2e74349 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -2,20 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public class LegacyFollowCircle : CompositeDrawable + public class LegacyFollowCircle : FollowCircle { - [Resolved(canBeNull: true)] - private DrawableHitObject? parentObject { get; set; } - public LegacyFollowCircle(Drawable animationContent) { // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x @@ -27,41 +21,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy InternalChild = animationContent; } - [BackgroundDependencyLoader] - private void load() + protected override void OnTrackingChanged(ValueChangedEvent tracking) { - if (parentObject != null) - { - var slider = (DrawableSlider)parentObject; - slider.Tracking.BindValueChanged(trackingChanged, true); - } - } + Debug.Assert(ParentObject != null); - protected override void LoadComplete() - { - base.LoadComplete(); - - if (parentObject != null) - { - parentObject.HitObjectApplied += onHitObjectApplied; - onHitObjectApplied(parentObject); - - parentObject.ApplyCustomUpdateState += updateStateTransforms; - updateStateTransforms(parentObject, parentObject.State.Value); - } - } - - private void trackingChanged(ValueChangedEvent tracking) - { - Debug.Assert(parentObject != null); - - if (parentObject.Judged) + if (ParentObject.Judged) return; const float scale_duration = 180f; const float fade_duration = 90f; - double maxScaleDuration = parentObject.HitStateUpdateTime - Time.Current; + double maxScaleDuration = ParentObject.HitStateUpdateTime - Time.Current; double realScaleDuration = scale_duration; if (tracking.NewValue && maxScaleDuration < realScaleDuration && maxScaleDuration >= 0) realScaleDuration = maxScaleDuration; @@ -71,39 +41,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy .FadeTo(tracking.NewValue ? 1f : 0f, realFadeDuration, Easing.OutQuad); } - private void onHitObjectApplied(DrawableHitObject drawableObject) + protected override void OnSliderEnd() { - this.ScaleTo(1f) - .FadeOut(); - } - - private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) - { - // see comment in LegacySliderBall.updateStateTransforms - if (drawableObject is not DrawableSlider) - return; - const float shrink_duration = 200f; const float fade_delay = 175f; const float fade_duration = 35f; - using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) - { - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.75f, shrink_duration, Easing.OutQuad) - .Delay(fade_delay) - .FadeOut(fade_duration); - } - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (parentObject != null) - { - parentObject.HitObjectApplied -= onHitObjectApplied; - parentObject.ApplyCustomUpdateState -= updateStateTransforms; - } + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.75f, shrink_duration, Easing.OutQuad) + .Delay(fade_delay) + .FadeOut(fade_duration); } } } From 4453b0b3e831a9619213aa33a42376eea0f43598 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Thu, 14 Jul 2022 16:53:51 -0700 Subject: [PATCH 555/803] Replace comment pointer with actual comment --- osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs index 37e5e150bd..97bb4a3697 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs @@ -73,7 +73,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { - // see comment in LegacySliderBall.updateStateTransforms + // Gets called by slider ticks, tails, etc., leading to duplicated + // animations which may negatively affect performance if (drawableObject is not DrawableSlider) return; From 1581f1a0ff8a0570d684a2e02bc58417003aeded Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Thu, 14 Jul 2022 17:08:52 -0700 Subject: [PATCH 556/803] Convert constructor in abstract class to protected --- osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index e1e792b89e..943b7d3b4f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [Resolved(canBeNull: true)] protected DrawableHitObject? ParentObject { get; private set; } - public FollowCircle() + protected FollowCircle() { RelativeSizeAxes = Axes.Both; } From 0bafafd63b4ab42462158af4f67d68cc7eb2fbbe Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 15 Jul 2022 03:19:50 +0300 Subject: [PATCH 557/803] Remove unnecessary test coverage RIP hours. --- .../TestSceneDrawableRoomPlaylist.cs | 149 +----------------- 1 file changed, 6 insertions(+), 143 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 599a28f913..1797c82fb9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -12,20 +12,16 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; -using osu.Game.Collections; using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; -using osu.Game.Graphics.UserInterface; using osu.Game.Models; using osu.Game.Online.API; using osu.Game.Online.Rooms; -using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -39,16 +35,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneDrawableRoomPlaylist : MultiplayerTestScene { - protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; - private TestPlaylist playlist; private BeatmapManager manager; private RulesetStore rulesets; - private CollectionManager collections; - - private BeatmapSetOverlay beatmapOverlay; - private ManageCollectionsDialog manageCollectionsDialog; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -56,14 +46,6 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); - - base.Content.AddRange(new Drawable[] - { - collections = new CollectionManager(LocalStorage), - Content - }); - - Dependencies.Cache(collections); } [Test] @@ -321,111 +303,6 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } - [Test] - public void TestContextMenuDetails() - { - OsuContextMenu contextMenu = null; - - createPlaylist(); - - AddAssert("beatmap overlay hidden", () => beatmapOverlay.State.Value == Visibility.Hidden); - - moveToItem(0); - AddStep("right click", () => InputManager.Click(MouseButton.Right)); - AddAssert("context menu open", () => (contextMenu = this.ChildrenOfType().SingleOrDefault())?.State == MenuState.Open); - - AddStep("click details", () => - { - InputManager.MoveMouseTo(contextMenu.ChildrenOfType().First()); - InputManager.Click(MouseButton.Left); - }); - AddAssert("beatmap overlay visible", () => beatmapOverlay.State.Value == Visibility.Visible); - } - - [Test] - public void TestContextMenuCollection() - { - OsuContextMenu contextMenu = null; - BeatmapInfo beatmap = null; - Live imported = null; - - AddStep("import beatmap", () => - { - beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; - - Debug.Assert(beatmap.BeatmapSet != null); - imported = manager.Import(beatmap.BeatmapSet); - }); - - createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach())); - - AddStep("add two collections", () => - { - collections.Collections.Clear(); - collections.Collections.AddRange(new[] - { - new BeatmapCollection { Name = { Value = "Collection #1" }, BeatmapHashes = { beatmap.MD5Hash } }, - new BeatmapCollection { Name = { Value = "Collection #2" } }, - }); - }); - - moveToItem(0); - AddStep("right click", () => InputManager.Click(MouseButton.Right)); - AddAssert("context menu open", () => (contextMenu = this.ChildrenOfType().SingleOrDefault())?.State == MenuState.Open); - - AddStep("select collections", () => InputManager.MoveMouseTo(contextMenu.ChildrenOfType().ElementAt(1))); - AddAssert("collection 1 present and beatmap added", () => - { - var item = (ToggleMenuItem)contextMenu.Items[1].Items[0]; - return item.Text.Value == "Collection #1" && item.State.Value; - }); - AddAssert("collection 2 present", () => - { - var item = (ToggleMenuItem)contextMenu.Items[1].Items[1]; - return item.Text.Value == "Collection #2" && !item.State.Value; - }); - - AddStep("select second collection", () => - { - InputManager.MoveMouseTo(contextMenu.ChildrenOfType().ElementAt(1)); - InputManager.Click(MouseButton.Left); - }); - AddAssert("beatmap added to second collection", () => collections.Collections[1].BeatmapHashes.Contains(beatmap.MD5Hash)); - AddAssert("item state updated", () => ((ToggleMenuItem)contextMenu.Items[1].Items[1]).State.Value); - } - - [Test] - public void TestContextMenuManageCollections() - { - OsuContextMenu contextMenu = null; - Live imported = null; - - AddStep("import beatmap", () => - { - var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; - - Debug.Assert(beatmap.BeatmapSet != null); - imported = manager.Import(beatmap.BeatmapSet); - }); - - createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach())); - - AddStep("clear collections", () => collections.Collections.Clear()); - AddAssert("manage collections dialog hidden", () => manageCollectionsDialog.State.Value == Visibility.Hidden); - - moveToItem(0); - AddStep("right click", () => InputManager.Click(MouseButton.Right)); - AddAssert("context menu open", () => (contextMenu = this.ChildrenOfType().SingleOrDefault())?.State == MenuState.Open); - - AddStep("select collections", () => InputManager.MoveMouseTo(contextMenu.ChildrenOfType().ElementAt(1))); - AddStep("click manage", () => - { - InputManager.MoveMouseTo(contextMenu.ChildrenOfType().ElementAt(1).ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); - AddAssert("manage collections dialog open", () => manageCollectionsDialog.State.Value == Visibility.Visible); - } - private void moveToItem(int index, Vector2? offset = null) => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(index), offset)); @@ -470,29 +347,15 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create playlist", () => { - Child = new DependencyProvidingContainer + Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] + Child = playlist = new TestPlaylist { - (typeof(BeatmapSetOverlay), beatmapOverlay = new BeatmapSetOverlay()), - (typeof(ManageCollectionsDialog), manageCollectionsDialog = new ManageCollectionsDialog()), - }, - Children = new Drawable[] - { - new OsuContextMenuContainer - { - RelativeSizeAxes = Axes.Both, - Child = playlist = new TestPlaylist - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500, 300), - }, - }, - beatmapOverlay, - manageCollectionsDialog, - }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500, 300) + } }; for (int i = 0; i < 20; i++) From aea786ea0c2f5e7df04911a37dac0f0f855316db Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 15 Jul 2022 05:56:49 +0300 Subject: [PATCH 558/803] Fix minor typo --- osu.Game/Overlays/DialogOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index dfc6a0010a..493cd66258 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays // if any existing dialog is being displayed, dismiss it before showing a new one. lastDialog?.Hide(); - // is the new dialog is hidden before added to the dialogContainer, bypass any further operations. + // if the new dialog is hidden before added to the dialogContainer, bypass any further operations. if (dialog.State.Value == Visibility.Hidden) { dismiss(); From eafa11555af3e83c6b79b4503f9904e7306bab7c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 15 Jul 2022 06:41:03 +0300 Subject: [PATCH 559/803] Allow specifying custom search action to metadata sections --- .../Overlays/BeatmapSet/MetadataSection.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSection.cs b/osu.Game/Overlays/BeatmapSet/MetadataSection.cs index c6bf94f507..317b369d8f 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSection.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSection.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -22,11 +23,14 @@ namespace osu.Game.Overlays.BeatmapSet private readonly MetadataType type; private TextFlowContainer textFlow; + private readonly Action searchAction; + private const float transition_duration = 250; - public MetadataSection(MetadataType type) + public MetadataSection(MetadataType type, Action searchAction = null) { this.type = type; + this.searchAction = searchAction; Alpha = 0; @@ -91,7 +95,12 @@ namespace osu.Game.Overlays.BeatmapSet for (int i = 0; i <= tags.Length - 1; i++) { - loaded.AddLink(tags[i], LinkAction.SearchBeatmapSet, tags[i]); + string tag = tags[i]; + + if (searchAction != null) + loaded.AddLink(tag, () => searchAction(tag)); + else + loaded.AddLink(tag, LinkAction.SearchBeatmapSet, tag); if (i != tags.Length - 1) loaded.AddText(" "); @@ -100,7 +109,11 @@ namespace osu.Game.Overlays.BeatmapSet break; case MetadataType.Source: - loaded.AddLink(text, LinkAction.SearchBeatmapSet, text); + if (searchAction != null) + loaded.AddLink(text, () => searchAction(text)); + else + loaded.AddLink(text, LinkAction.SearchBeatmapSet, text); + break; default: From 97c3eea3aaa939ad9b0d23862a6ab0434bc4bf3f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 15 Jul 2022 06:41:49 +0300 Subject: [PATCH 560/803] Fix beatmap details source and tags not filtering on song select --- osu.Game/Screens/Select/BeatmapDetails.cs | 15 ++++++++++++--- osu.Game/Screens/Select/FilterControl.cs | 21 +++++++++++---------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 2fe62c4a4e..a877dc4863 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -40,6 +40,9 @@ namespace osu.Game.Screens.Select [Resolved] private IAPIProvider api { get; set; } + [Resolved(canBeNull: true)] + private SongSelect songSelect { get; set; } + private IBeatmapInfo beatmapInfo; private APIFailTimes failTimes; @@ -140,9 +143,9 @@ namespace osu.Game.Screens.Select LayoutEasing = Easing.OutQuad, Children = new[] { - description = new MetadataSection(MetadataType.Description), - source = new MetadataSection(MetadataType.Source), - tags = new MetadataSection(MetadataType.Tags), + description = new MetadataSection(MetadataType.Description, searchOnSongSelect), + source = new MetadataSection(MetadataType.Source, searchOnSongSelect), + tags = new MetadataSection(MetadataType.Tags, searchOnSongSelect), }, }, }, @@ -175,6 +178,12 @@ namespace osu.Game.Screens.Select }, loading = new LoadingLayer(true) }; + + void searchOnSongSelect(string text) + { + if (songSelect != null) + songSelect.FilterControl.SearchTextBox.Text = text; + } } private void updateStatistics() diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index a92b631100..fe8369ed0c 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Select public FilterCriteria CreateCriteria() { - string query = searchTextBox.Text; + string query = SearchTextBox.Text; var criteria = new FilterCriteria { @@ -62,7 +62,8 @@ namespace osu.Game.Screens.Select return criteria; } - private SeekLimitedSearchTextBox searchTextBox; + public SeekLimitedSearchTextBox SearchTextBox { get; private set; } + private CollectionFilterDropdown collectionDropdown; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => @@ -103,7 +104,7 @@ namespace osu.Game.Screens.Select Height = 60, Children = new Drawable[] { - searchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X }, + SearchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X }, new Box { RelativeSizeAxes = Axes.X, @@ -204,23 +205,23 @@ namespace osu.Game.Screens.Select updateCriteria(); }; - searchTextBox.Current.ValueChanged += _ => updateCriteria(); + SearchTextBox.Current.ValueChanged += _ => updateCriteria(); updateCriteria(); } public void Deactivate() { - searchTextBox.ReadOnly = true; - searchTextBox.HoldFocus = false; - if (searchTextBox.HasFocus) - GetContainingInputManager().ChangeFocus(searchTextBox); + SearchTextBox.ReadOnly = true; + SearchTextBox.HoldFocus = false; + if (SearchTextBox.HasFocus) + GetContainingInputManager().ChangeFocus(SearchTextBox); } public void Activate() { - searchTextBox.ReadOnly = false; - searchTextBox.HoldFocus = true; + SearchTextBox.ReadOnly = false; + SearchTextBox.HoldFocus = true; } private readonly IBindable ruleset = new Bindable(); From 86d019c2b236e29dbba86f1db034837f0e28bea1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 15 Jul 2022 06:52:06 +0300 Subject: [PATCH 561/803] Enable NRT on `BeatmapDetails` --- osu.Game/Screens/Select/BeatmapDetails.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index a877dc4863..80721d14ae 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -38,18 +36,18 @@ namespace osu.Game.Screens.Select private readonly LoadingLayer loading; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; - [Resolved(canBeNull: true)] - private SongSelect songSelect { get; set; } + [Resolved] + private SongSelect? songSelect { get; set; } - private IBeatmapInfo beatmapInfo; + private IBeatmapInfo? beatmapInfo; - private APIFailTimes failTimes; + private APIFailTimes? failTimes; - private int[] ratings; + private int[]? ratings; - public IBeatmapInfo BeatmapInfo + public IBeatmapInfo? BeatmapInfo { get => beatmapInfo; set @@ -59,7 +57,7 @@ namespace osu.Game.Screens.Select beatmapInfo = value; var onlineInfo = beatmapInfo as IBeatmapOnlineInfo; - var onlineSetInfo = beatmapInfo.BeatmapSet as IBeatmapSetOnlineInfo; + var onlineSetInfo = beatmapInfo?.BeatmapSet as IBeatmapSetOnlineInfo; failTimes = onlineInfo?.FailTimes; ratings = onlineSetInfo?.Ratings; From 254d22de1c94ec53347dc597a3cdca70f8c219e5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 15 Jul 2022 07:44:56 +0300 Subject: [PATCH 562/803] Use proper variable name --- osu.Game/Collections/CollectionToggleMenuItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Collections/CollectionToggleMenuItem.cs b/osu.Game/Collections/CollectionToggleMenuItem.cs index fbc935d19a..f2b10305b8 100644 --- a/osu.Game/Collections/CollectionToggleMenuItem.cs +++ b/osu.Game/Collections/CollectionToggleMenuItem.cs @@ -9,9 +9,9 @@ namespace osu.Game.Collections public class CollectionToggleMenuItem : ToggleMenuItem { public CollectionToggleMenuItem(BeatmapCollection collection, IBeatmapInfo beatmap) - : base(collection.Name.Value, MenuItemType.Standard, s => + : base(collection.Name.Value, MenuItemType.Standard, state => { - if (s) + if (state) collection.BeatmapHashes.Add(beatmap.MD5Hash); else collection.BeatmapHashes.Remove(beatmap.MD5Hash); From 7e80a710204074a1011eb66f664b7061f734e9fa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 15 Jul 2022 08:16:36 +0300 Subject: [PATCH 563/803] Replace download tracker with local querying --- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index d89816d3c7..f38077a9a7 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -5,12 +5,11 @@ 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; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -95,12 +94,12 @@ namespace osu.Game.Screens.OnlinePlay private PanelBackground panelBackground; private FillFlowContainer mainFillFlow; - [CanBeNull] - private BeatmapDownloadTracker downloadTracker; - [Resolved] private RulesetStore rulesets { get; set; } + [Resolved] + private BeatmapManager beatmaps { get; set; } + [Resolved] private OsuColour colours { get; set; } @@ -321,15 +320,6 @@ namespace osu.Game.Screens.OnlinePlay difficultyIconContainer.FadeInFromZero(500, Easing.OutQuint); mainFillFlow.FadeInFromZero(500, Easing.OutQuint); - - downloadTracker?.RemoveAndDisposeImmediately(); - - if (beatmap != null) - { - Debug.Assert(beatmap.BeatmapSet != null); - downloadTracker = new BeatmapDownloadTracker(beatmap.BeatmapSet); - AddInternal(downloadTracker); - } } protected override Drawable CreateContent() @@ -505,13 +495,16 @@ namespace osu.Game.Screens.OnlinePlay if (beatmapOverlay != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(Item.Beatmap.OnlineID))); - if (beatmap != null && collectionManager != null && downloadTracker?.State.Value == DownloadState.LocallyAvailable) + if (collectionManager != null && beatmap != null) { - var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); - if (manageCollectionsDialog != null) - collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + if (beatmaps.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID) is BeatmapInfo local && !local.BeatmapSet.AsNonNull().DeletePending) + { + var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); - items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + } } return items.ToArray(); From 966882013df529b3662f8b594a7fe7dccd240906 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 15:47:05 +0900 Subject: [PATCH 564/803] Remove classic mod attribution to `SoloScoreInfo` conversion path --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index b70da194a5..d139718a3c 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -103,9 +103,6 @@ namespace osu.Game.Online.API.Requests.Responses var mods = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); - // all API scores provided by this class are considered to be legacy. - mods = mods.Append(rulesetInstance.CreateMod()).ToArray(); - var scoreInfo = ToScoreInfo(mods); scoreInfo.Ruleset = ruleset; From 688fcb256f41e5ad0fe96ee3fe2b9d2f82b5b960 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 15:47:25 +0900 Subject: [PATCH 565/803] Update score retrieval endpoint to access new storage --- osu.Game/Online/API/Requests/GetScoresRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index a6cd9a52c7..966e69938c 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -35,7 +35,7 @@ namespace osu.Game.Online.API.Requests this.mods = mods ?? Array.Empty(); } - protected override string Target => $@"beatmaps/{beatmapInfo.OnlineID}/scores{createQueryParameters()}"; + protected override string Target => $@"beatmaps/{beatmapInfo.OnlineID}/solo-scores{createQueryParameters()}"; private string createQueryParameters() { From 6122d2a52553a473f8a64fbb09bc78081d1740a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 15:58:52 +0900 Subject: [PATCH 566/803] Add "F" `ScoreRank` to handle old scores which have this specified Not sure on the future of this, but given it is used in the save-failed-reply pull request (#18785) I think it's fine to add back for now. Without this, JSON parsing of older scores in server-side storage will fail on missing enum type. --- osu.Game/Scoring/ScoreRank.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Scoring/ScoreRank.cs b/osu.Game/Scoring/ScoreRank.cs index 9de8561b4a..a1916953c4 100644 --- a/osu.Game/Scoring/ScoreRank.cs +++ b/osu.Game/Scoring/ScoreRank.cs @@ -11,6 +11,10 @@ namespace osu.Game.Scoring { public enum ScoreRank { + // TODO: Localisable? + [Description(@"F")] + F = -1, + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.RankD))] [Description(@"D")] D, From c8c79d2185d38b2cd440ed79e2448804ca74bd07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 16:11:11 +0900 Subject: [PATCH 567/803] Standardise `HasReplay` implementation (and remove from persisting to realm) --- osu.Game/Database/EFToRealmMigrator.cs | 1 - osu.Game/Database/RealmAccess.cs | 3 ++- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 3 +-- osu.Game/Scoring/ScoreInfo.cs | 2 +- osu.Game/Screens/Ranking/ReplayDownloadButton.cs | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 3b5424b3fb..294a8cd3ed 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -443,7 +443,6 @@ namespace osu.Game.Database TotalScore = score.TotalScore, MaxCombo = score.MaxCombo, Accuracy = score.Accuracy, - HasReplay = ((IScoreInfo)score).HasReplay, Date = score.Date, PP = score.PP, Rank = score.Rank, diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 02b5a51f1f..bebc101b13 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -59,8 +59,9 @@ namespace osu.Game.Database /// 13 2022-01-13 Final migration of beatmaps and scores to realm (multiple new storage fields). /// 14 2022-03-01 Added BeatmapUserSettings to BeatmapInfo. /// 15 2022-07-13 Added LastPlayed to BeatmapInfo. + /// 16 2022-07-15 Removed HasReplay from ScoreInfo. /// - private const int schema_version = 15; + private const int schema_version = 16; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index d139718a3c..6c48c7883f 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -129,8 +129,7 @@ namespace osu.Game.Online.API.Requests.Responses Rank = Rank, Statistics = Statistics, Date = EndedAt ?? DateTimeOffset.Now, - Hash = "online", // TODO: temporary? - HasReplay = HasReplay, + Hash = HasReplay ? "online" : string.Empty, // TODO: temporary? Mods = mods, PP = PP, }; diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index cbe9615380..81ca5f0300 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -45,7 +45,7 @@ namespace osu.Game.Scoring public double Accuracy { get; set; } - public bool HasReplay { get; set; } + public bool HasReplay => !string.IsNullOrEmpty(Hash); public DateTimeOffset Date { get; set; } diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index 954a5159b9..7081a0156e 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Ranking if (State.Value == DownloadState.LocallyAvailable) return ReplayAvailability.Local; - if (!string.IsNullOrEmpty(Score.Value?.Hash)) + if (Score.Value?.HasReplay == true) return ReplayAvailability.Online; return ReplayAvailability.NotAvailable; From 0ade8db550cf193b7093d5d42658f690bbba2a38 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 16:40:48 +0900 Subject: [PATCH 568/803] Tidy up nullability and casting --- osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index 943b7d3b4f..321705d25e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { public abstract class FollowCircle : CompositeDrawable { - [Resolved(canBeNull: true)] + [Resolved] protected DrawableHitObject? ParentObject { get; private set; } protected FollowCircle() @@ -23,11 +23,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load() { - if (ParentObject != null) - { - var slider = (DrawableSlider)ParentObject; - slider.Tracking.BindValueChanged(OnTrackingChanged, true); - } + ((DrawableSlider?)ParentObject)?.Tracking.BindValueChanged(OnTrackingChanged, true); } protected override void LoadComplete() From 7ed4eb5815cc682567da35177f5d7f774a406cad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 17:01:01 +0900 Subject: [PATCH 569/803] Adjust transform logic to match osu-stable (and add TODOs for remaining oversights) --- .../Skinning/Legacy/LegacyFollowCircle.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index 38e2e74349..324f2525bc 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.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.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -28,28 +28,28 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (ParentObject.Judged) return; - const float scale_duration = 180f; - const float fade_duration = 90f; + double remainingTime = ParentObject.HitStateUpdateTime - Time.Current; - double maxScaleDuration = ParentObject.HitStateUpdateTime - Time.Current; - double realScaleDuration = scale_duration; - if (tracking.NewValue && maxScaleDuration < realScaleDuration && maxScaleDuration >= 0) - realScaleDuration = maxScaleDuration; - double realFadeDuration = fade_duration * realScaleDuration / fade_duration; - - this.ScaleTo(tracking.NewValue ? DrawableSliderBall.FOLLOW_AREA : 1f, realScaleDuration, Easing.OutQuad) - .FadeTo(tracking.NewValue ? 1f : 0f, realFadeDuration, Easing.OutQuad); + // Note that the scale adjust here is 2 instead of DrawableSliderBall.FOLLOW_AREA to match legacy behaviour. + // This means the actual tracking area for gameplay purposes is larger than the sprite (but skins may be accounting for this). + if (tracking.NewValue) + { + // TODO: Follow circle should bounce on each slider tick. + this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out) + .FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime)); + } + else + { + // TODO: Should animate only at the next slider tick if we want to match stable perfectly. + this.ScaleTo(4f, 100) + .FadeTo(0f, 100); + } } protected override void OnSliderEnd() { - const float shrink_duration = 200f; - const float fade_delay = 175f; - const float fade_duration = 35f; - - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.75f, shrink_duration, Easing.OutQuad) - .Delay(fade_delay) - .FadeOut(fade_duration); + this.ScaleTo(1.6f, 200, Easing.Out) + .FadeOut(200, Easing.In); } } } From afec7941ffc9915fd99ba3e4fb680b0868edadee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 17:24:40 +0900 Subject: [PATCH 570/803] Adjust default follow circle animations to feel nicer --- .../Skinning/Default/DefaultFollowCircle.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 254e220996..07b99560e5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK.Graphics; @@ -32,11 +33,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default protected override void OnTrackingChanged(ValueChangedEvent tracking) { - const float scale_duration = 300f; - const float fade_duration = 300f; + const float duration = 300f; - this.ScaleTo(tracking.NewValue ? DrawableSliderBall.FOLLOW_AREA : 1f, scale_duration, Easing.OutQuint) - .FadeTo(tracking.NewValue ? 1f : 0, fade_duration, Easing.OutQuint); + if (tracking.NewValue) + { + if (Precision.AlmostEquals(0, Alpha)) + this.ScaleTo(1); + + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA, duration, Easing.OutQuint) + .FadeTo(1f, duration, Easing.OutQuint); + } + else + { + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.2f, duration / 2, Easing.OutQuint) + .FadeTo(0, duration / 2, Easing.OutQuint); + } } protected override void OnSliderEnd() From 4b253f83c3ca9c1795494ae2b6e48ce2b4ef2b32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 17:39:08 +0900 Subject: [PATCH 571/803] Fix intermittent test failures due to randomised score statistics --- .../Visual/Online/TestSceneScoresContainer.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index be03328caa..6ef87f762c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -274,14 +274,18 @@ namespace osu.Game.Tests.Visual.Online } }; + const int initial_great_count = 2000; + + int greatCount = initial_great_count; + foreach (var s in scores.Scores) { s.Statistics = new Dictionary { - { HitResult.Great, RNG.Next(2000) }, - { HitResult.Ok, RNG.Next(2000) }, - { HitResult.Meh, RNG.Next(2000) }, - { HitResult.Miss, RNG.Next(2000) } + { HitResult.Great, greatCount -= 100 }, + { HitResult.Ok, RNG.Next(100) }, + { HitResult.Meh, RNG.Next(100) }, + { HitResult.Miss, initial_great_count - greatCount } }; } From ba0a1587402ebd9fc6fd8d18743b093c8933b5cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 17:50:10 +0900 Subject: [PATCH 572/803] Show search online prompt even when no beatmaps are available locally --- osu.Game/Screens/Select/NoResultsPlaceholder.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/NoResultsPlaceholder.cs b/osu.Game/Screens/Select/NoResultsPlaceholder.cs index b8b589ff99..f3c3fb4d87 100644 --- a/osu.Game/Screens/Select/NoResultsPlaceholder.cs +++ b/osu.Game/Screens/Select/NoResultsPlaceholder.cs @@ -109,7 +109,7 @@ namespace osu.Game.Screens.Select textFlow.AddParagraph("No beatmaps found!"); textFlow.AddParagraph(string.Empty); - textFlow.AddParagraph("Consider using the \""); + textFlow.AddParagraph("- Consider running the \""); textFlow.AddLink(FirstRunSetupOverlayStrings.FirstRunSetupTitle, () => firstRunSetupOverlay?.Show()); textFlow.AddText("\" to download or import some beatmaps!"); } @@ -141,15 +141,14 @@ namespace osu.Game.Screens.Select textFlow.AddLink(" enabling ", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); textFlow.AddText("automatic conversion!"); } - - if (!string.IsNullOrEmpty(filter?.SearchText)) - { - textFlow.AddParagraph("- Try "); - textFlow.AddLink("searching online", LinkAction.SearchBeatmapSet, filter.SearchText); - textFlow.AddText($" for \"{filter.SearchText}\"."); - } } + if (!string.IsNullOrEmpty(filter?.SearchText)) + { + textFlow.AddParagraph("- Try "); + textFlow.AddLink("searching online", LinkAction.SearchBeatmapSet, filter.SearchText); + textFlow.AddText($" for \"{filter.SearchText}\"."); + } // TODO: add clickable link to reset criteria. } } From 437e01427c56c01a0aa262dcf6834c582c42236b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 17:53:44 +0900 Subject: [PATCH 573/803] Fix beatmap listing not entering correct search mode when arriving before `LoadComplete` --- osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 767b8646e3..2ca369d459 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -143,7 +143,7 @@ namespace osu.Game.Overlays.BeatmapListing } public void Search(string query) - => searchControl.Query.Value = query; + => Schedule(() => searchControl.Query.Value = query); protected override void LoadComplete() { From 105ffdbbdd200991de8a0961db6431dd90272578 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 17:57:14 +0900 Subject: [PATCH 574/803] 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 caaa83bff4..d5390e6a3d 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 355fd5f458..61fcf2e375 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 fcf71f3ab0..8843b5c831 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From e12e4803939cd3058bffac6c83262fc8916aa352 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 18:01:35 +0900 Subject: [PATCH 575/803] Only expose bindable string rather than full textbox --- osu.Game/Screens/Select/BeatmapDetails.cs | 2 +- osu.Game/Screens/Select/FilterControl.cs | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 80721d14ae..90418efe15 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -180,7 +180,7 @@ namespace osu.Game.Screens.Select void searchOnSongSelect(string text) { if (songSelect != null) - songSelect.FilterControl.SearchTextBox.Text = text; + songSelect.FilterControl.CurrentTextSearch.Value = text; } } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index fe8369ed0c..e43261f374 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -31,6 +31,8 @@ namespace osu.Game.Screens.Select public Action FilterChanged; + public Bindable CurrentTextSearch => searchTextBox.Current; + private OsuTabControl sortTabs; private Bindable sortMode; @@ -39,7 +41,7 @@ namespace osu.Game.Screens.Select public FilterCriteria CreateCriteria() { - string query = SearchTextBox.Text; + string query = searchTextBox.Text; var criteria = new FilterCriteria { @@ -62,7 +64,7 @@ namespace osu.Game.Screens.Select return criteria; } - public SeekLimitedSearchTextBox SearchTextBox { get; private set; } + private SeekLimitedSearchTextBox searchTextBox; private CollectionFilterDropdown collectionDropdown; @@ -104,7 +106,7 @@ namespace osu.Game.Screens.Select Height = 60, Children = new Drawable[] { - SearchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X }, + searchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X }, new Box { RelativeSizeAxes = Axes.X, @@ -205,23 +207,23 @@ namespace osu.Game.Screens.Select updateCriteria(); }; - SearchTextBox.Current.ValueChanged += _ => updateCriteria(); + searchTextBox.Current.ValueChanged += _ => updateCriteria(); updateCriteria(); } public void Deactivate() { - SearchTextBox.ReadOnly = true; - SearchTextBox.HoldFocus = false; - if (SearchTextBox.HasFocus) - GetContainingInputManager().ChangeFocus(SearchTextBox); + searchTextBox.ReadOnly = true; + searchTextBox.HoldFocus = false; + if (searchTextBox.HasFocus) + GetContainingInputManager().ChangeFocus(searchTextBox); } public void Activate() { - SearchTextBox.ReadOnly = false; - SearchTextBox.HoldFocus = true; + searchTextBox.ReadOnly = false; + searchTextBox.HoldFocus = true; } private readonly IBindable ruleset = new Bindable(); From cb1d60cd3634234a1ceef318f70ab29266a5b9dc Mon Sep 17 00:00:00 2001 From: Susko3 Date: Fri, 15 Jul 2022 11:57:05 +0200 Subject: [PATCH 576/803] Include new file in compile --- osu.Android/osu.Android.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index 90b02c527b..004cc8c39c 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -26,6 +26,7 @@ true + From 8a48cb701dc416ad9ad19ef4ad4d9e27799b2ceb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 18:54:19 +0900 Subject: [PATCH 577/803] Tidy up implementation and remove unnecessary enum --- osu.Game/Online/DownloadState.cs | 2 - .../Screens/Play/SaveFailedScoreButton.cs | 61 ++++++++----------- 2 files changed, 24 insertions(+), 39 deletions(-) diff --git a/osu.Game/Online/DownloadState.cs b/osu.Game/Online/DownloadState.cs index 3d389d45f9..a58c40d16a 100644 --- a/osu.Game/Online/DownloadState.cs +++ b/osu.Game/Online/DownloadState.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Online { public enum DownloadState diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index 256228a1cb..6dfceec9f3 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -18,22 +16,24 @@ namespace osu.Game.Screens.Play { public class SaveFailedScoreButton : CompositeDrawable { - public Func> ImportFailedScore; - private Task saveFailedScoreTask; - private ScoreInfo score; + private readonly Bindable state = new Bindable(); - protected readonly Bindable State = new Bindable(); + private readonly Func> importFailedScore; - private DownloadButton button; + private Task? saveFailedScoreTask; + + private ScoreInfo? score; + + private DownloadButton button = null!; public SaveFailedScoreButton(Func> requestImportFailedScore) { Size = new Vector2(50, 30); - ImportFailedScore = requestImportFailedScore; + importFailedScore = requestImportFailedScore; } - [BackgroundDependencyLoader(true)] - private void load(OsuGame game) + [BackgroundDependencyLoader] + private void load(OsuGame? game) { InternalChild = button = new DownloadButton { @@ -42,13 +42,13 @@ namespace osu.Game.Screens.Play button.Action = () => { - switch (State.Value) + switch (state.Value) { - case ImportState.Imported: + case DownloadState.LocallyAvailable: game?.PresentScore(score, ScorePresentType.Gameplay); break; - case ImportState.Importing: + case DownloadState.Importing: break; default: @@ -56,24 +56,24 @@ namespace osu.Game.Screens.Play break; } }; - State.BindValueChanged(state => + state.BindValueChanged(state => { switch (state.NewValue) { - case ImportState.Imported: + case DownloadState.LocallyAvailable: button.State.Value = DownloadState.LocallyAvailable; break; - case ImportState.Importing: + case DownloadState.Importing: button.State.Value = DownloadState.Importing; break; - case ImportState.Failed: + case DownloadState.NotDownloaded: button.State.Value = DownloadState.NotDownloaded; break; } }, true); - State.BindValueChanged(updateState, true); + state.BindValueChanged(updateState, true); } private void saveScore() @@ -83,48 +83,35 @@ namespace osu.Game.Screens.Play return; } - State.Value = ImportState.Importing; + state.Value = DownloadState.Importing; - saveFailedScoreTask = Task.Run(ImportFailedScore); + saveFailedScoreTask = Task.Run(importFailedScore); saveFailedScoreTask.ContinueWith(s => Schedule(() => { score = s.GetAwaiter().GetResult(); - State.Value = score != null ? ImportState.Imported : ImportState.Failed; + state.Value = score != null ? DownloadState.LocallyAvailable : DownloadState.NotDownloaded; })); } - private void updateState(ValueChangedEvent state) + private void updateState(ValueChangedEvent state) { switch (state.NewValue) { - case ImportState.Imported: + case DownloadState.LocallyAvailable: button.TooltipText = @"Watch replay"; button.Enabled.Value = true; break; - case ImportState.Importing: + case DownloadState.Importing: button.TooltipText = @"Importing score"; button.Enabled.Value = false; break; - case ImportState.Failed: - button.TooltipText = @"Import failed, click button to re-import"; - button.Enabled.Value = true; - break; - default: button.TooltipText = @"Save score"; button.Enabled.Value = true; break; } } - - public enum ImportState - { - NotImported, - Failed, - Importing, - Imported - } } } From 0e788ac7147c8c01f1cc72c6db6a74d0a072e33d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 19:02:12 +0900 Subject: [PATCH 578/803] Simplify bindable logic greatly --- .../Screens/Play/SaveFailedScoreButton.cs | 88 ++++++------------- 1 file changed, 28 insertions(+), 60 deletions(-) diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index 6dfceec9f3..ed4aa96977 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -5,6 +5,7 @@ using System; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Scoring; @@ -20,16 +21,15 @@ namespace osu.Game.Screens.Play private readonly Func> importFailedScore; - private Task? saveFailedScoreTask; - private ScoreInfo? score; private DownloadButton button = null!; - public SaveFailedScoreButton(Func> requestImportFailedScore) + public SaveFailedScoreButton(Func> importFailedScore) { Size = new Vector2(50, 30); - importFailedScore = requestImportFailedScore; + + this.importFailedScore = importFailedScore; } [BackgroundDependencyLoader] @@ -38,80 +38,48 @@ namespace osu.Game.Screens.Play InternalChild = button = new DownloadButton { RelativeSizeAxes = Axes.Both, - }; - - button.Action = () => - { - switch (state.Value) + State = { BindTarget = state }, + Action = () => { - case DownloadState.LocallyAvailable: - game?.PresentScore(score, ScorePresentType.Gameplay); - break; + switch (state.Value) + { + case DownloadState.LocallyAvailable: + game?.PresentScore(score, ScorePresentType.Gameplay); + break; - case DownloadState.Importing: - break; + case DownloadState.NotDownloaded: + state.Value = DownloadState.Importing; - default: - saveScore(); - break; + Task.Run(importFailedScore).ContinueWith(result => Schedule(() => + { + score = result.GetResultSafely(); + state.Value = score != null ? DownloadState.LocallyAvailable : DownloadState.NotDownloaded; + })); + break; + } } }; + state.BindValueChanged(state => { switch (state.NewValue) { case DownloadState.LocallyAvailable: - button.State.Value = DownloadState.LocallyAvailable; + button.TooltipText = @"watch replay"; + button.Enabled.Value = true; break; case DownloadState.Importing: - button.State.Value = DownloadState.Importing; + button.TooltipText = @"importing score"; + button.Enabled.Value = false; break; - case DownloadState.NotDownloaded: - button.State.Value = DownloadState.NotDownloaded; + default: + button.TooltipText = @"save score"; + button.Enabled.Value = true; break; } }, true); - state.BindValueChanged(updateState, true); - } - - private void saveScore() - { - if (saveFailedScoreTask != null) - { - return; - } - - state.Value = DownloadState.Importing; - - saveFailedScoreTask = Task.Run(importFailedScore); - saveFailedScoreTask.ContinueWith(s => Schedule(() => - { - score = s.GetAwaiter().GetResult(); - state.Value = score != null ? DownloadState.LocallyAvailable : DownloadState.NotDownloaded; - })); - } - - private void updateState(ValueChangedEvent state) - { - switch (state.NewValue) - { - case DownloadState.LocallyAvailable: - button.TooltipText = @"Watch replay"; - button.Enabled.Value = true; - break; - - case DownloadState.Importing: - button.TooltipText = @"Importing score"; - button.Enabled.Value = false; - break; - - default: - button.TooltipText = @"Save score"; - button.Enabled.Value = true; - break; - } } } } From 0200ef1d481dde0070c1d39109162892e98863e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 19:06:44 +0900 Subject: [PATCH 579/803] Make delegate firing more safe to being set later than BDL --- osu.Game/Screens/Play/FailOverlay.cs | 2 +- osu.Game/Screens/Play/Player.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index 2af5102369..76cd030a8f 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Horizontal, Children = new Drawable[] { - new SaveFailedScoreButton(SaveReplay) + new SaveFailedScoreButton(() => SaveReplay()) { Width = 300 }, diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 19f0c90341..17ab8f6710 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1048,6 +1048,7 @@ namespace osu.Game.Screens.Play { Score.ScoreInfo.Passed = false; Score.ScoreInfo.Rank = ScoreRank.F; + var scoreCopy = Score.DeepClone(); try From 775c6c83748df35e1ac23869124f461589b31ea6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 19:29:37 +0900 Subject: [PATCH 580/803] Fix potential crash in editor from transform time going below zero --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index 324f2525bc..5b7da5a1ba 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (ParentObject.Judged) return; - double remainingTime = ParentObject.HitStateUpdateTime - Time.Current; + double remainingTime = Math.Max(0, ParentObject.HitStateUpdateTime - Time.Current); // Note that the scale adjust here is 2 instead of DrawableSliderBall.FOLLOW_AREA to match legacy behaviour. // This means the actual tracking area for gameplay purposes is larger than the sprite (but skins may be accounting for this). From 21433d4ecbfde8ca42cf95f8697fcc1ad4ace81b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 20:14:58 +0900 Subject: [PATCH 581/803] Add test coverage of saving a failed score --- .../TestScenePlayerLocalScoreImport.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 5ec9e88728..113a0874ce 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -8,14 +8,18 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Extensions; using osu.Framework.Extensions.ObjectExtensions; +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.Containers; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Tests.Resources; @@ -58,13 +62,30 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool HasCustomSteps => true; - protected override bool AllowFail => false; + protected override bool AllowFail => allowFail; + + private bool allowFail; + + [Test] + public void TestSaveFailedReplay() + { + AddStep("set fail", () => allowFail = true); + AddStep("set no custom ruleset", () => customRuleset = null); + + CreateTest(); + + AddUntilStep("fail screen displayed", () => Player.ChildrenOfType().First().State.Value == Visibility.Visible); + AddUntilStep("score not in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) == null)); + AddStep("click save button", () => Player.ChildrenOfType().First().ChildrenOfType().First().TriggerClick()); + AddUntilStep("score not in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); + } [Test] public void TestLastPlayedUpdated() { DateTimeOffset? getLastPlayed() => Realm.Run(r => r.Find(Beatmap.Value.BeatmapInfo.ID)?.LastPlayed); + AddStep("set no fail", () => allowFail = false); AddStep("set no custom ruleset", () => customRuleset = null); AddAssert("last played is null", () => getLastPlayed() == null); @@ -77,6 +98,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestScoreStoredLocally() { + AddStep("set no fail", () => allowFail = false); AddStep("set no custom ruleset", () => customRuleset = null); CreateTest(); @@ -94,6 +116,7 @@ namespace osu.Game.Tests.Visual.Gameplay { Ruleset createCustomRuleset() => new CustomRuleset(); + AddStep("set no fail", () => allowFail = false); AddStep("import custom ruleset", () => Realm.Write(r => r.Add(createCustomRuleset().RulesetInfo))); AddStep("set custom ruleset", () => customRuleset = createCustomRuleset()); From d325c534ab60b0b58bc36015e5e3f32915016c8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 20:45:37 +0900 Subject: [PATCH 582/803] Check whether score is already imported and show correct state for save button --- osu.Game/Screens/Play/SaveFailedScoreButton.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index ed4aa96977..7eed2086ae 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Database; using osu.Game.Scoring; using osu.Game.Graphics.UserInterface; using osu.Game.Online; @@ -33,7 +34,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load(OsuGame? game) + private void load(OsuGame? game, Player? player, RealmAccess realm) { InternalChild = button = new DownloadButton { @@ -60,6 +61,13 @@ namespace osu.Game.Screens.Play } }; + if (player != null) + { + score = realm.Run(r => r.Find(player.Score.ScoreInfo.ID)?.Detach()); + if (score != null) + state.Value = DownloadState.LocallyAvailable; + } + state.BindValueChanged(state => { switch (state.NewValue) From 2beed6d7b77b9c2ecad01cda12befbacf5422d0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 20:45:48 +0900 Subject: [PATCH 583/803] Allow failed scores to fail in replay playback --- osu.Game/Screens/Play/ReplayPlayer.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 64b4853a67..e82238945b 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -22,12 +22,21 @@ namespace osu.Game.Screens.Play { private readonly Func, Score> createScore; + private readonly bool replayIsFailedScore; + // Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108) - protected override bool CheckModsAllowFailure() => false; + protected override bool CheckModsAllowFailure() + { + if (!replayIsFailedScore) + return false; + + return base.CheckModsAllowFailure(); + } public ReplayPlayer(Score score, PlayerConfiguration configuration = null) : this((_, _) => score, configuration) { + replayIsFailedScore = score.ScoreInfo.Rank == ScoreRank.F; } public ReplayPlayer(Func, Score> createScore, PlayerConfiguration configuration = null) From c64b5cc48bd49bd2db62f62a5d2c88eae77556c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Jul 2022 02:47:08 +0900 Subject: [PATCH 584/803] 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 d5390e6a3d..04b16c5b0f 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 61fcf2e375..1d3cd39cf3 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 8843b5c831..dfd229755f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From 68afb65affc2bd3313100eba9192ff7d7183ebc1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 15 Jul 2022 22:09:54 +0300 Subject: [PATCH 585/803] Move default state steps to `SetUp` rather than duplicating per test case --- .../Gameplay/TestScenePlayerLocalScoreImport.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 113a0874ce..6491987abe 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -66,11 +66,17 @@ namespace osu.Game.Tests.Visual.Gameplay private bool allowFail; + [SetUp] + public void SetUp() + { + allowFail = false; + customRuleset = null; + } + [Test] public void TestSaveFailedReplay() { - AddStep("set fail", () => allowFail = true); - AddStep("set no custom ruleset", () => customRuleset = null); + AddStep("allow fail", () => allowFail = true); CreateTest(); @@ -85,8 +91,6 @@ namespace osu.Game.Tests.Visual.Gameplay { DateTimeOffset? getLastPlayed() => Realm.Run(r => r.Find(Beatmap.Value.BeatmapInfo.ID)?.LastPlayed); - AddStep("set no fail", () => allowFail = false); - AddStep("set no custom ruleset", () => customRuleset = null); AddAssert("last played is null", () => getLastPlayed() == null); CreateTest(); @@ -98,9 +102,6 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestScoreStoredLocally() { - AddStep("set no fail", () => allowFail = false); - AddStep("set no custom ruleset", () => customRuleset = null); - CreateTest(); AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); @@ -116,7 +117,6 @@ namespace osu.Game.Tests.Visual.Gameplay { Ruleset createCustomRuleset() => new CustomRuleset(); - AddStep("set no fail", () => allowFail = false); AddStep("import custom ruleset", () => Realm.Write(r => r.Add(createCustomRuleset().RulesetInfo))); AddStep("set custom ruleset", () => customRuleset = createCustomRuleset()); From 6285442b7d997f9da0de8d2600310176b252b4fb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 15 Jul 2022 22:28:09 +0300 Subject: [PATCH 586/803] Fix failed scores not prepared before import --- osu.Game/Screens/Play/Player.cs | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 17ab8f6710..516364e13a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -267,7 +267,12 @@ namespace osu.Game.Screens.Play }, FailOverlay = new FailOverlay { - SaveReplay = saveFailedReplay, + SaveReplay = () => + { + Score.ScoreInfo.Passed = false; + Score.ScoreInfo.Rank = ScoreRank.F; + return prepareAndImportScore(); + }, OnRetry = Restart, OnQuit = () => PerformExit(true), }, @@ -721,7 +726,7 @@ namespace osu.Game.Screens.Play if (!Configuration.ShowResults) return; - prepareScoreForDisplayTask ??= Task.Run(prepareScoreForResults); + prepareScoreForDisplayTask ??= Task.Run(prepareAndImportScore); bool storyboardHasOutro = DimmableStoryboard.ContentDisplayed && !DimmableStoryboard.HasStoryboardEnded.Value; @@ -740,7 +745,7 @@ namespace osu.Game.Screens.Play /// Asynchronously run score preparation operations (database import, online submission etc.). /// /// The final score. - private async Task prepareScoreForResults() + private async Task prepareAndImportScore() { var scoreCopy = Score.DeepClone(); @@ -1044,26 +1049,6 @@ namespace osu.Game.Screens.Play return base.OnExiting(e); } - private async Task saveFailedReplay() - { - Score.ScoreInfo.Passed = false; - Score.ScoreInfo.Rank = ScoreRank.F; - - var scoreCopy = Score.DeepClone(); - - try - { - await ImportScore(scoreCopy); - } - catch (Exception ex) - { - Logger.Error(ex, @"Score import failed!"); - return null; - } - - return scoreCopy.ScoreInfo; - } - /// /// Creates the player's . /// From e6236ba088fddfceafacfae9df40a57b2cd833e5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 15 Jul 2022 23:39:04 +0300 Subject: [PATCH 587/803] Update save score button to check availability after import Previously was relying on whether `SaveReplay` returns null, but since I've changed it to use the standard "prepare score for import" path, the button has to check for local availability after import since that path doesn't return null on fail. --- osu.Game/Screens/Play/FailOverlay.cs | 2 +- osu.Game/Screens/Play/SaveFailedScoreButton.cs | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index 76cd030a8f..2af5102369 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Horizontal, Children = new Drawable[] { - new SaveFailedScoreButton(() => SaveReplay()) + new SaveFailedScoreButton(SaveReplay) { Width = 300 }, diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index 7eed2086ae..3f6e741dff 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Play private readonly Func> importFailedScore; - private ScoreInfo? score; + private ScoreInfo? importedScore; private DownloadButton button = null!; @@ -45,17 +45,16 @@ namespace osu.Game.Screens.Play switch (state.Value) { case DownloadState.LocallyAvailable: - game?.PresentScore(score, ScorePresentType.Gameplay); + game?.PresentScore(importedScore, ScorePresentType.Gameplay); break; case DownloadState.NotDownloaded: state.Value = DownloadState.Importing; - - Task.Run(importFailedScore).ContinueWith(result => Schedule(() => + Task.Run(importFailedScore).ContinueWith(t => { - score = result.GetResultSafely(); - state.Value = score != null ? DownloadState.LocallyAvailable : DownloadState.NotDownloaded; - })); + importedScore = realm.Run(r => r.Find(t.GetResultSafely().ID)?.Detach()); + Schedule(() => state.Value = importedScore != null ? DownloadState.LocallyAvailable : DownloadState.NotDownloaded); + }); break; } } @@ -63,8 +62,8 @@ namespace osu.Game.Screens.Play if (player != null) { - score = realm.Run(r => r.Find(player.Score.ScoreInfo.ID)?.Detach()); - if (score != null) + importedScore = realm.Run(r => r.Find(player.Score.ScoreInfo.ID)?.Detach()); + if (importedScore != null) state.Value = DownloadState.LocallyAvailable; } From b42f49aeaaf7d7a58b5ad08eb8c14067980bc8ac Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 00:34:21 +0300 Subject: [PATCH 588/803] Handle `APIException` from user request during logging in --- osu.Game/Online/API/APIAccess.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 43cea7fb97..7af19f6dd1 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -163,7 +163,13 @@ namespace osu.Game.Online.API userReq.Failure += ex => { - if (ex is WebException webException && webException.Message == @"Unauthorized") + if (ex is APIException) + { + LastLoginError = ex; + log.Add("Login failed on local user retrieval!"); + Logout(); + } + else if (ex is WebException webException && webException.Message == @"Unauthorized") { log.Add(@"Login no longer valid"); Logout(); From 4ea8fd75cc4277833efca0df2b64f51bd30a8381 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 04:05:07 +0300 Subject: [PATCH 589/803] Replace `Country` class with enumeration --- osu.Game/Users/Country.cs | 770 +++++++++++++++++++++++++++++++++++++- 1 file changed, 755 insertions(+), 15 deletions(-) diff --git a/osu.Game/Users/Country.cs b/osu.Game/Users/Country.cs index b38600fa1b..d1f33627c1 100644 --- a/osu.Game/Users/Country.cs +++ b/osu.Game/Users/Country.cs @@ -1,27 +1,767 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using System; +using System.ComponentModel; +using JetBrains.Annotations; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace osu.Game.Users { - public class Country : IEquatable + [JsonConverter(typeof(StringEnumConverter))] + public enum Country { - /// - /// The name of this country. - /// - [JsonProperty(@"name")] - public string FullName; + [Description("Alien")] + XX = 0, - /// - /// Two-letter flag acronym (ISO 3166 standard) - /// - [JsonProperty(@"code")] - public string FlagName; + [Description("Bangladesh")] + BD, - public bool Equals(Country other) => FlagName == other?.FlagName; + [Description("Belgium")] + BE, + + [Description("Burkina Faso")] + BF, + + [Description("Bulgaria")] + BG, + + [Description("Bosnia and Herzegovina")] + BA, + + [Description("Barbados")] + BB, + + [Description("Wallis and Futuna")] + WF, + + [Description("Saint Barthelemy")] + BL, + + [Description("Bermuda")] + BM, + + [Description("Brunei")] + BN, + + [Description("Bolivia")] + BO, + + [Description("Bahrain")] + BH, + + [Description("Burundi")] + BI, + + [Description("Benin")] + BJ, + + [Description("Bhutan")] + BT, + + [Description("Jamaica")] + JM, + + [Description("Bouvet Island")] + BV, + + [Description("Botswana")] + BW, + + [Description("Samoa")] + WS, + + [Description("Bonaire, Saint Eustatius and Saba")] + BQ, + + [Description("Brazil")] + BR, + + [Description("Bahamas")] + BS, + + [Description("Jersey")] + JE, + + [Description("Belarus")] + BY, + + [Description("Belize")] + BZ, + + [Description("Russia")] + RU, + + [Description("Rwanda")] + RW, + + [Description("Serbia")] + RS, + + [Description("East Timor")] + TL, + + [Description("Reunion")] + RE, + + [Description("Turkmenistan")] + TM, + + [Description("Tajikistan")] + TJ, + + [Description("Romania")] + RO, + + [Description("Tokelau")] + TK, + + [Description("Guinea-Bissau")] + GW, + + [Description("Guam")] + GU, + + [Description("Guatemala")] + GT, + + [Description("South Georgia and the South Sandwich Islands")] + GS, + + [Description("Greece")] + GR, + + [Description("Equatorial Guinea")] + GQ, + + [Description("Guadeloupe")] + GP, + + [Description("Japan")] + JP, + + [Description("Guyana")] + GY, + + [Description("Guernsey")] + GG, + + [Description("French Guiana")] + GF, + + [Description("Georgia")] + GE, + + [Description("Grenada")] + GD, + + [Description("United Kingdom")] + GB, + + [Description("Gabon")] + GA, + + [Description("El Salvador")] + SV, + + [Description("Guinea")] + GN, + + [Description("Gambia")] + GM, + + [Description("Greenland")] + GL, + + [Description("Gibraltar")] + GI, + + [Description("Ghana")] + GH, + + [Description("Oman")] + OM, + + [Description("Tunisia")] + TN, + + [Description("Jordan")] + JO, + + [Description("Croatia")] + HR, + + [Description("Haiti")] + HT, + + [Description("Hungary")] + HU, + + [Description("Hong Kong")] + HK, + + [Description("Honduras")] + HN, + + [Description("Heard Island and McDonald Islands")] + HM, + + [Description("Venezuela")] + VE, + + [Description("Puerto Rico")] + PR, + + [Description("Palestinian Territory")] + PS, + + [Description("Palau")] + PW, + + [Description("Portugal")] + PT, + + [Description("Svalbard and Jan Mayen")] + SJ, + + [Description("Paraguay")] + PY, + + [Description("Iraq")] + IQ, + + [Description("Panama")] + PA, + + [Description("French Polynesia")] + PF, + + [Description("Papua New Guinea")] + PG, + + [Description("Peru")] + PE, + + [Description("Pakistan")] + PK, + + [Description("Philippines")] + PH, + + [Description("Pitcairn")] + PN, + + [Description("Poland")] + PL, + + [Description("Saint Pierre and Miquelon")] + PM, + + [Description("Zambia")] + ZM, + + [Description("Western Sahara")] + EH, + + [Description("Estonia")] + EE, + + [Description("Egypt")] + EG, + + [Description("South Africa")] + ZA, + + [Description("Ecuador")] + EC, + + [Description("Italy")] + IT, + + [Description("Vietnam")] + VN, + + [Description("Solomon Islands")] + SB, + + [Description("Ethiopia")] + ET, + + [Description("Somalia")] + SO, + + [Description("Zimbabwe")] + ZW, + + [Description("Saudi Arabia")] + SA, + + [Description("Spain")] + ES, + + [Description("Eritrea")] + ER, + + [Description("Montenegro")] + ME, + + [Description("Moldova")] + MD, + + [Description("Madagascar")] + MG, + + [Description("Saint Martin")] + MF, + + [Description("Morocco")] + MA, + + [Description("Monaco")] + MC, + + [Description("Uzbekistan")] + UZ, + + [Description("Myanmar")] + MM, + + [Description("Mali")] + ML, + + [Description("Macao")] + MO, + + [Description("Mongolia")] + MN, + + [Description("Marshall Islands")] + MH, + + [Description("North Macedonia")] + MK, + + [Description("Mauritius")] + MU, + + [Description("Malta")] + MT, + + [Description("Malawi")] + MW, + + [Description("Maldives")] + MV, + + [Description("Martinique")] + MQ, + + [Description("Northern Mariana Islands")] + MP, + + [Description("Montserrat")] + MS, + + [Description("Mauritania")] + MR, + + [Description("Isle of Man")] + IM, + + [Description("Uganda")] + UG, + + [Description("Tanzania")] + TZ, + + [Description("Malaysia")] + MY, + + [Description("Mexico")] + MX, + + [Description("Israel")] + IL, + + [Description("France")] + FR, + + [Description("British Indian Ocean Territory")] + IO, + + [Description("Saint Helena")] + SH, + + [Description("Finland")] + FI, + + [Description("Fiji")] + FJ, + + [Description("Falkland Islands")] + FK, + + [Description("Micronesia")] + FM, + + [Description("Faroe Islands")] + FO, + + [Description("Nicaragua")] + NI, + + [Description("Netherlands")] + NL, + + [Description("Norway")] + NO, + + [Description("Namibia")] + NA, + + [Description("Vanuatu")] + VU, + + [Description("New Caledonia")] + NC, + + [Description("Niger")] + NE, + + [Description("Norfolk Island")] + NF, + + [Description("Nigeria")] + NG, + + [Description("New Zealand")] + NZ, + + [Description("Nepal")] + NP, + + [Description("Nauru")] + NR, + + [Description("Niue")] + NU, + + [Description("Cook Islands")] + CK, + + [Description("Kosovo")] + XK, + + [Description("Ivory Coast")] + CI, + + [Description("Switzerland")] + CH, + + [Description("Colombia")] + CO, + + [Description("China")] + CN, + + [Description("Cameroon")] + CM, + + [Description("Chile")] + CL, + + [Description("Cocos Islands")] + CC, + + [Description("Canada")] + CA, + + [Description("Republic of the Congo")] + CG, + + [Description("Central African Republic")] + CF, + + [Description("Democratic Republic of the Congo")] + CD, + + [Description("Czech Republic")] + CZ, + + [Description("Cyprus")] + CY, + + [Description("Christmas Island")] + CX, + + [Description("Costa Rica")] + CR, + + [Description("Curacao")] + CW, + + [Description("Cabo Verde")] + CV, + + [Description("Cuba")] + CU, + + [Description("Eswatini")] + SZ, + + [Description("Syria")] + SY, + + [Description("Sint Maarten")] + SX, + + [Description("Kyrgyzstan")] + KG, + + [Description("Kenya")] + KE, + + [Description("South Sudan")] + SS, + + [Description("Suriname")] + SR, + + [Description("Kiribati")] + KI, + + [Description("Cambodia")] + KH, + + [Description("Saint Kitts and Nevis")] + KN, + + [Description("Comoros")] + KM, + + [Description("Sao Tome and Principe")] + ST, + + [Description("Slovakia")] + SK, + + [Description("South Korea")] + KR, + + [Description("Slovenia")] + SI, + + [Description("North Korea")] + KP, + + [Description("Kuwait")] + KW, + + [Description("Senegal")] + SN, + + [Description("San Marino")] + SM, + + [Description("Sierra Leone")] + SL, + + [Description("Seychelles")] + SC, + + [Description("Kazakhstan")] + KZ, + + [Description("Cayman Islands")] + KY, + + [Description("Singapore")] + SG, + + [Description("Sweden")] + SE, + + [Description("Sudan")] + SD, + + [Description("Dominican Republic")] + DO, + + [Description("Dominica")] + DM, + + [Description("Djibouti")] + DJ, + + [Description("Denmark")] + DK, + + [Description("British Virgin Islands")] + VG, + + [Description("Germany")] + DE, + + [Description("Yemen")] + YE, + + [Description("Algeria")] + DZ, + + [Description("United States")] + US, + + [Description("Uruguay")] + UY, + + [Description("Mayotte")] + YT, + + [Description("United States Minor Outlying Islands")] + UM, + + [Description("Lebanon")] + LB, + + [Description("Saint Lucia")] + LC, + + [Description("Laos")] + LA, + + [Description("Tuvalu")] + TV, + + [Description("Taiwan")] + TW, + + [Description("Trinidad and Tobago")] + TT, + + [Description("Turkey")] + TR, + + [Description("Sri Lanka")] + LK, + + [Description("Liechtenstein")] + LI, + + [Description("Latvia")] + LV, + + [Description("Tonga")] + TO, + + [Description("Lithuania")] + LT, + + [Description("Luxembourg")] + LU, + + [Description("Liberia")] + LR, + + [Description("Lesotho")] + LS, + + [Description("Thailand")] + TH, + + [Description("French Southern Territories")] + TF, + + [Description("Togo")] + TG, + + [Description("Chad")] + TD, + + [Description("Turks and Caicos Islands")] + TC, + + [Description("Libya")] + LY, + + [Description("Vatican")] + VA, + + [Description("Saint Vincent and the Grenadines")] + VC, + + [Description("United Arab Emirates")] + AE, + + [Description("Andorra")] + AD, + + [Description("Antigua and Barbuda")] + AG, + + [Description("Afghanistan")] + AF, + + [Description("Anguilla")] + AI, + + [Description("U.S. Virgin Islands")] + VI, + + [Description("Iceland")] + IS, + + [Description("Iran")] + IR, + + [Description("Armenia")] + AM, + + [Description("Albania")] + AL, + + [Description("Angola")] + AO, + + [Description("Antarctica")] + AQ, + + [Description("American Samoa")] + AS, + + [Description("Argentina")] + AR, + + [Description("Australia")] + AU, + + [Description("Austria")] + AT, + + [Description("Aruba")] + AW, + + [Description("India")] + IN, + + [Description("Aland Islands")] + AX, + + [Description("Azerbaijan")] + AZ, + + [Description("Ireland")] + IE, + + [Description("Indonesia")] + ID, + + [Description("Ukraine")] + UA, + + [Description("Qatar")] + QA, + + [Description("Mozambique")] + MZ, } } From 00f4c8052e569ba71a819259a0aff47553add330 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 04:06:25 +0300 Subject: [PATCH 590/803] Update `APIUser` to provide enum from serialised country code --- .../Online/API/Requests/Responses/APIUser.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 63aaa9b90e..1a2a38e789 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -34,8 +34,24 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"previous_usernames")] public string[] PreviousUsernames; + private Country? country; + + public Country Country + { + get => country ??= (Enum.TryParse(userCountry.Code, out Country result) ? result : default); + set => country = value; + } + +#pragma warning disable 649 [JsonProperty(@"country")] - public Country Country; + private UserCountry userCountry; + + private class UserCountry + { + [JsonProperty(@"code")] + public string Code; + } +#pragma warning restore 649 public readonly Bindable Status = new Bindable(); From 1b2b42bb8a7cc2d3b510ef9d45ef075187b832c0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 04:13:04 +0300 Subject: [PATCH 591/803] Update `CountryStatistics` to use `code` for country enum --- osu.Game/Users/CountryStatistics.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Users/CountryStatistics.cs b/osu.Game/Users/CountryStatistics.cs index e784630d56..eaee13a37b 100644 --- a/osu.Game/Users/CountryStatistics.cs +++ b/osu.Game/Users/CountryStatistics.cs @@ -9,11 +9,8 @@ namespace osu.Game.Users { public class CountryStatistics { - [JsonProperty] - public Country Country; - [JsonProperty(@"code")] - public string FlagName; + public Country Country; [JsonProperty(@"active_users")] public long ActiveUsers; From b2b2a4adafab9d6b1dd1d10675b78a629acd0d49 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 04:06:48 +0300 Subject: [PATCH 592/803] Update tournament migration logic to check for null `Country` --- osu.Game.Tournament/Models/TournamentUser.cs | 3 ++- osu.Game.Tournament/TournamentGameBase.cs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Models/TournamentUser.cs b/osu.Game.Tournament/Models/TournamentUser.cs index 80e58538e5..7faf6d1798 100644 --- a/osu.Game.Tournament/Models/TournamentUser.cs +++ b/osu.Game.Tournament/Models/TournamentUser.cs @@ -22,7 +22,8 @@ namespace osu.Game.Tournament.Models /// /// The player's country. /// - public Country? Country { get; set; } + [JsonProperty("country_code")] + public Country Country { get; set; } /// /// The player's global rank, or null if not available. diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index f2a35ea5b3..853ccec83c 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -186,7 +186,9 @@ namespace osu.Game.Tournament { var playersRequiringPopulation = ladder.Teams .SelectMany(t => t.Players) - .Where(p => string.IsNullOrEmpty(p.Username) || p.Rank == null).ToList(); + .Where(p => string.IsNullOrEmpty(p.Username) + || p.Country == default + || p.Rank == null).ToList(); if (playersRequiringPopulation.Count == 0) return false; From e62049f4a94ec526c81eb0ae61d5b02bbd6b083c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 04:40:37 +0300 Subject: [PATCH 593/803] Update various usages of `Country` inline with new enum --- .../Visual/Online/TestSceneFriendDisplay.cs | 6 +- .../Online/TestSceneRankingsCountryFilter.cs | 16 +--- .../Visual/Online/TestSceneRankingsHeader.cs | 15 +--- .../Visual/Online/TestSceneRankingsOverlay.cs | 12 +-- .../Visual/Online/TestSceneRankingsTables.cs | 12 ++- .../Visual/Online/TestSceneScoresContainer.cs | 36 ++------- .../Visual/Online/TestSceneUserPanel.cs | 6 +- .../Online/TestSceneUserProfileOverlay.cs | 8 +- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 74 ++++--------------- .../TestSceneUserTopScoreContainer.cs | 18 +---- .../API/Requests/GetUserRankingsRequest.cs | 9 ++- .../Profile/Header/TopHeaderContainer.cs | 5 +- osu.Game/Overlays/Rankings/CountryFilter.cs | 2 +- osu.Game/Overlays/Rankings/CountryPill.cs | 7 +- .../Rankings/Tables/CountriesTable.cs | 5 +- osu.Game/Overlays/RankingsOverlay.cs | 8 +- .../Participants/ParticipantPanel.cs | 2 +- osu.Game/Users/Drawables/DrawableFlag.cs | 6 +- osu.Game/Users/Drawables/UpdateableFlag.cs | 4 +- 19 files changed, 74 insertions(+), 177 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs index c5c61cdd72..43d41e9fb9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Online Id = 3103765, IsOnline = true, Statistics = new UserStatistics { GlobalRank = 1111 }, - Country = new Country { FlagName = "JP" }, + Country = Country.JP, CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" }, new APIUser @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online Id = 2, IsOnline = false, Statistics = new UserStatistics { GlobalRank = 2222 }, - Country = new Country { FlagName = "AU" }, + Country = Country.AU, CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", IsSupporter = true, SupportLevel = 3, @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Online { Username = "Evast", Id = 8195163, - Country = new Country { FlagName = "BY" }, + Country = Country.BY, CoverUrl = "https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg", IsOnline = false, LastVisit = DateTimeOffset.Now diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsCountryFilter.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsCountryFilter.cs index e7d799222a..ce0ca0b6c9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsCountryFilter.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsCountryFilter.cs @@ -56,20 +56,12 @@ namespace osu.Game.Tests.Visual.Online } }); - var country = new Country - { - FlagName = "BY", - FullName = "Belarus" - }; - var unknownCountry = new Country - { - FlagName = "CK", - FullName = "Cook Islands" - }; + const Country country = Country.BY; + const Country unknown_country = Country.CK; AddStep("Set country", () => countryBindable.Value = country); - AddStep("Set null country", () => countryBindable.Value = null); - AddStep("Set country with no flag", () => countryBindable.Value = unknownCountry); + AddStep("Set default country", () => countryBindable.Value = default); + AddStep("Set country with no flag", () => countryBindable.Value = unknown_country); } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs index c8f08d70be..9aedfb0a14 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs @@ -30,21 +30,12 @@ namespace osu.Game.Tests.Visual.Online Ruleset = { BindTarget = ruleset } }); - var country = new Country - { - FlagName = "BY", - FullName = "Belarus" - }; - - var unknownCountry = new Country - { - FlagName = "CK", - FullName = "Cook Islands" - }; + const Country country = Country.BY; + const Country unknown_country = Country.CK; AddStep("Set country", () => countryBindable.Value = country); AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); - AddStep("Set country with no flag", () => countryBindable.Value = unknownCountry); + AddStep("Set country with no flag", () => countryBindable.Value = unknown_country); } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs index 62dad7b458..ea7eb14c8e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs @@ -48,15 +48,15 @@ namespace osu.Game.Tests.Visual.Online public void TestFlagScopeDependency() { AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); - AddAssert("Check country is Null", () => countryBindable.Value == null); - AddStep("Set country", () => countryBindable.Value = us_country); + AddAssert("Check country is default", () => countryBindable.Value == default); + AddStep("Set country", () => countryBindable.Value = Country.US); AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance); } [Test] public void TestShowCountry() { - AddStep("Show US", () => rankingsOverlay.ShowCountry(us_country)); + AddStep("Show US", () => rankingsOverlay.ShowCountry(Country.US)); } private void loadRankingsOverlay() @@ -69,12 +69,6 @@ namespace osu.Game.Tests.Visual.Online }; } - private static readonly Country us_country = new Country - { - FlagName = "US", - FullName = "United States" - }; - private class TestRankingsOverlay : RankingsOverlay { public new Bindable Country => base.Country; diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs index e357b0fffc..f889e3f7dd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs @@ -57,8 +57,7 @@ namespace osu.Game.Tests.Visual.Online { new CountryStatistics { - Country = new Country { FlagName = "US", FullName = "United States" }, - FlagName = "US", + Country = Country.US, ActiveUsers = 2_972_623, PlayCount = 3_086_515_743, RankedScore = 449_407_643_332_546, @@ -66,8 +65,7 @@ namespace osu.Game.Tests.Visual.Online }, new CountryStatistics { - Country = new Country { FlagName = "RU", FullName = "Russian Federation" }, - FlagName = "RU", + Country = Country.RU, ActiveUsers = 1_609_989, PlayCount = 1_637_052_841, RankedScore = 221_660_827_473_004, @@ -86,7 +84,7 @@ namespace osu.Game.Tests.Visual.Online User = new APIUser { Username = "first active user", - Country = new Country { FlagName = "JP" }, + Country = Country.JP, Active = true, }, Accuracy = 0.9972, @@ -106,7 +104,7 @@ namespace osu.Game.Tests.Visual.Online User = new APIUser { Username = "inactive user", - Country = new Country { FlagName = "AU" }, + Country = Country.AU, Active = false, }, Accuracy = 0.9831, @@ -126,7 +124,7 @@ namespace osu.Game.Tests.Visual.Online User = new APIUser { Username = "second active user", - Country = new Country { FlagName = "PL" }, + Country = Country.PL, Active = true, }, Accuracy = 0.9584, diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 6ef87f762c..6ac3d5cb86 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -157,11 +157,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 6602580, Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, + Country = Country.ES, }, Mods = new[] { @@ -184,11 +180,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 4608074, Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, + Country = Country.BR, }, Mods = new[] { @@ -210,11 +202,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 1014222, Username = @"eLy", - Country = new Country - { - FullName = @"Japan", - FlagName = @"JP", - }, + Country = Country.JP, }, Mods = new[] { @@ -235,11 +223,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 1541390, Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, + Country = Country.CA, }, Mods = new[] { @@ -259,11 +243,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 7151382, Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, + Country = Country.TH, }, Rank = ScoreRank.D, PP = 160, @@ -302,11 +282,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 7151382, Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, + Country = Country.TH, }, Rank = ScoreRank.D, PP = 160, diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index fff40b3c74..addcf799e3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"flyte", Id = 3103765, - Country = new Country { FlagName = @"JP" }, + Country = Country.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg", Status = { Value = new UserStatusOnline() } }) { Width = 300 }, @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"peppy", Id = 2, - Country = new Country { FlagName = @"AU" }, + Country = Country.AU, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", IsSupporter = true, SupportLevel = 3, @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"Evast", Id = 8195163, - Country = new Country { FlagName = @"BY" }, + Country = Country.BY, CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg", IsOnline = false, LastVisit = DateTimeOffset.Now diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index ad3215b1ef..dd6b022624 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"Somebody", Id = 1, - Country = new Country { FullName = @"Alien" }, + Country = Country.XX, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", JoinDate = DateTimeOffset.Now.AddDays(-1), LastVisit = DateTimeOffset.Now, @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Online Username = @"peppy", Id = 2, IsSupporter = true, - Country = new Country { FullName = @"Australia", FlagName = @"AU" }, + Country = Country.AU, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" })); @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"flyte", Id = 3103765, - Country = new Country { FullName = @"Japan", FlagName = @"JP" }, + Country = Country.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" })); @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Online Username = @"BanchoBot", Id = 3, IsBot = true, - Country = new Country { FullName = @"Saint Helena", FlagName = @"SH" }, + Country = Country.SH, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c4.jpg" })); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index ef0c7d7d4d..48fdf671f1 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -140,11 +140,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6602580, Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, + Country = Country.ES, }, }); } @@ -164,12 +160,8 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6602580, Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, - }, + Country = Country.ES, + } }); } @@ -225,11 +217,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6602580, Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, + Country = Country.ES, }, }, new ScoreInfo @@ -246,11 +234,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 4608074, Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, + Country = Country.BR, }, }, new ScoreInfo @@ -268,11 +252,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 1014222, Username = @"eLy", - Country = new Country - { - FullName = @"Japan", - FlagName = @"JP", - }, + Country = Country.JP, }, }, new ScoreInfo @@ -290,11 +270,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 1541390, Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, + Country = Country.CA, }, }, new ScoreInfo @@ -312,11 +288,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 2243452, Username = @"Satoruu", - Country = new Country - { - FullName = @"Venezuela", - FlagName = @"VE", - }, + Country = Country.VE, }, }, new ScoreInfo @@ -334,11 +306,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 2705430, Username = @"Mooha", - Country = new Country - { - FullName = @"France", - FlagName = @"FR", - }, + Country = Country.FR, }, }, new ScoreInfo @@ -356,11 +324,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 7151382, Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, + Country = Country.TH, }, }, new ScoreInfo @@ -378,11 +342,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 2051389, Username = @"FunOrange", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, + Country = Country.CA, }, }, new ScoreInfo @@ -400,11 +360,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6169483, Username = @"-Hebel-", - Country = new Country - { - FullName = @"Mexico", - FlagName = @"MX", - }, + Country = Country.MX, }, }, new ScoreInfo @@ -422,11 +378,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6702666, Username = @"prhtnsm", - Country = new Country - { - FullName = @"Germany", - FlagName = @"DE", - }, + Country = Country.DE, }, }, }; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs index 16966e489a..5356e74eae 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs @@ -69,11 +69,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6602580, Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, + Country = Country.ES, }, }, new ScoreInfo @@ -88,11 +84,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 4608074, Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, + Country = Country.BR, }, }, new ScoreInfo @@ -107,11 +99,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 1541390, Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, + Country = Country.CA, }, } }; diff --git a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs index ab0cc3a56d..75088675bd 100644 --- a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs @@ -5,6 +5,7 @@ using osu.Framework.IO.Network; using osu.Game.Rulesets; +using osu.Game.Users; namespace osu.Game.Online.API.Requests { @@ -12,9 +13,9 @@ namespace osu.Game.Online.API.Requests { public readonly UserRankingsType Type; - private readonly string country; + private readonly Country country; - public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, string country = null) + public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, Country country = default) : base(ruleset, page) { Type = type; @@ -25,8 +26,8 @@ namespace osu.Game.Online.API.Requests { var req = base.CreateWebRequest(); - if (country != null) - req.AddParameter("country", country); + if (country != default) + req.AddParameter("country", country.ToString()); return req; } diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 9c957e387a..818a84b9aa 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; @@ -174,8 +175,8 @@ namespace osu.Game.Overlays.Profile.Header avatar.User = user; usernameText.Text = user?.Username ?? string.Empty; openUserExternally.Link = $@"{api.WebsiteRootUrl}/users/{user?.Id ?? 0}"; - userFlag.Country = user?.Country; - userCountryText.Text = user?.Country?.FullName ?? "Alien"; + userFlag.Country = user?.Country ?? default; + userCountryText.Text = (user?.Country ?? default).GetDescription(); supporterTag.SupportLevel = user?.SupportLevel ?? 0; titleText.Text = user?.Title ?? string.Empty; titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff"); diff --git a/osu.Game/Overlays/Rankings/CountryFilter.cs b/osu.Game/Overlays/Rankings/CountryFilter.cs index 9ba2018522..469d92a771 100644 --- a/osu.Game/Overlays/Rankings/CountryFilter.cs +++ b/osu.Game/Overlays/Rankings/CountryFilter.cs @@ -91,7 +91,7 @@ namespace osu.Game.Overlays.Rankings private void onCountryChanged(ValueChangedEvent country) { - if (country.NewValue == null) + if (country.NewValue == default) { countryPill.Collapse(); this.ResizeHeightTo(0, duration, Easing.OutQuint); diff --git a/osu.Game/Overlays/Rankings/CountryPill.cs b/osu.Game/Overlays/Rankings/CountryPill.cs index 90f8c85557..96d677611e 100644 --- a/osu.Game/Overlays/Rankings/CountryPill.cs +++ b/osu.Game/Overlays/Rankings/CountryPill.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -93,7 +94,7 @@ namespace osu.Game.Overlays.Rankings { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Action = () => Current.Value = null + Action = Current.SetDefault, } } } @@ -132,11 +133,11 @@ namespace osu.Game.Overlays.Rankings private void onCountryChanged(ValueChangedEvent country) { - if (country.NewValue == null) + if (country.NewValue == default) return; flag.Country = country.NewValue; - countryName.Text = country.NewValue.FullName; + countryName.Text = country.NewValue.GetDescription(); } private class CloseButton : OsuHoverContainer diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 53d10b3e53..27d4a6b6d3 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Game.Resources.Localisation.Web; @@ -77,8 +78,8 @@ namespace osu.Game.Overlays.Rankings.Tables RelativeSizeAxes = Axes.Y; TextAnchor = Anchor.CentreLeft; - if (!string.IsNullOrEmpty(country.FullName)) - AddLink(country.FullName, () => rankings?.ShowCountry(country)); + if (country != default) + AddLink(country.GetDescription(), () => rankings?.ShowCountry(country)); } } } diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index a66b8cf2ba..9894df1c9d 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays Country.BindValueChanged(_ => { // if a country is requested, force performance scope. - if (Country.Value != null) + if (!Country.IsDefault) Header.Current.Value = RankingsScope.Performance; Scheduler.AddOnce(triggerTabChanged); @@ -76,7 +76,7 @@ namespace osu.Game.Overlays { // country filtering is only valid for performance scope. if (Header.Current.Value != RankingsScope.Performance) - Country.Value = null; + Country.SetDefault(); Scheduler.AddOnce(triggerTabChanged); } @@ -87,7 +87,7 @@ namespace osu.Game.Overlays public void ShowCountry(Country requested) { - if (requested == null) + if (requested == default) return; Show(); @@ -128,7 +128,7 @@ namespace osu.Game.Overlays switch (Header.Current.Value) { case RankingsScope.Performance: - return new GetUserRankingsRequest(ruleset.Value, country: Country.Value?.FlagName); + return new GetUserRankingsRequest(ruleset.Value, country: Country.Value); case RankingsScope.Country: return new GetCountryRankingsRequest(ruleset.Value); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index f632951f41..652a832689 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -128,7 +128,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Size = new Vector2(28, 20), - Country = user?.Country + Country = user?.Country ?? default }, new OsuSpriteText { diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index e5ac8fa257..84b56a8d16 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; @@ -16,7 +17,7 @@ namespace osu.Game.Users.Drawables { private readonly Country country; - public LocalisableString TooltipText => country?.FullName; + public LocalisableString TooltipText => country == default ? string.Empty : country.GetDescription(); public DrawableFlag(Country country) { @@ -29,7 +30,8 @@ namespace osu.Game.Users.Drawables if (ts == null) throw new ArgumentNullException(nameof(ts)); - Texture = ts.Get($@"Flags/{country?.FlagName ?? @"__"}") ?? ts.Get(@"Flags/__"); + string textureName = country == default ? "__" : country.ToString(); + Texture = ts.Get($@"Flags/{textureName}") ?? ts.Get(@"Flags/__"); } } } diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index fd59bf305d..2f08f1c787 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -32,14 +32,14 @@ namespace osu.Game.Users.Drawables /// public Action Action; - public UpdateableFlag(Country country = null) + public UpdateableFlag(Country country = default) { Country = country; } protected override Drawable CreateDrawable(Country country) { - if (country == null && !ShowPlaceholderOnNull) + if (country == default && !ShowPlaceholderOnNull) return null; return new Container From 08f1280aa8df25140ef4b189fe39b146b94a3e64 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 04:42:33 +0300 Subject: [PATCH 594/803] Add `UsedImplicitly` specification to silence unused member inspection Also applied to `Language` while at it. --- osu.Game/Localisation/Language.cs | 2 ++ osu.Game/Users/Country.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs index c13a1a10cb..6a4e5110e6 100644 --- a/osu.Game/Localisation/Language.cs +++ b/osu.Game/Localisation/Language.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; +using JetBrains.Annotations; namespace osu.Game.Localisation { + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] public enum Language { [Description(@"English")] diff --git a/osu.Game/Users/Country.cs b/osu.Game/Users/Country.cs index d1f33627c1..e96b96e57c 100644 --- a/osu.Game/Users/Country.cs +++ b/osu.Game/Users/Country.cs @@ -9,6 +9,7 @@ using Newtonsoft.Json.Converters; namespace osu.Game.Users { [JsonConverter(typeof(StringEnumConverter))] + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] public enum Country { [Description("Alien")] From 7c6f4b798bc66d5936e689ab6d7b4d26005db91a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 05:16:59 +0300 Subject: [PATCH 595/803] Replace `countries.json` with country enum and acronym extension --- osu.Game.Tournament/CountryExtensions.cs | 770 ++++++++++ osu.Game.Tournament/Resources/countries.json | 1252 ----------------- .../Screens/Editors/TeamEditorScreen.cs | 18 +- 3 files changed, 782 insertions(+), 1258 deletions(-) create mode 100644 osu.Game.Tournament/CountryExtensions.cs delete mode 100644 osu.Game.Tournament/Resources/countries.json diff --git a/osu.Game.Tournament/CountryExtensions.cs b/osu.Game.Tournament/CountryExtensions.cs new file mode 100644 index 0000000000..180c7a96af --- /dev/null +++ b/osu.Game.Tournament/CountryExtensions.cs @@ -0,0 +1,770 @@ +// 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.Users; + +namespace osu.Game.Tournament +{ + public static class CountryExtensions + { + public static string GetAcronym(this Country country) + { + switch (country) + { + case Country.BD: + return "BGD"; + + case Country.BE: + return "BEL"; + + case Country.BF: + return "BFA"; + + case Country.BG: + return "BGR"; + + case Country.BA: + return "BIH"; + + case Country.BB: + return "BRB"; + + case Country.WF: + return "WLF"; + + case Country.BL: + return "BLM"; + + case Country.BM: + return "BMU"; + + case Country.BN: + return "BRN"; + + case Country.BO: + return "BOL"; + + case Country.BH: + return "BHR"; + + case Country.BI: + return "BDI"; + + case Country.BJ: + return "BEN"; + + case Country.BT: + return "BTN"; + + case Country.JM: + return "JAM"; + + case Country.BV: + return "BVT"; + + case Country.BW: + return "BWA"; + + case Country.WS: + return "WSM"; + + case Country.BQ: + return "BES"; + + case Country.BR: + return "BRA"; + + case Country.BS: + return "BHS"; + + case Country.JE: + return "JEY"; + + case Country.BY: + return "BLR"; + + case Country.BZ: + return "BLZ"; + + case Country.RU: + return "RUS"; + + case Country.RW: + return "RWA"; + + case Country.RS: + return "SRB"; + + case Country.TL: + return "TLS"; + + case Country.RE: + return "REU"; + + case Country.TM: + return "TKM"; + + case Country.TJ: + return "TJK"; + + case Country.RO: + return "ROU"; + + case Country.TK: + return "TKL"; + + case Country.GW: + return "GNB"; + + case Country.GU: + return "GUM"; + + case Country.GT: + return "GTM"; + + case Country.GS: + return "SGS"; + + case Country.GR: + return "GRC"; + + case Country.GQ: + return "GNQ"; + + case Country.GP: + return "GLP"; + + case Country.JP: + return "JPN"; + + case Country.GY: + return "GUY"; + + case Country.GG: + return "GGY"; + + case Country.GF: + return "GUF"; + + case Country.GE: + return "GEO"; + + case Country.GD: + return "GRD"; + + case Country.GB: + return "GBR"; + + case Country.GA: + return "GAB"; + + case Country.SV: + return "SLV"; + + case Country.GN: + return "GIN"; + + case Country.GM: + return "GMB"; + + case Country.GL: + return "GRL"; + + case Country.GI: + return "GIB"; + + case Country.GH: + return "GHA"; + + case Country.OM: + return "OMN"; + + case Country.TN: + return "TUN"; + + case Country.JO: + return "JOR"; + + case Country.HR: + return "HRV"; + + case Country.HT: + return "HTI"; + + case Country.HU: + return "HUN"; + + case Country.HK: + return "HKG"; + + case Country.HN: + return "HND"; + + case Country.HM: + return "HMD"; + + case Country.VE: + return "VEN"; + + case Country.PR: + return "PRI"; + + case Country.PS: + return "PSE"; + + case Country.PW: + return "PLW"; + + case Country.PT: + return "PRT"; + + case Country.SJ: + return "SJM"; + + case Country.PY: + return "PRY"; + + case Country.IQ: + return "IRQ"; + + case Country.PA: + return "PAN"; + + case Country.PF: + return "PYF"; + + case Country.PG: + return "PNG"; + + case Country.PE: + return "PER"; + + case Country.PK: + return "PAK"; + + case Country.PH: + return "PHL"; + + case Country.PN: + return "PCN"; + + case Country.PL: + return "POL"; + + case Country.PM: + return "SPM"; + + case Country.ZM: + return "ZMB"; + + case Country.EH: + return "ESH"; + + case Country.EE: + return "EST"; + + case Country.EG: + return "EGY"; + + case Country.ZA: + return "ZAF"; + + case Country.EC: + return "ECU"; + + case Country.IT: + return "ITA"; + + case Country.VN: + return "VNM"; + + case Country.SB: + return "SLB"; + + case Country.ET: + return "ETH"; + + case Country.SO: + return "SOM"; + + case Country.ZW: + return "ZWE"; + + case Country.SA: + return "SAU"; + + case Country.ES: + return "ESP"; + + case Country.ER: + return "ERI"; + + case Country.ME: + return "MNE"; + + case Country.MD: + return "MDA"; + + case Country.MG: + return "MDG"; + + case Country.MF: + return "MAF"; + + case Country.MA: + return "MAR"; + + case Country.MC: + return "MCO"; + + case Country.UZ: + return "UZB"; + + case Country.MM: + return "MMR"; + + case Country.ML: + return "MLI"; + + case Country.MO: + return "MAC"; + + case Country.MN: + return "MNG"; + + case Country.MH: + return "MHL"; + + case Country.MK: + return "MKD"; + + case Country.MU: + return "MUS"; + + case Country.MT: + return "MLT"; + + case Country.MW: + return "MWI"; + + case Country.MV: + return "MDV"; + + case Country.MQ: + return "MTQ"; + + case Country.MP: + return "MNP"; + + case Country.MS: + return "MSR"; + + case Country.MR: + return "MRT"; + + case Country.IM: + return "IMN"; + + case Country.UG: + return "UGA"; + + case Country.TZ: + return "TZA"; + + case Country.MY: + return "MYS"; + + case Country.MX: + return "MEX"; + + case Country.IL: + return "ISR"; + + case Country.FR: + return "FRA"; + + case Country.IO: + return "IOT"; + + case Country.SH: + return "SHN"; + + case Country.FI: + return "FIN"; + + case Country.FJ: + return "FJI"; + + case Country.FK: + return "FLK"; + + case Country.FM: + return "FSM"; + + case Country.FO: + return "FRO"; + + case Country.NI: + return "NIC"; + + case Country.NL: + return "NLD"; + + case Country.NO: + return "NOR"; + + case Country.NA: + return "NAM"; + + case Country.VU: + return "VUT"; + + case Country.NC: + return "NCL"; + + case Country.NE: + return "NER"; + + case Country.NF: + return "NFK"; + + case Country.NG: + return "NGA"; + + case Country.NZ: + return "NZL"; + + case Country.NP: + return "NPL"; + + case Country.NR: + return "NRU"; + + case Country.NU: + return "NIU"; + + case Country.CK: + return "COK"; + + case Country.XK: + return "XKX"; + + case Country.CI: + return "CIV"; + + case Country.CH: + return "CHE"; + + case Country.CO: + return "COL"; + + case Country.CN: + return "CHN"; + + case Country.CM: + return "CMR"; + + case Country.CL: + return "CHL"; + + case Country.CC: + return "CCK"; + + case Country.CA: + return "CAN"; + + case Country.CG: + return "COG"; + + case Country.CF: + return "CAF"; + + case Country.CD: + return "COD"; + + case Country.CZ: + return "CZE"; + + case Country.CY: + return "CYP"; + + case Country.CX: + return "CXR"; + + case Country.CR: + return "CRI"; + + case Country.CW: + return "CUW"; + + case Country.CV: + return "CPV"; + + case Country.CU: + return "CUB"; + + case Country.SZ: + return "SWZ"; + + case Country.SY: + return "SYR"; + + case Country.SX: + return "SXM"; + + case Country.KG: + return "KGZ"; + + case Country.KE: + return "KEN"; + + case Country.SS: + return "SSD"; + + case Country.SR: + return "SUR"; + + case Country.KI: + return "KIR"; + + case Country.KH: + return "KHM"; + + case Country.KN: + return "KNA"; + + case Country.KM: + return "COM"; + + case Country.ST: + return "STP"; + + case Country.SK: + return "SVK"; + + case Country.KR: + return "KOR"; + + case Country.SI: + return "SVN"; + + case Country.KP: + return "PRK"; + + case Country.KW: + return "KWT"; + + case Country.SN: + return "SEN"; + + case Country.SM: + return "SMR"; + + case Country.SL: + return "SLE"; + + case Country.SC: + return "SYC"; + + case Country.KZ: + return "KAZ"; + + case Country.KY: + return "CYM"; + + case Country.SG: + return "SGP"; + + case Country.SE: + return "SWE"; + + case Country.SD: + return "SDN"; + + case Country.DO: + return "DOM"; + + case Country.DM: + return "DMA"; + + case Country.DJ: + return "DJI"; + + case Country.DK: + return "DNK"; + + case Country.VG: + return "VGB"; + + case Country.DE: + return "DEU"; + + case Country.YE: + return "YEM"; + + case Country.DZ: + return "DZA"; + + case Country.US: + return "USA"; + + case Country.UY: + return "URY"; + + case Country.YT: + return "MYT"; + + case Country.UM: + return "UMI"; + + case Country.LB: + return "LBN"; + + case Country.LC: + return "LCA"; + + case Country.LA: + return "LAO"; + + case Country.TV: + return "TUV"; + + case Country.TW: + return "TWN"; + + case Country.TT: + return "TTO"; + + case Country.TR: + return "TUR"; + + case Country.LK: + return "LKA"; + + case Country.LI: + return "LIE"; + + case Country.LV: + return "LVA"; + + case Country.TO: + return "TON"; + + case Country.LT: + return "LTU"; + + case Country.LU: + return "LUX"; + + case Country.LR: + return "LBR"; + + case Country.LS: + return "LSO"; + + case Country.TH: + return "THA"; + + case Country.TF: + return "ATF"; + + case Country.TG: + return "TGO"; + + case Country.TD: + return "TCD"; + + case Country.TC: + return "TCA"; + + case Country.LY: + return "LBY"; + + case Country.VA: + return "VAT"; + + case Country.VC: + return "VCT"; + + case Country.AE: + return "ARE"; + + case Country.AD: + return "AND"; + + case Country.AG: + return "ATG"; + + case Country.AF: + return "AFG"; + + case Country.AI: + return "AIA"; + + case Country.VI: + return "VIR"; + + case Country.IS: + return "ISL"; + + case Country.IR: + return "IRN"; + + case Country.AM: + return "ARM"; + + case Country.AL: + return "ALB"; + + case Country.AO: + return "AGO"; + + case Country.AQ: + return "ATA"; + + case Country.AS: + return "ASM"; + + case Country.AR: + return "ARG"; + + case Country.AU: + return "AUS"; + + case Country.AT: + return "AUT"; + + case Country.AW: + return "ABW"; + + case Country.IN: + return "IND"; + + case Country.AX: + return "ALA"; + + case Country.AZ: + return "AZE"; + + case Country.IE: + return "IRL"; + + case Country.ID: + return "IDN"; + + case Country.UA: + return "UKR"; + + case Country.QA: + return "QAT"; + + case Country.MZ: + return "MOZ"; + + default: + throw new ArgumentOutOfRangeException(nameof(country)); + } + } + } +} diff --git a/osu.Game.Tournament/Resources/countries.json b/osu.Game.Tournament/Resources/countries.json deleted file mode 100644 index 7306a8bec5..0000000000 --- a/osu.Game.Tournament/Resources/countries.json +++ /dev/null @@ -1,1252 +0,0 @@ -[ - { - "FlagName": "BD", - "FullName": "Bangladesh", - "Acronym": "BGD" - }, - { - "FlagName": "BE", - "FullName": "Belgium", - "Acronym": "BEL" - }, - { - "FlagName": "BF", - "FullName": "Burkina Faso", - "Acronym": "BFA" - }, - { - "FlagName": "BG", - "FullName": "Bulgaria", - "Acronym": "BGR" - }, - { - "FlagName": "BA", - "FullName": "Bosnia and Herzegovina", - "Acronym": "BIH" - }, - { - "FlagName": "BB", - "FullName": "Barbados", - "Acronym": "BRB" - }, - { - "FlagName": "WF", - "FullName": "Wallis and Futuna", - "Acronym": "WLF" - }, - { - "FlagName": "BL", - "FullName": "Saint Barthelemy", - "Acronym": "BLM" - }, - { - "FlagName": "BM", - "FullName": "Bermuda", - "Acronym": "BMU" - }, - { - "FlagName": "BN", - "FullName": "Brunei", - "Acronym": "BRN" - }, - { - "FlagName": "BO", - "FullName": "Bolivia", - "Acronym": "BOL" - }, - { - "FlagName": "BH", - "FullName": "Bahrain", - "Acronym": "BHR" - }, - { - "FlagName": "BI", - "FullName": "Burundi", - "Acronym": "BDI" - }, - { - "FlagName": "BJ", - "FullName": "Benin", - "Acronym": "BEN" - }, - { - "FlagName": "BT", - "FullName": "Bhutan", - "Acronym": "BTN" - }, - { - "FlagName": "JM", - "FullName": "Jamaica", - "Acronym": "JAM" - }, - { - "FlagName": "BV", - "FullName": "Bouvet Island", - "Acronym": "BVT" - }, - { - "FlagName": "BW", - "FullName": "Botswana", - "Acronym": "BWA" - }, - { - "FlagName": "WS", - "FullName": "Samoa", - "Acronym": "WSM" - }, - { - "FlagName": "BQ", - "FullName": "Bonaire, Saint Eustatius and Saba", - "Acronym": "BES" - }, - { - "FlagName": "BR", - "FullName": "Brazil", - "Acronym": "BRA" - }, - { - "FlagName": "BS", - "FullName": "Bahamas", - "Acronym": "BHS" - }, - { - "FlagName": "JE", - "FullName": "Jersey", - "Acronym": "JEY" - }, - { - "FlagName": "BY", - "FullName": "Belarus", - "Acronym": "BLR" - }, - { - "FlagName": "BZ", - "FullName": "Belize", - "Acronym": "BLZ" - }, - { - "FlagName": "RU", - "FullName": "Russia", - "Acronym": "RUS" - }, - { - "FlagName": "RW", - "FullName": "Rwanda", - "Acronym": "RWA" - }, - { - "FlagName": "RS", - "FullName": "Serbia", - "Acronym": "SRB" - }, - { - "FlagName": "TL", - "FullName": "East Timor", - "Acronym": "TLS" - }, - { - "FlagName": "RE", - "FullName": "Reunion", - "Acronym": "REU" - }, - { - "FlagName": "TM", - "FullName": "Turkmenistan", - "Acronym": "TKM" - }, - { - "FlagName": "TJ", - "FullName": "Tajikistan", - "Acronym": "TJK" - }, - { - "FlagName": "RO", - "FullName": "Romania", - "Acronym": "ROU" - }, - { - "FlagName": "TK", - "FullName": "Tokelau", - "Acronym": "TKL" - }, - { - "FlagName": "GW", - "FullName": "Guinea-Bissau", - "Acronym": "GNB" - }, - { - "FlagName": "GU", - "FullName": "Guam", - "Acronym": "GUM" - }, - { - "FlagName": "GT", - "FullName": "Guatemala", - "Acronym": "GTM" - }, - { - "FlagName": "GS", - "FullName": "South Georgia and the South Sandwich Islands", - "Acronym": "SGS" - }, - { - "FlagName": "GR", - "FullName": "Greece", - "Acronym": "GRC" - }, - { - "FlagName": "GQ", - "FullName": "Equatorial Guinea", - "Acronym": "GNQ" - }, - { - "FlagName": "GP", - "FullName": "Guadeloupe", - "Acronym": "GLP" - }, - { - "FlagName": "JP", - "FullName": "Japan", - "Acronym": "JPN" - }, - { - "FlagName": "GY", - "FullName": "Guyana", - "Acronym": "GUY" - }, - { - "FlagName": "GG", - "FullName": "Guernsey", - "Acronym": "GGY" - }, - { - "FlagName": "GF", - "FullName": "French Guiana", - "Acronym": "GUF" - }, - { - "FlagName": "GE", - "FullName": "Georgia", - "Acronym": "GEO" - }, - { - "FlagName": "GD", - "FullName": "Grenada", - "Acronym": "GRD" - }, - { - "FlagName": "GB", - "FullName": "United Kingdom", - "Acronym": "GBR" - }, - { - "FlagName": "GA", - "FullName": "Gabon", - "Acronym": "GAB" - }, - { - "FlagName": "SV", - "FullName": "El Salvador", - "Acronym": "SLV" - }, - { - "FlagName": "GN", - "FullName": "Guinea", - "Acronym": "GIN" - }, - { - "FlagName": "GM", - "FullName": "Gambia", - "Acronym": "GMB" - }, - { - "FlagName": "GL", - "FullName": "Greenland", - "Acronym": "GRL" - }, - { - "FlagName": "GI", - "FullName": "Gibraltar", - "Acronym": "GIB" - }, - { - "FlagName": "GH", - "FullName": "Ghana", - "Acronym": "GHA" - }, - { - "FlagName": "OM", - "FullName": "Oman", - "Acronym": "OMN" - }, - { - "FlagName": "TN", - "FullName": "Tunisia", - "Acronym": "TUN" - }, - { - "FlagName": "JO", - "FullName": "Jordan", - "Acronym": "JOR" - }, - { - "FlagName": "HR", - "FullName": "Croatia", - "Acronym": "HRV" - }, - { - "FlagName": "HT", - "FullName": "Haiti", - "Acronym": "HTI" - }, - { - "FlagName": "HU", - "FullName": "Hungary", - "Acronym": "HUN" - }, - { - "FlagName": "HK", - "FullName": "Hong Kong", - "Acronym": "HKG" - }, - { - "FlagName": "HN", - "FullName": "Honduras", - "Acronym": "HND" - }, - { - "FlagName": "HM", - "FullName": "Heard Island and McDonald Islands", - "Acronym": "HMD" - }, - { - "FlagName": "VE", - "FullName": "Venezuela", - "Acronym": "VEN" - }, - { - "FlagName": "PR", - "FullName": "Puerto Rico", - "Acronym": "PRI" - }, - { - "FlagName": "PS", - "FullName": "Palestinian Territory", - "Acronym": "PSE" - }, - { - "FlagName": "PW", - "FullName": "Palau", - "Acronym": "PLW" - }, - { - "FlagName": "PT", - "FullName": "Portugal", - "Acronym": "PRT" - }, - { - "FlagName": "SJ", - "FullName": "Svalbard and Jan Mayen", - "Acronym": "SJM" - }, - { - "FlagName": "PY", - "FullName": "Paraguay", - "Acronym": "PRY" - }, - { - "FlagName": "IQ", - "FullName": "Iraq", - "Acronym": "IRQ" - }, - { - "FlagName": "PA", - "FullName": "Panama", - "Acronym": "PAN" - }, - { - "FlagName": "PF", - "FullName": "French Polynesia", - "Acronym": "PYF" - }, - { - "FlagName": "PG", - "FullName": "Papua New Guinea", - "Acronym": "PNG" - }, - { - "FlagName": "PE", - "FullName": "Peru", - "Acronym": "PER" - }, - { - "FlagName": "PK", - "FullName": "Pakistan", - "Acronym": "PAK" - }, - { - "FlagName": "PH", - "FullName": "Philippines", - "Acronym": "PHL" - }, - { - "FlagName": "PN", - "FullName": "Pitcairn", - "Acronym": "PCN" - }, - { - "FlagName": "PL", - "FullName": "Poland", - "Acronym": "POL" - }, - { - "FlagName": "PM", - "FullName": "Saint Pierre and Miquelon", - "Acronym": "SPM" - }, - { - "FlagName": "ZM", - "FullName": "Zambia", - "Acronym": "ZMB" - }, - { - "FlagName": "EH", - "FullName": "Western Sahara", - "Acronym": "ESH" - }, - { - "FlagName": "EE", - "FullName": "Estonia", - "Acronym": "EST" - }, - { - "FlagName": "EG", - "FullName": "Egypt", - "Acronym": "EGY" - }, - { - "FlagName": "ZA", - "FullName": "South Africa", - "Acronym": "ZAF" - }, - { - "FlagName": "EC", - "FullName": "Ecuador", - "Acronym": "ECU" - }, - { - "FlagName": "IT", - "FullName": "Italy", - "Acronym": "ITA" - }, - { - "FlagName": "VN", - "FullName": "Vietnam", - "Acronym": "VNM" - }, - { - "FlagName": "SB", - "FullName": "Solomon Islands", - "Acronym": "SLB" - }, - { - "FlagName": "ET", - "FullName": "Ethiopia", - "Acronym": "ETH" - }, - { - "FlagName": "SO", - "FullName": "Somalia", - "Acronym": "SOM" - }, - { - "FlagName": "ZW", - "FullName": "Zimbabwe", - "Acronym": "ZWE" - }, - { - "FlagName": "SA", - "FullName": "Saudi Arabia", - "Acronym": "SAU" - }, - { - "FlagName": "ES", - "FullName": "Spain", - "Acronym": "ESP" - }, - { - "FlagName": "ER", - "FullName": "Eritrea", - "Acronym": "ERI" - }, - { - "FlagName": "ME", - "FullName": "Montenegro", - "Acronym": "MNE" - }, - { - "FlagName": "MD", - "FullName": "Moldova", - "Acronym": "MDA" - }, - { - "FlagName": "MG", - "FullName": "Madagascar", - "Acronym": "MDG" - }, - { - "FlagName": "MF", - "FullName": "Saint Martin", - "Acronym": "MAF" - }, - { - "FlagName": "MA", - "FullName": "Morocco", - "Acronym": "MAR" - }, - { - "FlagName": "MC", - "FullName": "Monaco", - "Acronym": "MCO" - }, - { - "FlagName": "UZ", - "FullName": "Uzbekistan", - "Acronym": "UZB" - }, - { - "FlagName": "MM", - "FullName": "Myanmar", - "Acronym": "MMR" - }, - { - "FlagName": "ML", - "FullName": "Mali", - "Acronym": "MLI" - }, - { - "FlagName": "MO", - "FullName": "Macao", - "Acronym": "MAC" - }, - { - "FlagName": "MN", - "FullName": "Mongolia", - "Acronym": "MNG" - }, - { - "FlagName": "MH", - "FullName": "Marshall Islands", - "Acronym": "MHL" - }, - { - "FlagName": "MK", - "FullName": "North Macedonia", - "Acronym": "MKD" - }, - { - "FlagName": "MU", - "FullName": "Mauritius", - "Acronym": "MUS" - }, - { - "FlagName": "MT", - "FullName": "Malta", - "Acronym": "MLT" - }, - { - "FlagName": "MW", - "FullName": "Malawi", - "Acronym": "MWI" - }, - { - "FlagName": "MV", - "FullName": "Maldives", - "Acronym": "MDV" - }, - { - "FlagName": "MQ", - "FullName": "Martinique", - "Acronym": "MTQ" - }, - { - "FlagName": "MP", - "FullName": "Northern Mariana Islands", - "Acronym": "MNP" - }, - { - "FlagName": "MS", - "FullName": "Montserrat", - "Acronym": "MSR" - }, - { - "FlagName": "MR", - "FullName": "Mauritania", - "Acronym": "MRT" - }, - { - "FlagName": "IM", - "FullName": "Isle of Man", - "Acronym": "IMN" - }, - { - "FlagName": "UG", - "FullName": "Uganda", - "Acronym": "UGA" - }, - { - "FlagName": "TZ", - "FullName": "Tanzania", - "Acronym": "TZA" - }, - { - "FlagName": "MY", - "FullName": "Malaysia", - "Acronym": "MYS" - }, - { - "FlagName": "MX", - "FullName": "Mexico", - "Acronym": "MEX" - }, - { - "FlagName": "IL", - "FullName": "Israel", - "Acronym": "ISR" - }, - { - "FlagName": "FR", - "FullName": "France", - "Acronym": "FRA" - }, - { - "FlagName": "IO", - "FullName": "British Indian Ocean Territory", - "Acronym": "IOT" - }, - { - "FlagName": "SH", - "FullName": "Saint Helena", - "Acronym": "SHN" - }, - { - "FlagName": "FI", - "FullName": "Finland", - "Acronym": "FIN" - }, - { - "FlagName": "FJ", - "FullName": "Fiji", - "Acronym": "FJI" - }, - { - "FlagName": "FK", - "FullName": "Falkland Islands", - "Acronym": "FLK" - }, - { - "FlagName": "FM", - "FullName": "Micronesia", - "Acronym": "FSM" - }, - { - "FlagName": "FO", - "FullName": "Faroe Islands", - "Acronym": "FRO" - }, - { - "FlagName": "NI", - "FullName": "Nicaragua", - "Acronym": "NIC" - }, - { - "FlagName": "NL", - "FullName": "Netherlands", - "Acronym": "NLD" - }, - { - "FlagName": "NO", - "FullName": "Norway", - "Acronym": "NOR" - }, - { - "FlagName": "NA", - "FullName": "Namibia", - "Acronym": "NAM" - }, - { - "FlagName": "VU", - "FullName": "Vanuatu", - "Acronym": "VUT" - }, - { - "FlagName": "NC", - "FullName": "New Caledonia", - "Acronym": "NCL" - }, - { - "FlagName": "NE", - "FullName": "Niger", - "Acronym": "NER" - }, - { - "FlagName": "NF", - "FullName": "Norfolk Island", - "Acronym": "NFK" - }, - { - "FlagName": "NG", - "FullName": "Nigeria", - "Acronym": "NGA" - }, - { - "FlagName": "NZ", - "FullName": "New Zealand", - "Acronym": "NZL" - }, - { - "FlagName": "NP", - "FullName": "Nepal", - "Acronym": "NPL" - }, - { - "FlagName": "NR", - "FullName": "Nauru", - "Acronym": "NRU" - }, - { - "FlagName": "NU", - "FullName": "Niue", - "Acronym": "NIU" - }, - { - "FlagName": "CK", - "FullName": "Cook Islands", - "Acronym": "COK" - }, - { - "FlagName": "XK", - "FullName": "Kosovo", - "Acronym": "XKX" - }, - { - "FlagName": "CI", - "FullName": "Ivory Coast", - "Acronym": "CIV" - }, - { - "FlagName": "CH", - "FullName": "Switzerland", - "Acronym": "CHE" - }, - { - "FlagName": "CO", - "FullName": "Colombia", - "Acronym": "COL" - }, - { - "FlagName": "CN", - "FullName": "China", - "Acronym": "CHN" - }, - { - "FlagName": "CM", - "FullName": "Cameroon", - "Acronym": "CMR" - }, - { - "FlagName": "CL", - "FullName": "Chile", - "Acronym": "CHL" - }, - { - "FlagName": "CC", - "FullName": "Cocos Islands", - "Acronym": "CCK" - }, - { - "FlagName": "CA", - "FullName": "Canada", - "Acronym": "CAN" - }, - { - "FlagName": "CG", - "FullName": "Republic of the Congo", - "Acronym": "COG" - }, - { - "FlagName": "CF", - "FullName": "Central African Republic", - "Acronym": "CAF" - }, - { - "FlagName": "CD", - "FullName": "Democratic Republic of the Congo", - "Acronym": "COD" - }, - { - "FlagName": "CZ", - "FullName": "Czech Republic", - "Acronym": "CZE" - }, - { - "FlagName": "CY", - "FullName": "Cyprus", - "Acronym": "CYP" - }, - { - "FlagName": "CX", - "FullName": "Christmas Island", - "Acronym": "CXR" - }, - { - "FlagName": "CR", - "FullName": "Costa Rica", - "Acronym": "CRI" - }, - { - "FlagName": "CW", - "FullName": "Curacao", - "Acronym": "CUW" - }, - { - "FlagName": "CV", - "FullName": "Cabo Verde", - "Acronym": "CPV" - }, - { - "FlagName": "CU", - "FullName": "Cuba", - "Acronym": "CUB" - }, - { - "FlagName": "SZ", - "FullName": "Eswatini", - "Acronym": "SWZ" - }, - { - "FlagName": "SY", - "FullName": "Syria", - "Acronym": "SYR" - }, - { - "FlagName": "SX", - "FullName": "Sint Maarten", - "Acronym": "SXM" - }, - { - "FlagName": "KG", - "FullName": "Kyrgyzstan", - "Acronym": "KGZ" - }, - { - "FlagName": "KE", - "FullName": "Kenya", - "Acronym": "KEN" - }, - { - "FlagName": "SS", - "FullName": "South Sudan", - "Acronym": "SSD" - }, - { - "FlagName": "SR", - "FullName": "Suriname", - "Acronym": "SUR" - }, - { - "FlagName": "KI", - "FullName": "Kiribati", - "Acronym": "KIR" - }, - { - "FlagName": "KH", - "FullName": "Cambodia", - "Acronym": "KHM" - }, - { - "FlagName": "KN", - "FullName": "Saint Kitts and Nevis", - "Acronym": "KNA" - }, - { - "FlagName": "KM", - "FullName": "Comoros", - "Acronym": "COM" - }, - { - "FlagName": "ST", - "FullName": "Sao Tome and Principe", - "Acronym": "STP" - }, - { - "FlagName": "SK", - "FullName": "Slovakia", - "Acronym": "SVK" - }, - { - "FlagName": "KR", - "FullName": "South Korea", - "Acronym": "KOR" - }, - { - "FlagName": "SI", - "FullName": "Slovenia", - "Acronym": "SVN" - }, - { - "FlagName": "KP", - "FullName": "North Korea", - "Acronym": "PRK" - }, - { - "FlagName": "KW", - "FullName": "Kuwait", - "Acronym": "KWT" - }, - { - "FlagName": "SN", - "FullName": "Senegal", - "Acronym": "SEN" - }, - { - "FlagName": "SM", - "FullName": "San Marino", - "Acronym": "SMR" - }, - { - "FlagName": "SL", - "FullName": "Sierra Leone", - "Acronym": "SLE" - }, - { - "FlagName": "SC", - "FullName": "Seychelles", - "Acronym": "SYC" - }, - { - "FlagName": "KZ", - "FullName": "Kazakhstan", - "Acronym": "KAZ" - }, - { - "FlagName": "KY", - "FullName": "Cayman Islands", - "Acronym": "CYM" - }, - { - "FlagName": "SG", - "FullName": "Singapore", - "Acronym": "SGP" - }, - { - "FlagName": "SE", - "FullName": "Sweden", - "Acronym": "SWE" - }, - { - "FlagName": "SD", - "FullName": "Sudan", - "Acronym": "SDN" - }, - { - "FlagName": "DO", - "FullName": "Dominican Republic", - "Acronym": "DOM" - }, - { - "FlagName": "DM", - "FullName": "Dominica", - "Acronym": "DMA" - }, - { - "FlagName": "DJ", - "FullName": "Djibouti", - "Acronym": "DJI" - }, - { - "FlagName": "DK", - "FullName": "Denmark", - "Acronym": "DNK" - }, - { - "FlagName": "VG", - "FullName": "British Virgin Islands", - "Acronym": "VGB" - }, - { - "FlagName": "DE", - "FullName": "Germany", - "Acronym": "DEU" - }, - { - "FlagName": "YE", - "FullName": "Yemen", - "Acronym": "YEM" - }, - { - "FlagName": "DZ", - "FullName": "Algeria", - "Acronym": "DZA" - }, - { - "FlagName": "US", - "FullName": "United States", - "Acronym": "USA" - }, - { - "FlagName": "UY", - "FullName": "Uruguay", - "Acronym": "URY" - }, - { - "FlagName": "YT", - "FullName": "Mayotte", - "Acronym": "MYT" - }, - { - "FlagName": "UM", - "FullName": "United States Minor Outlying Islands", - "Acronym": "UMI" - }, - { - "FlagName": "LB", - "FullName": "Lebanon", - "Acronym": "LBN" - }, - { - "FlagName": "LC", - "FullName": "Saint Lucia", - "Acronym": "LCA" - }, - { - "FlagName": "LA", - "FullName": "Laos", - "Acronym": "LAO" - }, - { - "FlagName": "TV", - "FullName": "Tuvalu", - "Acronym": "TUV" - }, - { - "FlagName": "TW", - "FullName": "Taiwan", - "Acronym": "TWN" - }, - { - "FlagName": "TT", - "FullName": "Trinidad and Tobago", - "Acronym": "TTO" - }, - { - "FlagName": "TR", - "FullName": "Turkey", - "Acronym": "TUR" - }, - { - "FlagName": "LK", - "FullName": "Sri Lanka", - "Acronym": "LKA" - }, - { - "FlagName": "LI", - "FullName": "Liechtenstein", - "Acronym": "LIE" - }, - { - "FlagName": "LV", - "FullName": "Latvia", - "Acronym": "LVA" - }, - { - "FlagName": "TO", - "FullName": "Tonga", - "Acronym": "TON" - }, - { - "FlagName": "LT", - "FullName": "Lithuania", - "Acronym": "LTU" - }, - { - "FlagName": "LU", - "FullName": "Luxembourg", - "Acronym": "LUX" - }, - { - "FlagName": "LR", - "FullName": "Liberia", - "Acronym": "LBR" - }, - { - "FlagName": "LS", - "FullName": "Lesotho", - "Acronym": "LSO" - }, - { - "FlagName": "TH", - "FullName": "Thailand", - "Acronym": "THA" - }, - { - "FlagName": "TF", - "FullName": "French Southern Territories", - "Acronym": "ATF" - }, - { - "FlagName": "TG", - "FullName": "Togo", - "Acronym": "TGO" - }, - { - "FlagName": "TD", - "FullName": "Chad", - "Acronym": "TCD" - }, - { - "FlagName": "TC", - "FullName": "Turks and Caicos Islands", - "Acronym": "TCA" - }, - { - "FlagName": "LY", - "FullName": "Libya", - "Acronym": "LBY" - }, - { - "FlagName": "VA", - "FullName": "Vatican", - "Acronym": "VAT" - }, - { - "FlagName": "VC", - "FullName": "Saint Vincent and the Grenadines", - "Acronym": "VCT" - }, - { - "FlagName": "AE", - "FullName": "United Arab Emirates", - "Acronym": "ARE" - }, - { - "FlagName": "AD", - "FullName": "Andorra", - "Acronym": "AND" - }, - { - "FlagName": "AG", - "FullName": "Antigua and Barbuda", - "Acronym": "ATG" - }, - { - "FlagName": "AF", - "FullName": "Afghanistan", - "Acronym": "AFG" - }, - { - "FlagName": "AI", - "FullName": "Anguilla", - "Acronym": "AIA" - }, - { - "FlagName": "VI", - "FullName": "U.S. Virgin Islands", - "Acronym": "VIR" - }, - { - "FlagName": "IS", - "FullName": "Iceland", - "Acronym": "ISL" - }, - { - "FlagName": "IR", - "FullName": "Iran", - "Acronym": "IRN" - }, - { - "FlagName": "AM", - "FullName": "Armenia", - "Acronym": "ARM" - }, - { - "FlagName": "AL", - "FullName": "Albania", - "Acronym": "ALB" - }, - { - "FlagName": "AO", - "FullName": "Angola", - "Acronym": "AGO" - }, - { - "FlagName": "AQ", - "FullName": "Antarctica", - "Acronym": "ATA" - }, - { - "FlagName": "AS", - "FullName": "American Samoa", - "Acronym": "ASM" - }, - { - "FlagName": "AR", - "FullName": "Argentina", - "Acronym": "ARG" - }, - { - "FlagName": "AU", - "FullName": "Australia", - "Acronym": "AUS" - }, - { - "FlagName": "AT", - "FullName": "Austria", - "Acronym": "AUT" - }, - { - "FlagName": "AW", - "FullName": "Aruba", - "Acronym": "ABW" - }, - { - "FlagName": "IN", - "FullName": "India", - "Acronym": "IND" - }, - { - "FlagName": "AX", - "FullName": "Aland Islands", - "Acronym": "ALA" - }, - { - "FlagName": "AZ", - "FullName": "Azerbaijan", - "Acronym": "AZE" - }, - { - "FlagName": "IE", - "FullName": "Ireland", - "Acronym": "IRL" - }, - { - "FlagName": "ID", - "FullName": "Indonesia", - "Acronym": "IDN" - }, - { - "FlagName": "UA", - "FullName": "Ukraine", - "Acronym": "UKR" - }, - { - "FlagName": "QA", - "FullName": "Qatar", - "Acronym": "QAT" - }, - { - "FlagName": "MZ", - "FullName": "Mozambique", - "Acronym": "MOZ" - } -] \ No newline at end of file diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 111893d18c..02c0a0d7e1 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -3,13 +3,13 @@ #nullable disable +using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; -using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -45,11 +45,17 @@ namespace osu.Game.Tournament.Screens.Editors private void addAllCountries() { - List countries; + var countries = new List(); - using (Stream stream = game.Resources.GetStream("Resources/countries.json")) - using (var sr = new StreamReader(stream)) - countries = JsonConvert.DeserializeObject>(sr.ReadToEnd()); + foreach (var country in Enum.GetValues(typeof(Country)).Cast().Skip(1)) + { + countries.Add(new TournamentTeam + { + FlagName = { Value = country.ToString() }, + FullName = { Value = country.GetDescription() }, + Acronym = { Value = country.GetAcronym() }, + }); + } Debug.Assert(countries != null); From 9c81241f4ce0abf7e77621f8cf048076446d9cc6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 06:02:04 +0300 Subject: [PATCH 596/803] Fix potential nullref on `APIUser.Country` We need NRT sooner than later... --- osu.Game/Online/API/Requests/Responses/APIUser.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 1a2a38e789..e2e1288b8c 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -38,11 +38,12 @@ namespace osu.Game.Online.API.Requests.Responses public Country Country { - get => country ??= (Enum.TryParse(userCountry.Code, out Country result) ? result : default); + get => country ??= (Enum.TryParse(userCountry?.Code, out Country result) ? result : default); set => country = value; } #pragma warning disable 649 + [CanBeNull] [JsonProperty(@"country")] private UserCountry userCountry; From 4968859e694770186cf5e09a88ee2d91195efc44 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 06:07:53 +0300 Subject: [PATCH 597/803] Rename placeholder display flag property to make sense --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs | 2 +- osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs | 2 +- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 2 +- osu.Game/Users/Drawables/UpdateableFlag.cs | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index b2e5be6601..669b701d1d 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -168,7 +168,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new UpdateableFlag(score.User.Country) { Size = new Vector2(19, 14), - ShowPlaceholderOnNull = false, + ShowPlaceholderOnUnknown = false, }, username, new OsuSpriteText diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index f093c6b53f..85c8f92a76 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -120,7 +120,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Origin = Anchor.CentreLeft, Size = new Vector2(19, 14), Margin = new MarginPadding { Top = 3 }, // makes spacing look more even - ShowPlaceholderOnNull = false, + ShowPlaceholderOnUnknown = false, }, } } diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 818a84b9aa..a213f99701 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -137,7 +137,7 @@ namespace osu.Game.Overlays.Profile.Header userFlag = new UpdateableFlag { Size = new Vector2(28, 20), - ShowPlaceholderOnNull = false, + ShowPlaceholderOnUnknown = false, }, userCountryText = new OsuSpriteText { diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 56e318637a..1e33b96ef1 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -100,7 +100,7 @@ namespace osu.Game.Overlays.Rankings.Tables new UpdateableFlag(GetCountry(item)) { Size = new Vector2(28, 20), - ShowPlaceholderOnNull = false, + ShowPlaceholderOnUnknown = false, }, CreateFlagContent(item) } diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index 2f08f1c787..88c08d4bb1 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -22,9 +22,9 @@ namespace osu.Game.Users.Drawables } /// - /// Whether to show a place holder on null country. + /// Whether to show a place holder on unknown country. /// - public bool ShowPlaceholderOnNull = true; + public bool ShowPlaceholderOnUnknown = true; /// /// Perform an action in addition to showing the country ranking. @@ -39,7 +39,7 @@ namespace osu.Game.Users.Drawables protected override Drawable CreateDrawable(Country country) { - if (country == default && !ShowPlaceholderOnNull) + if (country == default && !ShowPlaceholderOnUnknown) return null; return new Container From 4e7156cee8f66d057cacc7caf812518fcd7b3587 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 06:30:25 +0300 Subject: [PATCH 598/803] Store user country on databased scores --- osu.Game/Database/RealmAccess.cs | 3 ++- osu.Game/Models/RealmUser.cs | 12 ++++++++++-- osu.Game/Rulesets/Mods/ICreateReplayData.cs | 1 + osu.Game/Scoring/ScoreInfo.cs | 7 +++++-- osu.Game/Users/IUser.cs | 2 ++ 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index bebc101b13..0c44436ec8 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -60,8 +60,9 @@ namespace osu.Game.Database /// 14 2022-03-01 Added BeatmapUserSettings to BeatmapInfo. /// 15 2022-07-13 Added LastPlayed to BeatmapInfo. /// 16 2022-07-15 Removed HasReplay from ScoreInfo. + /// 17 2022-07-16 Added Country to RealmUser. /// - private const int schema_version = 16; + private const int schema_version = 17; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Models/RealmUser.cs b/osu.Game/Models/RealmUser.cs index 58fd7ff2a3..1668739bb5 100644 --- a/osu.Game/Models/RealmUser.cs +++ b/osu.Game/Models/RealmUser.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Game.Database; using osu.Game.Users; @@ -17,6 +15,16 @@ namespace osu.Game.Models public string Username { get; set; } = string.Empty; + [Ignored] + public Country Country + { + get => Enum.TryParse(CountryString, out Country country) ? country : default; + set => CountryString = value.ToString(); + } + + [MapTo(nameof(Country))] + public string CountryString { get; set; } = default(Country).ToString(); + public bool IsBot => false; public bool Equals(RealmUser other) diff --git a/osu.Game/Rulesets/Mods/ICreateReplayData.cs b/osu.Game/Rulesets/Mods/ICreateReplayData.cs index 6058380eb3..6c195f623c 100644 --- a/osu.Game/Rulesets/Mods/ICreateReplayData.cs +++ b/osu.Game/Rulesets/Mods/ICreateReplayData.cs @@ -58,6 +58,7 @@ namespace osu.Game.Rulesets.Mods public class ModCreatedUser : IUser { public int OnlineID => APIUser.SYSTEM_USER_ID; + public Country Country => default; public bool IsBot => true; public string Username { get; set; } = string.Empty; diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 81ca5f0300..f5942e4639 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -85,8 +85,9 @@ namespace osu.Game.Scoring { get => user ??= new APIUser { - Username = RealmUser.Username, Id = RealmUser.OnlineID, + Username = RealmUser.Username, + Country = RealmUser.Country, }; set { @@ -95,7 +96,8 @@ namespace osu.Game.Scoring RealmUser = new RealmUser { OnlineID = user.OnlineID, - Username = user.Username + Username = user.Username, + Country = user.Country, }; } } @@ -135,6 +137,7 @@ namespace osu.Game.Scoring { OnlineID = RealmUser.OnlineID, Username = RealmUser.Username, + Country = RealmUser.Country, }; return clone; diff --git a/osu.Game/Users/IUser.cs b/osu.Game/Users/IUser.cs index 7a233b5d8b..a520660c4d 100644 --- a/osu.Game/Users/IUser.cs +++ b/osu.Game/Users/IUser.cs @@ -10,6 +10,8 @@ namespace osu.Game.Users { string Username { get; } + Country Country { get; } + bool IsBot { get; } bool IEquatable.Equals(IUser? other) From d0fe4fe15aa7270d678a674f9d8fd2735d5507ea Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 06:31:01 +0300 Subject: [PATCH 599/803] Fix user population logic not including country --- osu.Game/Scoring/ScoreImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 53dd511d57..4107c66dfe 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -84,7 +84,7 @@ namespace osu.Game.Scoring api.Perform(userRequest); if (userRequest.Response is APIUser user) - model.RealmUser.OnlineID = user.Id; + model.User = user; } } } From 69d967172ac3a11e34c5ea499f64dba6fd78676d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 06:32:25 +0300 Subject: [PATCH 600/803] Remove unencessary null coalesce --- osu.Game/Skinning/LegacyBeatmapSkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 43ae0a2252..e8414b4c11 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -96,7 +96,7 @@ namespace osu.Game.Skinning new SkinInfo { Name = beatmapInfo.ToString(), - Creator = beatmapInfo.Metadata.Author.Username ?? string.Empty + Creator = beatmapInfo.Metadata.Author.Username }; } } From a10c398cd58516ce85dbc4e7072047f392b48272 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 06:40:53 +0300 Subject: [PATCH 601/803] Remove no longer necessary DI --- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 02c0a0d7e1..0f806a2403 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -25,9 +25,6 @@ namespace osu.Game.Tournament.Screens.Editors { public class TeamEditorScreen : TournamentEditorScreen { - [Resolved] - private TournamentGameBase game { get; set; } - protected override BindableList Storage => LadderInfo.Teams; [BackgroundDependencyLoader] From 9e945197dce72e6827e67fedf41e1048d843a608 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Jul 2022 14:49:14 +0900 Subject: [PATCH 602/803] Use "Unknown" instead of "Alient" for unknown countries --- osu.Game/Users/Country.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Users/Country.cs b/osu.Game/Users/Country.cs index e96b96e57c..2070bcab39 100644 --- a/osu.Game/Users/Country.cs +++ b/osu.Game/Users/Country.cs @@ -12,7 +12,7 @@ namespace osu.Game.Users [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] public enum Country { - [Description("Alien")] + [Description("Unknown")] XX = 0, [Description("Bangladesh")] From acd5254f51a37ac519e5c70debd0c6c08f02952b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Jul 2022 17:18:22 +0900 Subject: [PATCH 603/803] Add test coverage ensuring unique acronyms --- .../TestSceneNoConflictingModAcronyms.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneNoConflictingModAcronyms.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneNoConflictingModAcronyms.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneNoConflictingModAcronyms.cs new file mode 100644 index 0000000000..b2ba3d99ad --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneNoConflictingModAcronyms.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. + +#nullable disable +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [HeadlessTest] + public class TestSceneNoConflictingModAcronyms : TestSceneAllRulesetPlayers + { + protected override void AddCheckSteps() + { + AddStep("Check all mod acronyms are unique", () => + { + var mods = Ruleset.Value.CreateInstance().AllMods; + + IEnumerable acronyms = mods.Select(m => m.Acronym); + + Assert.That(acronyms, Is.Unique); + }); + } + } +} From b93b6ba2ca215fac5a66e224695a908ff57925e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Jul 2022 17:07:55 +0900 Subject: [PATCH 604/803] Change "single tap" mod acronym to not conflict with "strict tracking" --- osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs index 051ceb968c..b170d30448 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModSingleTap : InputBlockingMod { public override string Name => @"Single Tap"; - public override string Acronym => @"ST"; + public override string Acronym => @"SG"; public override string Description => @"You must only use one key!"; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray(); From cf7af0061c606a737d6411a96f46c131668d3574 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sat, 16 Jul 2022 15:20:15 +0200 Subject: [PATCH 605/803] Add Touch input handler settings section --- osu.Desktop/OsuGameDesktop.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 314a03a73e..524436235e 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -22,10 +22,12 @@ using osu.Framework.Input.Handlers; using osu.Framework.Input.Handlers.Joystick; using osu.Framework.Input.Handlers.Mouse; using osu.Framework.Input.Handlers.Tablet; +using osu.Framework.Input.Handlers.Touch; using osu.Framework.Threading; using osu.Game.IO; using osu.Game.IPC; using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections; using osu.Game.Overlays.Settings.Sections.Input; namespace osu.Desktop @@ -156,6 +158,9 @@ namespace osu.Desktop case JoystickHandler jh: return new JoystickSettings(jh); + case TouchHandler th: + return new InputSection.HandlerSection(th); + default: return base.CreateSettingsSubsectionFor(handler); } From bbb2398a8b4cee039a96883f4fa7a0ed9e18f92d Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 00:46:29 +0100 Subject: [PATCH 606/803] change retry button icon from ArrowCircleLeft to Redo --- osu.Game/Screens/Ranking/RetryButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/RetryButton.cs b/osu.Game/Screens/Ranking/RetryButton.cs index a0ddc98943..c56f364ae8 100644 --- a/osu.Game/Screens/Ranking/RetryButton.cs +++ b/osu.Game/Screens/Ranking/RetryButton.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Ranking Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(13), - Icon = FontAwesome.Solid.ArrowCircleLeft, + Icon = FontAwesome.Solid.Redo, }, }; From 6636e462dc925f70c368ab60b5fdff3640dff331 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 17 Jul 2022 06:18:59 +0300 Subject: [PATCH 607/803] Fix `SoloScoreInfo` not carrying mod settings in conversion methods --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 6c48c7883f..cf7aa599f3 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -101,7 +101,7 @@ namespace osu.Game.Online.API.Requests.Responses var rulesetInstance = ruleset.CreateInstance(); - var mods = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); + var mods = Mods.Select(apiMod => apiMod.ToMod(rulesetInstance)).ToArray(); var scoreInfo = ToScoreInfo(mods); From 9382636da987c101ea0c8b90b31ce048063e7bc2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 17 Jul 2022 06:19:34 +0300 Subject: [PATCH 608/803] Catch and log exceptions from mod setting copy failure --- osu.Game/Online/API/APIMod.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index dc1db08174..8346300767 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -65,7 +65,14 @@ namespace osu.Game.Online.API if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue)) continue; - resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue); + try + { + resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue); + } + catch (Exception ex) + { + Logger.Log($"Failed to copy mod setting value '{settingValue ?? "null"}' to \"{property.Name}\": {ex.Message}"); + } } } From 5532f56a30282b7a9f8b26dd80995ebfa0d2ba5a Mon Sep 17 00:00:00 2001 From: Jay L Date: Sun, 17 Jul 2022 14:10:49 +1000 Subject: [PATCH 609/803] performance points balancing --- .../Difficulty/TaikoPerformanceCalculator.cs | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index a9cde62f44..2c2dbddf13 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -35,13 +35,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things - - if (score.Mods.Any(m => m is ModNoFail)) - multiplier *= 0.90; + double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things if (score.Mods.Any(m => m is ModHidden)) - multiplier *= 1.10; + multiplier *= 1.075; + + if (score.Mods.Any(m => m is ModEasy)) + multiplier *= 0.975; double difficultyValue = computeDifficultyValue(score, taikoAttributes); double accuracyValue = computeAccuracyValue(score, taikoAttributes); @@ -61,12 +61,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) { - double difficultyValue = Math.Pow(5 * Math.Max(1.0, attributes.StarRating / 0.175) - 4.0, 2.25) / 450.0; + double difficultyValue = Math.Pow(5 * Math.Max(1.0, attributes.StarRating / 0.115) - 4.0, 2.25) / 1150.0; double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0); difficultyValue *= lengthBonus; - difficultyValue *= Math.Pow(0.985, countMiss); + difficultyValue *= Math.Pow(0.986, countMiss); + + if (score.Mods.Any(m => m is ModEasy)) + difficultyValue *= 0.980; if (score.Mods.Any(m => m is ModHidden)) difficultyValue *= 1.025; @@ -74,7 +77,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (score.Mods.Any(m => m is ModFlashlight)) difficultyValue *= 1.05 * lengthBonus; - return difficultyValue * score.Accuracy; + return difficultyValue * Math.Pow(score.Accuracy, 1.5); } private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) @@ -82,10 +85,16 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (attributes.GreatHitWindow <= 0) return 0; - double accValue = Math.Pow(150.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 15) * 22.0; + double accuracyValue = Math.Pow(140.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 12.0) * 27; - // Bonus for many objects - it's harder to keep good accuracy up for longer - return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); + double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); + accuracyValue *= lengthBonus; + + // Slight HDFL Bonus for accuracy. + if (score.Mods.Any(m => m is ModFlashlight) && score.Mods.Any(m => m is ModHidden)) + accuracyValue *= 1.10 * lengthBonus; + + return accuracyValue; } private int totalHits => countGreat + countOk + countMeh + countMiss; From 8e7e1e6b516bf7dcf1a3088fef794bd116226bf4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 17 Jul 2022 05:20:05 +0900 Subject: [PATCH 610/803] Fix spectator client not correctly reconnecting after shutdown --- osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs | 1 + osu.Game/Online/Spectator/OnlineSpectatorClient.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index c061398209..4e6ea997c1 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -80,6 +80,7 @@ namespace osu.Game.Online.Multiplayer try { + // Importantly, use Invoke rather than Send to capture exceptions. return await connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoomWithPassword), roomId, password ?? string.Empty); } catch (HubException exception) diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index cd9f5233a2..1ed7819b05 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -56,7 +56,8 @@ namespace osu.Game.Online.Spectator try { - await connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), state); + // Importantly, use Invoke rather than Send to capture exceptions. + await connection.InvokeAsync(nameof(ISpectatorServer.BeginPlaySession), state); } catch (HubException exception) { From da7edd5d49bf25bc13fa6e722bebc436768b6603 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 17 Jul 2022 04:35:41 +0900 Subject: [PATCH 611/803] Perform actions after server reconnection --- osu.Game/Online/HubClientConnector.cs | 12 ++++++------ osu.Game/Online/IHubClientConnector.cs | 3 ++- .../Online/Multiplayer/OnlineMultiplayerClient.cs | 8 +++++++- osu.Game/Online/Spectator/OnlineSpectatorClient.cs | 9 ++++++++- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index 01f0f3a902..6bfe09e911 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -64,26 +64,26 @@ namespace osu.Game.Online this.preferMessagePack = preferMessagePack; apiState.BindTo(api.State); - apiState.BindValueChanged(_ => connectIfPossible(), true); + apiState.BindValueChanged(_ => Task.Run(connectIfPossible), true); } - public void Reconnect() + public Task Reconnect() { Logger.Log($"{clientName} reconnecting...", LoggingTarget.Network); - Task.Run(connectIfPossible); + return Task.Run(connectIfPossible); } - private void connectIfPossible() + private async Task connectIfPossible() { switch (apiState.Value) { case APIState.Failing: case APIState.Offline: - Task.Run(() => disconnect(true)); + await disconnect(true); break; case APIState.Online: - Task.Run(connect); + await connect(); break; } } diff --git a/osu.Game/Online/IHubClientConnector.cs b/osu.Game/Online/IHubClientConnector.cs index 2afab9091b..53c4897e73 100644 --- a/osu.Game/Online/IHubClientConnector.cs +++ b/osu.Game/Online/IHubClientConnector.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Bindables; using osu.Game.Online.API; @@ -32,6 +33,6 @@ namespace osu.Game.Online /// /// Reconnect if already connected. /// - void Reconnect(); + Task Reconnect(); } } diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 4e6ea997c1..6425e3c155 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -86,7 +86,13 @@ namespace osu.Game.Online.Multiplayer catch (HubException exception) { if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE) - connector?.Reconnect(); + { + Debug.Assert(connector != null); + + await connector.Reconnect(); + return await JoinRoom(roomId, password); + } + throw; } } diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index 1ed7819b05..4f9e62d0a0 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -62,7 +62,14 @@ namespace osu.Game.Online.Spectator catch (HubException exception) { if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE) - connector?.Reconnect(); + { + Debug.Assert(connector != null); + + await connector.Reconnect(); + await BeginPlayingInternal(state); + return; + } + throw; } } From 55a8a3563b9f8c8f6bcda186494b986f9cc63ce1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 17 Jul 2022 18:23:15 +0900 Subject: [PATCH 612/803] Change `MultiplayerMatchSubScreen` to not immediately leave the room on connection loss Instead, we can rely on `MultiplayerClient.Room` going `null`. --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 5 ++--- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 8 ++------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 9832acb140..603bd10c38 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -21,7 +21,6 @@ using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Utils; -using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Online.Multiplayer { @@ -91,7 +90,7 @@ namespace osu.Game.Online.Multiplayer /// /// The joined . /// - public virtual MultiplayerRoom? Room + public virtual MultiplayerRoom? Room // virtual for moq { get { @@ -150,7 +149,7 @@ namespace osu.Game.Online.Multiplayer // clean up local room state on server disconnect. if (!connected.NewValue && Room != null) { - Logger.Log("Connection to multiplayer server was lost.", LoggingTarget.Runtime, LogLevel.Important); + Logger.Log("Clearing room due to multiplayer server connection loss.", LoggingTarget.Runtime, LogLevel.Important); LeaveRoom(); } })); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4eb16a854b..4bbeb39063 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -70,12 +70,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer client.LoadRequested += onLoadRequested; client.RoomUpdated += onRoomUpdated; - isConnected.BindTo(client.IsConnected); - isConnected.BindValueChanged(connected => - { - if (!connected.NewValue) - handleRoomLost(); - }, true); + if (!client.IsConnected.Value) + handleRoomLost(); } protected override Drawable CreateMainContent() => new Container From 51071be3152de31ca5f793262f4dae17487df793 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 17 Jul 2022 21:20:50 +0900 Subject: [PATCH 613/803] Don't show "missing background" messages to user Bit of an oversight. As reported on [twitter](https://twitter.com/emyl___/status/1548627793075998720) and somewhere else i forgot. --- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index ce883a7092..df44f01629 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -168,7 +168,7 @@ namespace osu.Game.Beatmaps if (texture == null) { - Logger.Log($"Beatmap background failed to load (file {Metadata.BackgroundFile} not found on disk at expected location {fileStorePath}).", level: LogLevel.Error); + Logger.Log($"Beatmap background failed to load (file {Metadata.BackgroundFile} not found on disk at expected location {fileStorePath})."); return null; } From 1f288157f491bafd470388c47f65e43c896ff008 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:07:05 +0100 Subject: [PATCH 614/803] change `GetUserScoresRequest` to return `SoloScoreInfo` instead of `APIScore` --- osu.Game/Online/API/Requests/GetUserScoresRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index 9bd78b7be1..8ef797f799 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests { - public class GetUserScoresRequest : PaginatedAPIRequest> + public class GetUserScoresRequest : PaginatedAPIRequest> { private readonly long userId; private readonly ScoreType type; From e8d88e29c6331d73e9041efefbdb705cbf93bd0c Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:08:04 +0100 Subject: [PATCH 615/803] change `DrawableProfileScore` and `DrawableProfileWeightedScore` to take `SoloScoreInfo` and `APIBeatmap` instead of `APIScore` --- .../Profile/Sections/Ranks/DrawableProfileScore.cs | 12 +++++++----- .../Sections/Ranks/DrawableProfileWeightedScore.cs | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 90a357a281..1ff1e0e842 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -31,7 +31,8 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private const float performance_background_shear = 0.45f; - protected readonly APIScore Score; + protected readonly SoloScoreInfo Score; + protected readonly APIBeatmap Beatmap; [Resolved] private OsuColour colours { get; set; } @@ -39,9 +40,10 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks [Resolved] private OverlayColourProvider colourProvider { get; set; } - public DrawableProfileScore(APIScore score) + public DrawableProfileScore(SoloScoreInfo score, APIBeatmap beatmap) { Score = score; + Beatmap = beatmap; RelativeSizeAxes = Axes.X; Height = height; @@ -84,7 +86,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Spacing = new Vector2(0, 2), Children = new Drawable[] { - new ScoreBeatmapMetadataContainer(Score.Beatmap), + new ScoreBeatmapMetadataContainer(Beatmap), new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -94,11 +96,11 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { new OsuSpriteText { - Text = $"{Score.Beatmap?.DifficultyName}", + Text = $"{Beatmap.DifficultyName}", Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), Colour = colours.Yellow }, - new DrawableDate(Score.Date, 12) + new DrawableDate(Score.EndedAt ?? DateTimeOffset.Now, 12) { Colour = colourProvider.Foreground1 } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs index f8f83883fc..8d0a41ec48 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs @@ -18,8 +18,8 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly double weight; - public DrawableProfileWeightedScore(APIScore score, double weight) - : base(score) + public DrawableProfileWeightedScore(SoloScoreInfo score, APIBeatmap beatmap, double weight) + : base(score, beatmap) { this.weight = weight; } From ef4237c4ac8bf32d5a0b8bb51662aa6e3100b9ef Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:08:31 +0100 Subject: [PATCH 616/803] create special subsection class for paginated profile scores --- .../PaginatedProfileScoreSubsection.cs | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 osu.Game/Overlays/Profile/Sections/PaginatedProfileScoreSubsection.cs diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileScoreSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileScoreSubsection.cs new file mode 100644 index 0000000000..408d4a96bf --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileScoreSubsection.cs @@ -0,0 +1,193 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osuTK; + +namespace osu.Game.Overlays.Profile.Sections +{ + public abstract class PaginatedProfileScoreSubsection : ProfileSubsection + { + /// + /// The number of items displayed per page. + /// + protected virtual int ItemsPerPage => 50; + + /// + /// The number of items displayed initially. + /// + protected virtual int InitialItemsCount => 5; + + [Resolved] + private IAPIProvider api { get; set; } + + protected PaginationParameters? CurrentPage { get; private set; } + + protected ReverseChildIDFillFlowContainer ItemsContainer { get; private set; } + + private APIRequest> scoreRetrievalRequest; + private APIRequest beatmapRetrievalRequest; + private CancellationTokenSource loadCancellation; + + protected List CurrentScores { get; private set; } = new List(); + + private ShowMoreButton moreButton; + private OsuSpriteText missing; + private readonly LocalisableString? missingText; + + protected PaginatedProfileScoreSubsection(Bindable user, LocalisableString? headerText = null, LocalisableString? missingText = null) + : base(user, headerText, CounterVisibilityState.AlwaysVisible) + { + this.missingText = missingText; + } + + protected override Drawable CreateContent() => new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + // reverse ID flow is required for correct Z-ordering of the items (last item should be front-most). + // particularly important in PaginatedBeatmapContainer, as it uses beatmap cards, which have expandable overhanging content. + ItemsContainer = new ReverseChildIDFillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Spacing = new Vector2(0, 2), + // ensure the container and its contents are in front of the "more" button. + Depth = float.MinValue + }, + moreButton = new ShowMoreButton + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Alpha = 0, + Margin = new MarginPadding { Top = 10 }, + Action = showMore, + }, + missing = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 15), + Text = missingText ?? string.Empty, + Alpha = 0, + } + } + }; + + protected override void LoadComplete() + { + base.LoadComplete(); + User.BindValueChanged(onUserChanged, true); + } + + private void onUserChanged(ValueChangedEvent e) + { + loadCancellation?.Cancel(); + scoreRetrievalRequest?.Cancel(); + beatmapRetrievalRequest?.Cancel(); + + CurrentPage = null; + ItemsContainer.Clear(); + CurrentScores.Clear(); + + if (e.NewValue != null) + { + showMore(); + SetCount(GetCount(e.NewValue)); + } + } + + private void showMore() + { + loadCancellation = new CancellationTokenSource(); + + CurrentPage = CurrentPage?.TakeNext(ItemsPerPage) ?? new PaginationParameters(InitialItemsCount); + + scoreRetrievalRequest = CreateScoreRequest(CurrentPage.Value); + scoreRetrievalRequest.Success += requestBeatmaps; + + api.Queue(scoreRetrievalRequest); + } + + private void requestBeatmaps(List items) + { + CurrentScores = items; + + beatmapRetrievalRequest = CreateBeatmapsRequest(items); + beatmapRetrievalRequest.Success += UpdateItems; + + api.Queue(beatmapRetrievalRequest); + } + + protected virtual APIRequest CreateBeatmapsRequest(List items) => new GetBeatmapsRequest(items.Select(i => i.BeatmapID).ToArray()); + + protected virtual void UpdateItems(GetBeatmapsResponse beatmaps) => Schedule(() => + { + var scoreBeatmapPairs = new List>(); + + foreach (var score in CurrentScores) + { + var beatmap = beatmaps.Beatmaps.Find(m => m.OnlineID == score.BeatmapID); + scoreBeatmapPairs.Add(new Tuple(score, beatmap)); + } + + OnItemsReceived(scoreBeatmapPairs); + + if (!scoreBeatmapPairs.Any() && CurrentPage?.Offset == 0) + { + moreButton.Hide(); + moreButton.IsLoading = false; + + if (missingText.HasValue) + missing.Show(); + + return; + } + + LoadComponentsAsync(scoreBeatmapPairs.Select(CreateDrawableItem).Where(d => d != null), drawables => + { + missing.Hide(); + + moreButton.FadeTo(scoreBeatmapPairs.Count == CurrentPage?.Limit ? 1 : 0); + moreButton.IsLoading = false; + + ItemsContainer.AddRange(drawables); + }, loadCancellation.Token); + }); + + protected virtual int GetCount(APIUser user) => 0; + + protected virtual void OnItemsReceived(List> scoreBeatmapPairs) + { + } + + protected abstract APIRequest> CreateScoreRequest(PaginationParameters pagination); + + protected abstract Drawable CreateDrawableItem(Tuple scoreBeatmapPair); + + protected override void Dispose(bool isDisposing) + { + scoreRetrievalRequest?.Cancel(); + loadCancellation?.Cancel(); + base.Dispose(isDisposing); + } + } +} From 71a4b8843ffc52602f1fa5e3620da84b7bb0958e Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:08:44 +0100 Subject: [PATCH 617/803] update tests to new profile score format --- .../Online/TestSceneUserProfileScores.cs | 104 +++++++++--------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs index fa28df3061..33ddf4a3a9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs @@ -21,20 +21,12 @@ namespace osu.Game.Tests.Visual.Online { public TestSceneUserProfileScores() { - var firstScore = new APIScore + var firstScore = new SoloScoreInfo { PP = 1047.21, Rank = ScoreRank.SH, - Beatmap = new APIBeatmap - { - BeatmapSet = new APIBeatmapSet - { - Title = "JUSTadICE (TV Size)", - Artist = "Oomori Seiko", - }, - DifficultyName = "Extreme" - }, - Date = DateTimeOffset.Now, + BeatmapID = 2058788, + EndedAt = DateTimeOffset.Now, Mods = new[] { new APIMod { Acronym = new OsuModHidden().Acronym }, @@ -43,21 +35,22 @@ namespace osu.Game.Tests.Visual.Online }, Accuracy = 0.9813 }; + var firstBeatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet() + { + Title = "JUSTadICE (TV Size)", + Artist = "Oomori Seiko", + }, + DifficultyName = "Extreme" + }; - var secondScore = new APIScore + var secondScore = new SoloScoreInfo { PP = 134.32, Rank = ScoreRank.A, - Beatmap = new APIBeatmap - { - BeatmapSet = new APIBeatmapSet - { - Title = "Triumph & Regret", - Artist = "typeMARS", - }, - DifficultyName = "[4K] Regret" - }, - Date = DateTimeOffset.Now, + BeatmapID = 767046, + EndedAt = DateTimeOffset.Now, Mods = new[] { new APIMod { Acronym = new OsuModHardRock().Acronym }, @@ -65,39 +58,50 @@ namespace osu.Game.Tests.Visual.Online }, Accuracy = 0.998546 }; + var secondBeatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet() + { + Title = "Triumph & Regret", + Artist = "typeMARS", + }, + DifficultyName = "[4K] Regret" + }; - var thirdScore = new APIScore + var thirdScore = new SoloScoreInfo { PP = 96.83, Rank = ScoreRank.S, - Beatmap = new APIBeatmap - { - BeatmapSet = new APIBeatmapSet - { - Title = "Idolize", - Artist = "Creo", - }, - DifficultyName = "Insane" - }, - Date = DateTimeOffset.Now, + BeatmapID = 2134713, + EndedAt = DateTimeOffset.Now, Accuracy = 0.9726 }; + var thirdBeatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet() + { + Title = "Idolize", + Artist = "Creo", + }, + DifficultyName = "Insane" + }; - var noPPScore = new APIScore + var noPPScore = new SoloScoreInfo { Rank = ScoreRank.B, - Beatmap = new APIBeatmap - { - BeatmapSet = new APIBeatmapSet - { - Title = "C18H27NO3(extend)", - Artist = "Team Grimoire", - }, - DifficultyName = "[4K] Cataclysmic Hypernova" - }, - Date = DateTimeOffset.Now, + BeatmapID = 992512, + EndedAt = DateTimeOffset.Now, Accuracy = 0.55879 }; + var noPPBeatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet() + { + Title = "Galaxy Collapse", + Artist = "Kurokotei", + }, + DifficultyName = "[4K] Cataclysmic Hypernova" + }; Add(new FillFlowContainer { @@ -109,12 +113,12 @@ namespace osu.Game.Tests.Visual.Online Spacing = new Vector2(0, 10), Children = new[] { - new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(firstScore)), - new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(secondScore)), - new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore)), - new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(firstScore, 0.97)), - new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(secondScore, 0.85)), - new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(thirdScore, 0.66)), + new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(firstScore, firstBeatmap)), + new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(secondScore, secondBeatmap)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore, noPPBeatmap)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(firstScore, firstBeatmap, 0.97)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(secondScore, secondBeatmap, 0.85)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(thirdScore, thirdBeatmap, 0.66)), } }); } From 3a8b5d48b9d4a3c93f83a42734a69a7cb05d6d63 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:09:14 +0100 Subject: [PATCH 618/803] update `PaginatedScoreContainer` to use new class and format --- .../Sections/Ranks/PaginatedScoreContainer.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 15c7b8f042..9af7d59d53 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -17,7 +17,7 @@ using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class PaginatedScoreContainer : PaginatedProfileSubsection + public class PaginatedScoreContainer : PaginatedProfileScoreSubsection { private readonly ScoreType type; @@ -54,28 +54,28 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks } } - protected override void OnItemsReceived(List items) + protected override void OnItemsReceived(List> scoreBeatmapPairs) { if (CurrentPage == null || CurrentPage?.Offset == 0) drawableItemIndex = 0; - base.OnItemsReceived(items); + base.OnItemsReceived(scoreBeatmapPairs); } - protected override APIRequest> CreateRequest(PaginationParameters pagination) => + protected override APIRequest> CreateScoreRequest(PaginationParameters pagination) => new GetUserScoresRequest(User.Value.Id, type, pagination); private int drawableItemIndex; - protected override Drawable CreateDrawableItem(APIScore model) + protected override Drawable CreateDrawableItem(Tuple scoreBeatmapPair) { switch (type) { default: - return new DrawableProfileScore(model); + return new DrawableProfileScore(scoreBeatmapPair.Item1, scoreBeatmapPair.Item2); case ScoreType.Best: - return new DrawableProfileWeightedScore(model, Math.Pow(0.95, drawableItemIndex++)); + return new DrawableProfileWeightedScore(scoreBeatmapPair.Item1, scoreBeatmapPair.Item2, Math.Pow(0.95, drawableItemIndex++)); } } } From c2277031f0b7b728d5d6dbdea0dac31f53c9d7e3 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:21:55 +0100 Subject: [PATCH 619/803] add `Beatmap` to SoloScoreInfo --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 6c48c7883f..963b2975d2 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -82,6 +82,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("user")] public APIUser? User { get; set; } + [JsonProperty("beatmap")] + public APIBeatmap? Beatmap { get; set; } + [JsonProperty("pp")] public double? PP { get; set; } From 7135329c8c3339e3b95861d0311c497d58a843ad Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:22:27 +0100 Subject: [PATCH 620/803] remove `PaginatedProfileScoreSubsectio, revert profile score drawables --- .../PaginatedProfileScoreSubsection.cs | 193 ------------------ .../Sections/Ranks/DrawableProfileScore.cs | 8 +- .../Ranks/DrawableProfileWeightedScore.cs | 4 +- .../Sections/Ranks/PaginatedScoreContainer.cs | 14 +- 4 files changed, 12 insertions(+), 207 deletions(-) delete mode 100644 osu.Game/Overlays/Profile/Sections/PaginatedProfileScoreSubsection.cs diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileScoreSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileScoreSubsection.cs deleted file mode 100644 index 408d4a96bf..0000000000 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileScoreSubsection.cs +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Localisation; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; -using osu.Game.Graphics.Containers; -using osu.Game.Online.API.Requests; -using osu.Game.Online.API.Requests.Responses; -using osuTK; - -namespace osu.Game.Overlays.Profile.Sections -{ - public abstract class PaginatedProfileScoreSubsection : ProfileSubsection - { - /// - /// The number of items displayed per page. - /// - protected virtual int ItemsPerPage => 50; - - /// - /// The number of items displayed initially. - /// - protected virtual int InitialItemsCount => 5; - - [Resolved] - private IAPIProvider api { get; set; } - - protected PaginationParameters? CurrentPage { get; private set; } - - protected ReverseChildIDFillFlowContainer ItemsContainer { get; private set; } - - private APIRequest> scoreRetrievalRequest; - private APIRequest beatmapRetrievalRequest; - private CancellationTokenSource loadCancellation; - - protected List CurrentScores { get; private set; } = new List(); - - private ShowMoreButton moreButton; - private OsuSpriteText missing; - private readonly LocalisableString? missingText; - - protected PaginatedProfileScoreSubsection(Bindable user, LocalisableString? headerText = null, LocalisableString? missingText = null) - : base(user, headerText, CounterVisibilityState.AlwaysVisible) - { - this.missingText = missingText; - } - - protected override Drawable CreateContent() => new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - // reverse ID flow is required for correct Z-ordering of the items (last item should be front-most). - // particularly important in PaginatedBeatmapContainer, as it uses beatmap cards, which have expandable overhanging content. - ItemsContainer = new ReverseChildIDFillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Spacing = new Vector2(0, 2), - // ensure the container and its contents are in front of the "more" button. - Depth = float.MinValue - }, - moreButton = new ShowMoreButton - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Alpha = 0, - Margin = new MarginPadding { Top = 10 }, - Action = showMore, - }, - missing = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 15), - Text = missingText ?? string.Empty, - Alpha = 0, - } - } - }; - - protected override void LoadComplete() - { - base.LoadComplete(); - User.BindValueChanged(onUserChanged, true); - } - - private void onUserChanged(ValueChangedEvent e) - { - loadCancellation?.Cancel(); - scoreRetrievalRequest?.Cancel(); - beatmapRetrievalRequest?.Cancel(); - - CurrentPage = null; - ItemsContainer.Clear(); - CurrentScores.Clear(); - - if (e.NewValue != null) - { - showMore(); - SetCount(GetCount(e.NewValue)); - } - } - - private void showMore() - { - loadCancellation = new CancellationTokenSource(); - - CurrentPage = CurrentPage?.TakeNext(ItemsPerPage) ?? new PaginationParameters(InitialItemsCount); - - scoreRetrievalRequest = CreateScoreRequest(CurrentPage.Value); - scoreRetrievalRequest.Success += requestBeatmaps; - - api.Queue(scoreRetrievalRequest); - } - - private void requestBeatmaps(List items) - { - CurrentScores = items; - - beatmapRetrievalRequest = CreateBeatmapsRequest(items); - beatmapRetrievalRequest.Success += UpdateItems; - - api.Queue(beatmapRetrievalRequest); - } - - protected virtual APIRequest CreateBeatmapsRequest(List items) => new GetBeatmapsRequest(items.Select(i => i.BeatmapID).ToArray()); - - protected virtual void UpdateItems(GetBeatmapsResponse beatmaps) => Schedule(() => - { - var scoreBeatmapPairs = new List>(); - - foreach (var score in CurrentScores) - { - var beatmap = beatmaps.Beatmaps.Find(m => m.OnlineID == score.BeatmapID); - scoreBeatmapPairs.Add(new Tuple(score, beatmap)); - } - - OnItemsReceived(scoreBeatmapPairs); - - if (!scoreBeatmapPairs.Any() && CurrentPage?.Offset == 0) - { - moreButton.Hide(); - moreButton.IsLoading = false; - - if (missingText.HasValue) - missing.Show(); - - return; - } - - LoadComponentsAsync(scoreBeatmapPairs.Select(CreateDrawableItem).Where(d => d != null), drawables => - { - missing.Hide(); - - moreButton.FadeTo(scoreBeatmapPairs.Count == CurrentPage?.Limit ? 1 : 0); - moreButton.IsLoading = false; - - ItemsContainer.AddRange(drawables); - }, loadCancellation.Token); - }); - - protected virtual int GetCount(APIUser user) => 0; - - protected virtual void OnItemsReceived(List> scoreBeatmapPairs) - { - } - - protected abstract APIRequest> CreateScoreRequest(PaginationParameters pagination); - - protected abstract Drawable CreateDrawableItem(Tuple scoreBeatmapPair); - - protected override void Dispose(bool isDisposing) - { - scoreRetrievalRequest?.Cancel(); - loadCancellation?.Cancel(); - base.Dispose(isDisposing); - } - } -} diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 1ff1e0e842..26181e7b40 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -32,7 +32,6 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private const float performance_background_shear = 0.45f; protected readonly SoloScoreInfo Score; - protected readonly APIBeatmap Beatmap; [Resolved] private OsuColour colours { get; set; } @@ -40,10 +39,9 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks [Resolved] private OverlayColourProvider colourProvider { get; set; } - public DrawableProfileScore(SoloScoreInfo score, APIBeatmap beatmap) + public DrawableProfileScore(SoloScoreInfo score) { Score = score; - Beatmap = beatmap; RelativeSizeAxes = Axes.X; Height = height; @@ -86,7 +84,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Spacing = new Vector2(0, 2), Children = new Drawable[] { - new ScoreBeatmapMetadataContainer(Beatmap), + new ScoreBeatmapMetadataContainer(Score.Beatmap), new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -96,7 +94,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { new OsuSpriteText { - Text = $"{Beatmap.DifficultyName}", + Text = $"{Score.Beatmap?.DifficultyName}", Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), Colour = colours.Yellow }, diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs index 8d0a41ec48..94d95dc27e 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs @@ -18,8 +18,8 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly double weight; - public DrawableProfileWeightedScore(SoloScoreInfo score, APIBeatmap beatmap, double weight) - : base(score, beatmap) + public DrawableProfileWeightedScore(SoloScoreInfo score, double weight) + : base(score) { this.weight = weight; } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 9af7d59d53..8378d61119 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -17,7 +17,7 @@ using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class PaginatedScoreContainer : PaginatedProfileScoreSubsection + public class PaginatedScoreContainer : PaginatedProfileSubsection { private readonly ScoreType type; @@ -54,28 +54,28 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks } } - protected override void OnItemsReceived(List> scoreBeatmapPairs) + protected override void OnItemsReceived(List items) { if (CurrentPage == null || CurrentPage?.Offset == 0) drawableItemIndex = 0; - base.OnItemsReceived(scoreBeatmapPairs); + base.OnItemsReceived(items); } - protected override APIRequest> CreateScoreRequest(PaginationParameters pagination) => + protected override APIRequest> CreateRequest(PaginationParameters pagination) => new GetUserScoresRequest(User.Value.Id, type, pagination); private int drawableItemIndex; - protected override Drawable CreateDrawableItem(Tuple scoreBeatmapPair) + protected override Drawable CreateDrawableItem(SoloScoreInfo item) { switch (type) { default: - return new DrawableProfileScore(scoreBeatmapPair.Item1, scoreBeatmapPair.Item2); + return new DrawableProfileScore(item); case ScoreType.Best: - return new DrawableProfileWeightedScore(scoreBeatmapPair.Item1, scoreBeatmapPair.Item2, Math.Pow(0.95, drawableItemIndex++)); + return new DrawableProfileWeightedScore(item, Math.Pow(0.95, drawableItemIndex++)); } } } From 14ae183c700b18188f2d00288b4f5c405f21493c Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:22:46 +0100 Subject: [PATCH 621/803] update tests to match `SoloScoreInfo` --- .../Online/TestSceneUserProfileScores.cs | 88 +++++++++---------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs index 33ddf4a3a9..0eb6ec3c04 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs @@ -25,7 +25,15 @@ namespace osu.Game.Tests.Visual.Online { PP = 1047.21, Rank = ScoreRank.SH, - BeatmapID = 2058788, + Beatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet + { + Title = "JUSTadICE (TV Size)", + Artist = "Oomori Seiko", + }, + DifficultyName = "Extreme" + }, EndedAt = DateTimeOffset.Now, Mods = new[] { @@ -35,21 +43,20 @@ namespace osu.Game.Tests.Visual.Online }, Accuracy = 0.9813 }; - var firstBeatmap = new APIBeatmap - { - BeatmapSet = new APIBeatmapSet() - { - Title = "JUSTadICE (TV Size)", - Artist = "Oomori Seiko", - }, - DifficultyName = "Extreme" - }; var secondScore = new SoloScoreInfo { PP = 134.32, Rank = ScoreRank.A, - BeatmapID = 767046, + Beatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet + { + Title = "Triumph & Regret", + Artist = "typeMARS", + }, + DifficultyName = "[4K] Regret" + }, EndedAt = DateTimeOffset.Now, Mods = new[] { @@ -58,50 +65,39 @@ namespace osu.Game.Tests.Visual.Online }, Accuracy = 0.998546 }; - var secondBeatmap = new APIBeatmap - { - BeatmapSet = new APIBeatmapSet() - { - Title = "Triumph & Regret", - Artist = "typeMARS", - }, - DifficultyName = "[4K] Regret" - }; var thirdScore = new SoloScoreInfo { PP = 96.83, Rank = ScoreRank.S, - BeatmapID = 2134713, + Beatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet + { + Title = "Idolize", + Artist = "Creo", + }, + DifficultyName = "Insane" + }, EndedAt = DateTimeOffset.Now, Accuracy = 0.9726 }; - var thirdBeatmap = new APIBeatmap - { - BeatmapSet = new APIBeatmapSet() - { - Title = "Idolize", - Artist = "Creo", - }, - DifficultyName = "Insane" - }; var noPPScore = new SoloScoreInfo { Rank = ScoreRank.B, - BeatmapID = 992512, + Beatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet + { + Title = "C18H27NO3(extend)", + Artist = "Team Grimoire", + }, + DifficultyName = "[4K] Cataclysmic Hypernova" + }, EndedAt = DateTimeOffset.Now, Accuracy = 0.55879 }; - var noPPBeatmap = new APIBeatmap - { - BeatmapSet = new APIBeatmapSet() - { - Title = "Galaxy Collapse", - Artist = "Kurokotei", - }, - DifficultyName = "[4K] Cataclysmic Hypernova" - }; Add(new FillFlowContainer { @@ -113,12 +109,12 @@ namespace osu.Game.Tests.Visual.Online Spacing = new Vector2(0, 10), Children = new[] { - new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(firstScore, firstBeatmap)), - new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(secondScore, secondBeatmap)), - new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore, noPPBeatmap)), - new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(firstScore, firstBeatmap, 0.97)), - new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(secondScore, secondBeatmap, 0.85)), - new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(thirdScore, thirdBeatmap, 0.66)), + new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(firstScore)), + new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(secondScore)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(firstScore, 0.97)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(secondScore, 0.85)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(thirdScore, 0.66)), } }); } From c73eff7c890014fbec8a30a1304eb424216bd7d2 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:46:22 +0100 Subject: [PATCH 622/803] add `BeatmapSet` to `SoloScoreInfo` --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 963b2975d2..e7abb28286 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -85,6 +85,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("beatmap")] public APIBeatmap? Beatmap { get; set; } + [JsonProperty("beatmapset")] + public APIBeatmapSet? BeatmapSet { get; set; } + [JsonProperty("pp")] public double? PP { get; set; } From 486fbd25315c56835a1dd3ce67f2fef503aaa6cf Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:47:04 +0100 Subject: [PATCH 623/803] create instance of `BeatmapInfo` for use in `ScoreBeatmapMetadataContainer` --- .../Sections/Ranks/DrawableProfileScore.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 26181e7b40..c11a99808e 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -50,6 +50,24 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { + var beatmapSet = Score.BeatmapSet ?? Score.Beatmap?.BeatmapSet; + var beatmapInfo = new BeatmapInfo(); + + if (beatmapSet != null) + { + var beatmapMetadata = new BeatmapMetadata + { + Artist = beatmapSet.Artist, + ArtistUnicode = beatmapSet.ArtistUnicode, + Title = beatmapSet.Title, + TitleUnicode = beatmapSet.TitleUnicode, + }; + + beatmapInfo.Metadata = beatmapMetadata; + if (Score.Beatmap?.DifficultyName != null) + beatmapInfo.DifficultyName = Score.Beatmap.DifficultyName; + } + AddInternal(new ProfileItemContainer { Children = new Drawable[] @@ -84,7 +102,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Spacing = new Vector2(0, 2), Children = new Drawable[] { - new ScoreBeatmapMetadataContainer(Score.Beatmap), + new ScoreBeatmapMetadataContainer(beatmapInfo), new FillFlowContainer { AutoSizeAxes = Axes.Both, From 7a6666996fef438427666806bbfbffe9b934d872 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:50:53 +0100 Subject: [PATCH 624/803] rename `item` to `model` in `CreateDrawableItem` --- .../Profile/Sections/Ranks/PaginatedScoreContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 8378d61119..2564692c87 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -67,15 +67,15 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private int drawableItemIndex; - protected override Drawable CreateDrawableItem(SoloScoreInfo item) + protected override Drawable CreateDrawableItem(SoloScoreInfo model) { switch (type) { default: - return new DrawableProfileScore(item); + return new DrawableProfileScore(model); case ScoreType.Best: - return new DrawableProfileWeightedScore(item, Math.Pow(0.95, drawableItemIndex++)); + return new DrawableProfileWeightedScore(model, Math.Pow(0.95, drawableItemIndex++)); } } } From 1caab78bdc38dcc512831a88809749db334837f7 Mon Sep 17 00:00:00 2001 From: NotGumballer91 <64581009+NotGumballer91@users.noreply.github.com> Date: Mon, 18 Jul 2022 00:09:31 +0800 Subject: [PATCH 625/803] Update ModAutoplay.cs --- osu.Game/Rulesets/Mods/ModAutoplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 6d786fc8e2..b4ab0500c7 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods public override bool ValidForMultiplayer => false; public override bool ValidForMultiplayerAsFreeMod => false; - public override Type[] IncompatibleMods => new[] { typeof(ModCinema), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) }; + public override Type[] IncompatibleMods => new[] { typeof(ModCinema), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAdaptiveSpeed) }; public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; From 028653eb97ef47051b6c21dc7156ecbb45a24eed Mon Sep 17 00:00:00 2001 From: NotGumballer91 <64581009+NotGumballer91@users.noreply.github.com> Date: Mon, 18 Jul 2022 00:10:49 +0800 Subject: [PATCH 626/803] Update ModAdaptiveSpeed.cs --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index aea6e12a07..eab0f8dc26 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mods public override bool ValidForMultiplayer => false; public override bool ValidForMultiplayerAsFreeMod => false; - public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp) }; + public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp), typeof(ModAutoplay) }; [SettingSource("Initial rate", "The starting speed of the track")] public BindableNumber InitialRate { get; } = new BindableDouble From 4e8bf1da52b2c410bd5739a8b14eb0926cf9b7e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 01:23:46 +0900 Subject: [PATCH 627/803] Don't sent ruleset configuration failures to sentry --- osu.Game/Overlays/Settings/Sections/RulesetSection.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/RulesetSection.cs b/osu.Game/Overlays/Settings/Sections/RulesetSection.cs index 2368459b4e..a5f5810214 100644 --- a/osu.Game/Overlays/Settings/Sections/RulesetSection.cs +++ b/osu.Game/Overlays/Settings/Sections/RulesetSection.cs @@ -1,17 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Logging; -using osu.Game.Rulesets; using osu.Game.Localisation; +using osu.Game.Rulesets; namespace osu.Game.Overlays.Settings.Sections { @@ -36,9 +33,9 @@ namespace osu.Game.Overlays.Settings.Sections if (section != null) Add(section); } - catch (Exception e) + catch { - Logger.Error(e, "Failed to load ruleset settings"); + Logger.Log($"Failed to load ruleset settings for {ruleset.RulesetInfo.Name}. Please check for an update from the developer.", level: LogLevel.Error); } } } From 8791edf84c1ca5b2500dcb7b80ddcd058b952f69 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 18:10:33 +0100 Subject: [PATCH 628/803] set `Beatmap.BeatmapSet` to `BeatmapSet` property in `SoloScoreInfo` --- .../API/Requests/Responses/SoloScoreInfo.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index e7abb28286..d8adbbb9ad 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using osu.Game.Beatmaps; @@ -86,7 +87,19 @@ namespace osu.Game.Online.API.Requests.Responses public APIBeatmap? Beatmap { get; set; } [JsonProperty("beatmapset")] - public APIBeatmapSet? BeatmapSet { get; set; } + [CanBeNull] + public APIBeatmapSet BeatmapSet + { + set + { + // in the deserialisation case we need to ferry this data across. + // the order of properties returned by the API guarantees that the beatmap is populated by this point. + if (!(Beatmap is APIBeatmap apiBeatmap)) + throw new InvalidOperationException("Beatmap set metadata arrived before beatmap metadata in response"); + + apiBeatmap.BeatmapSet = value; + } + } [JsonProperty("pp")] public double? PP { get; set; } From a5d7075ef171b2d0ba50a9ac6b6c3f4e3c48331b Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 18:10:47 +0100 Subject: [PATCH 629/803] simplify beatmap metadata logic in `DrawableProfileScore` --- .../Sections/Ranks/DrawableProfileScore.cs | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index c11a99808e..26181e7b40 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -50,24 +50,6 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - var beatmapSet = Score.BeatmapSet ?? Score.Beatmap?.BeatmapSet; - var beatmapInfo = new BeatmapInfo(); - - if (beatmapSet != null) - { - var beatmapMetadata = new BeatmapMetadata - { - Artist = beatmapSet.Artist, - ArtistUnicode = beatmapSet.ArtistUnicode, - Title = beatmapSet.Title, - TitleUnicode = beatmapSet.TitleUnicode, - }; - - beatmapInfo.Metadata = beatmapMetadata; - if (Score.Beatmap?.DifficultyName != null) - beatmapInfo.DifficultyName = Score.Beatmap.DifficultyName; - } - AddInternal(new ProfileItemContainer { Children = new Drawable[] @@ -102,7 +84,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Spacing = new Vector2(0, 2), Children = new Drawable[] { - new ScoreBeatmapMetadataContainer(beatmapInfo), + new ScoreBeatmapMetadataContainer(Score.Beatmap), new FillFlowContainer { AutoSizeAxes = Axes.Both, From 032cc6c670869cf2414f398c9ed1d8cdda4c3eac Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 18:15:36 +0100 Subject: [PATCH 630/803] use type annotation for nullable `BeatmapSet` --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index d8adbbb9ad..21443280b6 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -87,8 +87,7 @@ namespace osu.Game.Online.API.Requests.Responses public APIBeatmap? Beatmap { get; set; } [JsonProperty("beatmapset")] - [CanBeNull] - public APIBeatmapSet BeatmapSet + public APIBeatmapSet? BeatmapSet { set { From 5875f53e07198ec5b367e0325a6ad0cd092b991e Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 18:16:30 +0100 Subject: [PATCH 631/803] remove unused import --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 21443280b6..393597dbfe 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using osu.Game.Beatmaps; From e13c1254e5211c459c00a4a84a4a63151d66035a Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 20:41:43 +0100 Subject: [PATCH 632/803] make perfect incompatible with autopilot --- osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs index c5795177d0..1b12aab150 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs @@ -3,11 +3,14 @@ #nullable disable +using System; +using System.Linq; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.Mods { public class OsuModPerfect : ModPerfect { + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new Type[] { typeof(OsuModAutopilot) }).ToArray(); } } From 491558261f4163f1f06e0b16f7cffe7a255e9af1 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 20:44:06 +0100 Subject: [PATCH 633/803] remove unnecessary type-specification --- osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs index 1b12aab150..bde7718da5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs @@ -11,6 +11,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModPerfect : ModPerfect { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new Type[] { typeof(OsuModAutopilot) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray(); } } From 83429d2f221567198f19b1a618f785a260ad578f Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 20:45:17 +0100 Subject: [PATCH 634/803] make cinema incompatible with repel --- osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs index 704b922ee5..d5096619b9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModCinema : ModCinema { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap), typeof(OsuModRepel) }).ToArray(); public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) => new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" }); From 7e4ce899814167ee3dee340597b28e49b881c201 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 04:03:18 +0300 Subject: [PATCH 635/803] Include mod settings in profile score mod icons --- .../Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 26181e7b40..c5e2b6df0c 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -138,7 +138,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { var ruleset = rulesets.GetRuleset(Score.RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {Score.RulesetID} not found locally"); - return new ModIcon(ruleset.CreateInstance().CreateModFromAcronym(mod.Acronym)) + return new ModIcon(mod.ToMod(ruleset.CreateInstance())) { Scale = new Vector2(0.35f) }; From b33f8aa0fcdb3d4ace4d3054d4811c86e1f72aec Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 06:45:00 +0300 Subject: [PATCH 636/803] Fix "Start Chat" on multiplayer/playlist chat not opening overlay --- osu.Game/Overlays/Chat/ChatLine.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 5961298485..4c425d3d4c 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -257,10 +257,14 @@ namespace osu.Game.Overlays.Chat } [BackgroundDependencyLoader] - private void load(UserProfileOverlay? profile, ChannelManager? chatManager) + private void load(UserProfileOverlay? profile, ChannelManager? chatManager, ChatOverlay? chatOverlay) { Action = () => profile?.ShowUser(sender); - startChatAction = () => chatManager?.OpenPrivateChannel(sender); + startChatAction = () => + { + chatManager?.OpenPrivateChannel(sender); + chatOverlay?.Show(); + }; } public MenuItem[] ContextMenuItems From 6ad7723d60700ee25c95113f6f806caaf29bdc67 Mon Sep 17 00:00:00 2001 From: Fyra Tedja Date: Sun, 17 Jul 2022 22:13:08 -0600 Subject: [PATCH 637/803] Make sure stats name are title-cased when score panel is contracted --- .../Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 7b8b4ce608..3e67868f34 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -214,7 +215,7 @@ namespace osu.Game.Screens.Ranking.Contracted { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Text = key, + Text = key.ToTitle(), Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) }, new OsuSpriteText From 6bfa5e53e0848e2d09543a75cbb2801f524c96d8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 07:21:10 +0300 Subject: [PATCH 638/803] Add property for whether mod uses default configuration --- osu.Game/Rulesets/Mods/Mod.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 17093e3033..d519256d23 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -128,6 +128,11 @@ namespace osu.Game.Rulesets.Mods .Cast() .ToList(); + /// + /// Whether all settings in this mod are set to their default state. + /// + protected virtual bool UsesDefaultConfiguration => Settings.All(s => s.IsDefault); + /// /// Creates a copy of this initialised to a default state. /// From 32ba58109b59518f784dfa1fc434f8ea44bab5bd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 07:22:25 +0300 Subject: [PATCH 639/803] Remove score multiplier on difficulty-increasing mods with customised settings --- osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs | 2 +- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 2 +- osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs | 2 +- osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs | 2 +- osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs index 4db0a53005..c58ce9b07d 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs @@ -9,6 +9,6 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModDoubleTime : ModDoubleTime { - public override double ScoreMultiplier => 1.06; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; } } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index e05932ca03..d166646eaf 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModFlashlight : ModFlashlight { - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableFloat SizeMultiplier { get; } = new BindableFloat diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index 7db9bf2dfd..39b992b3f5 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModHardRock : ModHardRock, IApplicableToBeatmapProcessor { - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor) { diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs index c02eedf936..b4fbc9d566 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Mods public class CatchModHidden : ModHidden, IApplicableToDrawableRuleset { public override string Description => @"Play with fading fruits."; - public override double ScoreMultiplier => 1.06; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; private const double fade_out_offset_multiplier = 0.6; private const double fade_out_duration_multiplier = 0.44; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs index 365d987794..1fd2227eb7 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModNightcore : ModNightcore { - public override double ScoreMultiplier => 1.06; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 199c735787..17a9a81de8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override IconUsage? Icon => FontAwesome.Solid.Adjust; public override ModType Type => ModType.DifficultyIncrease; - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight) }; private DrawableOsuBlinds blinds; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs index 2d19305509..b86efe84ee 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs @@ -9,6 +9,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModDoubleTime : ModDoubleTime { - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index c082805a0e..b72e6b4dcb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModFlashlight : ModFlashlight, IApplicableToDrawableHitObject { - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModBlinds)).ToArray(); private const double default_follow_delay = 120; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs index fdddfed4d5..1f25655c8c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModHardRock : ModHardRock, IApplicableToHitObject { - public override double ScoreMultiplier => 1.06; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModMirror)).ToArray(); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 11ceb0f710..253eaf473b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods public Bindable OnlyFadeApproachCircles { get; } = new BindableBool(); public override string Description => @"Play with no approach circles and fading circles/sliders."; - public override double ScoreMultiplier => 1.06; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) }; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs index e9be56fcc5..b1fe066a1e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModNightcore : ModNightcore { - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs index 01f1632ae2..b19c2eaccf 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs @@ -9,6 +9,6 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModDoubleTime : ModDoubleTime { - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 2f9cccfe86..fe02a6caf9 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModFlashlight : ModFlashlight { - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableFloat SizeMultiplier { get; } = new BindableFloat diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs index 7fcd925c04..7780936e7d 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModHardRock : ModHardRock { - public override double ScoreMultiplier => 1.06; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; /// /// Multiplier factor added to the scrolling speed. diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index e065bb43fd..fe3e5ca11c 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModHidden : ModHidden, IApplicableToDrawableRuleset { public override string Description => @"Beats fade out before you hit them!"; - public override double ScoreMultiplier => 1.06; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; /// /// How far away from the hit target should hitobjects start to fade out. diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs index f2a57ecf88..e02a16f62f 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModNightcore : ModNightcore { - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; } } From eddae7b14395485c8bd58d0f2a04c1b43c81ae39 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 07:38:56 +0300 Subject: [PATCH 640/803] Fix mod overlay and footer not updating multiplayer on settings change --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 7 +++++++ osu.Game/Screens/Select/FooterButtonMods.cs | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 04c424461e..ea152f58ad 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -222,6 +222,8 @@ namespace osu.Game.Overlays.Mods globalAvailableMods.BindTo(game.AvailableMods); } + private ModSettingChangeTracker? modSettingChangeTracker; + protected override void LoadComplete() { // this is called before base call so that the mod state is populated early, and the transition in `PopIn()` can play out properly. @@ -238,9 +240,14 @@ namespace osu.Game.Overlays.Mods SelectedMods.BindValueChanged(val => { + modSettingChangeTracker?.Dispose(); + updateMultiplier(); updateCustomisation(val); updateFromExternalSelection(); + + modSettingChangeTracker = new ModSettingChangeTracker(val.NewValue); + modSettingChangeTracker.SettingChanged += _ => updateMultiplier(); }, true); customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index c938f58984..1fb73c2333 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -9,9 +9,11 @@ using osu.Game.Screens.Play.HUD; using osu.Game.Rulesets.Mods; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.UserInterface; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; @@ -61,11 +63,22 @@ namespace osu.Game.Screens.Select Hotkey = GlobalAction.ToggleModSelection; } + [CanBeNull] + private ModSettingChangeTracker modSettingChangeTracker; + protected override void LoadComplete() { base.LoadComplete(); - Current.BindValueChanged(_ => updateMultiplierText(), true); + Current.BindValueChanged(mods => + { + modSettingChangeTracker?.Dispose(); + + updateMultiplierText(); + + modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); + modSettingChangeTracker.SettingChanged += _ => updateMultiplierText(); + }, true); } private void updateMultiplierText() => Schedule(() => From 0533249d11ac220e223369f774fafcabc3f30c51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 14:34:02 +0900 Subject: [PATCH 641/803] Update all `OnlineSpectatorCalls` to `InvokeAsync` --- osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs | 1 - osu.Game/Online/Spectator/OnlineSpectatorClient.cs | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 4e6ea997c1..c061398209 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -80,7 +80,6 @@ namespace osu.Game.Online.Multiplayer try { - // Importantly, use Invoke rather than Send to capture exceptions. return await connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoomWithPassword), roomId, password ?? string.Empty); } catch (HubException exception) diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index 1ed7819b05..2a05fb1bc0 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -56,7 +56,6 @@ namespace osu.Game.Online.Spectator try { - // Importantly, use Invoke rather than Send to capture exceptions. await connection.InvokeAsync(nameof(ISpectatorServer.BeginPlaySession), state); } catch (HubException exception) @@ -74,7 +73,7 @@ namespace osu.Game.Online.Spectator Debug.Assert(connection != null); - return connection.SendAsync(nameof(ISpectatorServer.SendFrameData), bundle); + return connection.InvokeAsync(nameof(ISpectatorServer.SendFrameData), bundle); } protected override Task EndPlayingInternal(SpectatorState state) @@ -84,7 +83,7 @@ namespace osu.Game.Online.Spectator Debug.Assert(connection != null); - return connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), state); + return connection.InvokeAsync(nameof(ISpectatorServer.EndPlaySession), state); } protected override Task WatchUserInternal(int userId) @@ -94,7 +93,7 @@ namespace osu.Game.Online.Spectator Debug.Assert(connection != null); - return connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); + return connection.InvokeAsync(nameof(ISpectatorServer.StartWatchingUser), userId); } protected override Task StopWatchingUserInternal(int userId) @@ -104,7 +103,7 @@ namespace osu.Game.Online.Spectator Debug.Assert(connection != null); - return connection.SendAsync(nameof(ISpectatorServer.EndWatchingUser), userId); + return connection.InvokeAsync(nameof(ISpectatorServer.EndWatchingUser), userId); } } } From 100c53f9ef24e2fd66743aa81dcc0624ae5f23df Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 08:40:34 +0300 Subject: [PATCH 642/803] `Country` -> `CountryCode` --- .../Visual/Online/TestSceneFriendDisplay.cs | 6 ++--- .../Online/TestSceneRankingsCountryFilter.cs | 6 ++--- .../Visual/Online/TestSceneRankingsHeader.cs | 6 ++--- .../Visual/Online/TestSceneRankingsOverlay.cs | 8 +++---- .../Visual/Online/TestSceneRankingsTables.cs | 10 ++++---- .../Visual/Online/TestSceneScoresContainer.cs | 12 +++++----- .../Visual/Online/TestSceneUserPanel.cs | 6 ++--- .../Online/TestSceneUserProfileOverlay.cs | 8 +++---- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 24 +++++++++---------- .../TestSceneUserTopScoreContainer.cs | 6 ++--- osu.Game.Tournament/Models/TournamentUser.cs | 4 ++-- osu.Game.Tournament/TournamentGameBase.cs | 4 ++-- .../API/Requests/GetUserRankingsRequest.cs | 10 ++++---- .../Online/Leaderboards/LeaderboardScore.cs | 2 +- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- .../BeatmapSet/Scores/TopScoreUserSection.cs | 2 +- .../Profile/Header/TopHeaderContainer.cs | 4 ++-- osu.Game/Overlays/Rankings/CountryFilter.cs | 8 +++---- osu.Game/Overlays/Rankings/CountryPill.cs | 10 ++++---- .../Rankings/RankingsOverlayHeader.cs | 2 +- .../Rankings/Tables/CountriesTable.cs | 10 ++++---- .../Overlays/Rankings/Tables/RankingsTable.cs | 4 ++-- .../Rankings/Tables/UserBasedTable.cs | 2 +- osu.Game/Overlays/RankingsOverlay.cs | 6 ++--- .../Participants/ParticipantPanel.cs | 2 +- osu.Game/Users/{Country.cs => CountryCode.cs} | 2 +- osu.Game/Users/CountryStatistics.cs | 2 +- osu.Game/Users/Drawables/DrawableFlag.cs | 10 ++++---- osu.Game/Users/Drawables/UpdateableFlag.cs | 16 ++++++------- osu.Game/Users/ExtendedUserPanel.cs | 2 +- 30 files changed, 98 insertions(+), 98 deletions(-) rename osu.Game/Users/{Country.cs => CountryCode.cs} (99%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs index 43d41e9fb9..5454c87dff 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Online Id = 3103765, IsOnline = true, Statistics = new UserStatistics { GlobalRank = 1111 }, - Country = Country.JP, + CountryCode = CountryCode.JP, CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" }, new APIUser @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online Id = 2, IsOnline = false, Statistics = new UserStatistics { GlobalRank = 2222 }, - Country = Country.AU, + CountryCode = CountryCode.AU, CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", IsSupporter = true, SupportLevel = 3, @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Online { Username = "Evast", Id = 8195163, - Country = Country.BY, + CountryCode = CountryCode.BY, CoverUrl = "https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg", IsOnline = false, LastVisit = DateTimeOffset.Now diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsCountryFilter.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsCountryFilter.cs index ce0ca0b6c9..6a39db4870 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsCountryFilter.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsCountryFilter.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Online public TestSceneRankingsCountryFilter() { - var countryBindable = new Bindable(); + var countryBindable = new Bindable(); AddRange(new Drawable[] { @@ -56,8 +56,8 @@ namespace osu.Game.Tests.Visual.Online } }); - const Country country = Country.BY; - const Country unknown_country = Country.CK; + const CountryCode country = CountryCode.BY; + const CountryCode unknown_country = CountryCode.CK; AddStep("Set country", () => countryBindable.Value = country); AddStep("Set default country", () => countryBindable.Value = default); diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs index 9aedfb0a14..c776cfe377 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Online public TestSceneRankingsHeader() { - var countryBindable = new Bindable(); + var countryBindable = new Bindable(); var ruleset = new Bindable(); var scope = new Bindable(); @@ -30,8 +30,8 @@ namespace osu.Game.Tests.Visual.Online Ruleset = { BindTarget = ruleset } }); - const Country country = Country.BY; - const Country unknown_country = Country.CK; + const CountryCode country = CountryCode.BY; + const CountryCode unknown_country = CountryCode.CK; AddStep("Set country", () => countryBindable.Value = country); AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs index ea7eb14c8e..7f4b1284a0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Online private TestRankingsOverlay rankingsOverlay; - private readonly Bindable countryBindable = new Bindable(); + private readonly Bindable countryBindable = new Bindable(); private readonly Bindable scope = new Bindable(); [SetUp] @@ -49,14 +49,14 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); AddAssert("Check country is default", () => countryBindable.Value == default); - AddStep("Set country", () => countryBindable.Value = Country.US); + AddStep("Set country", () => countryBindable.Value = CountryCode.US); AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance); } [Test] public void TestShowCountry() { - AddStep("Show US", () => rankingsOverlay.ShowCountry(Country.US)); + AddStep("Show US", () => rankingsOverlay.ShowCountry(CountryCode.US)); } private void loadRankingsOverlay() @@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual.Online private class TestRankingsOverlay : RankingsOverlay { - public new Bindable Country => base.Country; + public new Bindable Country => base.Country; } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs index f889e3f7dd..81b76d19ac 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Online { new CountryStatistics { - Country = Country.US, + Code = CountryCode.US, ActiveUsers = 2_972_623, PlayCount = 3_086_515_743, RankedScore = 449_407_643_332_546, @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.Online }, new CountryStatistics { - Country = Country.RU, + Code = CountryCode.RU, ActiveUsers = 1_609_989, PlayCount = 1_637_052_841, RankedScore = 221_660_827_473_004, @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Online User = new APIUser { Username = "first active user", - Country = Country.JP, + CountryCode = CountryCode.JP, Active = true, }, Accuracy = 0.9972, @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.Online User = new APIUser { Username = "inactive user", - Country = Country.AU, + CountryCode = CountryCode.AU, Active = false, }, Accuracy = 0.9831, @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.Online User = new APIUser { Username = "second active user", - Country = Country.PL, + CountryCode = CountryCode.PL, Active = true, }, Accuracy = 0.9584, diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 6ac3d5cb86..beca3a8700 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 6602580, Username = @"waaiiru", - Country = Country.ES, + CountryCode = CountryCode.ES, }, Mods = new[] { @@ -180,7 +180,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 4608074, Username = @"Skycries", - Country = Country.BR, + CountryCode = CountryCode.BR, }, Mods = new[] { @@ -202,7 +202,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 1014222, Username = @"eLy", - Country = Country.JP, + CountryCode = CountryCode.JP, }, Mods = new[] { @@ -223,7 +223,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 1541390, Username = @"Toukai", - Country = Country.CA, + CountryCode = CountryCode.CA, }, Mods = new[] { @@ -243,7 +243,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 7151382, Username = @"Mayuri Hana", - Country = Country.TH, + CountryCode = CountryCode.TH, }, Rank = ScoreRank.D, PP = 160, @@ -282,7 +282,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 7151382, Username = @"Mayuri Hana", - Country = Country.TH, + CountryCode = CountryCode.TH, }, Rank = ScoreRank.D, PP = 160, diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index addcf799e3..2a70fd7df3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"flyte", Id = 3103765, - Country = Country.JP, + CountryCode = CountryCode.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg", Status = { Value = new UserStatusOnline() } }) { Width = 300 }, @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"peppy", Id = 2, - Country = Country.AU, + CountryCode = CountryCode.AU, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", IsSupporter = true, SupportLevel = 3, @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"Evast", Id = 8195163, - Country = Country.BY, + CountryCode = CountryCode.BY, CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg", IsOnline = false, LastVisit = DateTimeOffset.Now diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index dd6b022624..c2766e2142 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"Somebody", Id = 1, - Country = Country.XX, + CountryCode = CountryCode.XX, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", JoinDate = DateTimeOffset.Now.AddDays(-1), LastVisit = DateTimeOffset.Now, @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Online Username = @"peppy", Id = 2, IsSupporter = true, - Country = Country.AU, + CountryCode = CountryCode.AU, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" })); @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"flyte", Id = 3103765, - Country = Country.JP, + CountryCode = CountryCode.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" })); @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Online Username = @"BanchoBot", Id = 3, IsBot = true, - Country = Country.SH, + CountryCode = CountryCode.SH, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c4.jpg" })); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 48fdf671f1..abcb888cd4 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -140,7 +140,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6602580, Username = @"waaiiru", - Country = Country.ES, + CountryCode = CountryCode.ES, }, }); } @@ -160,7 +160,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6602580, Username = @"waaiiru", - Country = Country.ES, + CountryCode = CountryCode.ES, } }); } @@ -217,7 +217,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6602580, Username = @"waaiiru", - Country = Country.ES, + CountryCode = CountryCode.ES, }, }, new ScoreInfo @@ -234,7 +234,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 4608074, Username = @"Skycries", - Country = Country.BR, + CountryCode = CountryCode.BR, }, }, new ScoreInfo @@ -252,7 +252,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 1014222, Username = @"eLy", - Country = Country.JP, + CountryCode = CountryCode.JP, }, }, new ScoreInfo @@ -270,7 +270,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 1541390, Username = @"Toukai", - Country = Country.CA, + CountryCode = CountryCode.CA, }, }, new ScoreInfo @@ -288,7 +288,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 2243452, Username = @"Satoruu", - Country = Country.VE, + CountryCode = CountryCode.VE, }, }, new ScoreInfo @@ -306,7 +306,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 2705430, Username = @"Mooha", - Country = Country.FR, + CountryCode = CountryCode.FR, }, }, new ScoreInfo @@ -324,7 +324,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 7151382, Username = @"Mayuri Hana", - Country = Country.TH, + CountryCode = CountryCode.TH, }, }, new ScoreInfo @@ -342,7 +342,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 2051389, Username = @"FunOrange", - Country = Country.CA, + CountryCode = CountryCode.CA, }, }, new ScoreInfo @@ -360,7 +360,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6169483, Username = @"-Hebel-", - Country = Country.MX, + CountryCode = CountryCode.MX, }, }, new ScoreInfo @@ -378,7 +378,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6702666, Username = @"prhtnsm", - Country = Country.DE, + CountryCode = CountryCode.DE, }, }, }; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs index 5356e74eae..39fd9fda2b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6602580, Username = @"waaiiru", - Country = Country.ES, + CountryCode = CountryCode.ES, }, }, new ScoreInfo @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 4608074, Username = @"Skycries", - Country = Country.BR, + CountryCode = CountryCode.BR, }, }, new ScoreInfo @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 1541390, Username = @"Toukai", - Country = Country.CA, + CountryCode = CountryCode.CA, }, } }; diff --git a/osu.Game.Tournament/Models/TournamentUser.cs b/osu.Game.Tournament/Models/TournamentUser.cs index 7faf6d1798..78ca014860 100644 --- a/osu.Game.Tournament/Models/TournamentUser.cs +++ b/osu.Game.Tournament/Models/TournamentUser.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tournament.Models /// The player's country. /// [JsonProperty("country_code")] - public Country Country { get; set; } + public CountryCode CountryCode { get; set; } /// /// The player's global rank, or null if not available. @@ -41,7 +41,7 @@ namespace osu.Game.Tournament.Models { Id = OnlineID, Username = Username, - Country = Country, + CountryCode = CountryCode, CoverUrl = CoverUrl, }; diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 853ccec83c..afeb58fde8 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -187,7 +187,7 @@ namespace osu.Game.Tournament var playersRequiringPopulation = ladder.Teams .SelectMany(t => t.Players) .Where(p => string.IsNullOrEmpty(p.Username) - || p.Country == default + || p.CountryCode == default || p.Rank == null).ToList(); if (playersRequiringPopulation.Count == 0) @@ -290,7 +290,7 @@ namespace osu.Game.Tournament user.Username = res.Username; user.CoverUrl = res.CoverUrl; - user.Country = res.Country; + user.CountryCode = res.CountryCode; user.Rank = res.Statistics?.GlobalRank; success?.Invoke(); diff --git a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs index 75088675bd..c55e7d65b6 100644 --- a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs @@ -13,21 +13,21 @@ namespace osu.Game.Online.API.Requests { public readonly UserRankingsType Type; - private readonly Country country; + private readonly CountryCode countryCode; - public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, Country country = default) + public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, CountryCode countryCode = default) : base(ruleset, page) { Type = type; - this.country = country; + this.countryCode = countryCode; } protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); - if (country != default) - req.AddParameter("country", country.ToString()); + if (countryCode != default) + req.AddParameter("country", countryCode.ToString()); return req; } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 62827f50aa..a7b6bd044d 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -181,7 +181,7 @@ namespace osu.Game.Online.Leaderboards Masking = true, Children = new Drawable[] { - new UpdateableFlag(user.Country) + new UpdateableFlag(user.CountryCode) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 669b701d1d..6acc9bf002 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -165,7 +165,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Font = OsuFont.GetFont(size: text_size), Colour = score.Accuracy == 1 ? highAccuracyColour : Color4.White }, - new UpdateableFlag(score.User.Country) + new UpdateableFlag(score.User.CountryCode) { Size = new Vector2(19, 14), ShowPlaceholderOnUnknown = false, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index 85c8f92a76..2eaa03a05d 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -141,7 +141,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores set { avatar.User = value.User; - flag.Country = value.User.Country; + flag.CountryCode = value.User.CountryCode; achievedOn.Date = value.Date; usernameText.Clear(); diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index a213f99701..7e079c8341 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -175,8 +175,8 @@ namespace osu.Game.Overlays.Profile.Header avatar.User = user; usernameText.Text = user?.Username ?? string.Empty; openUserExternally.Link = $@"{api.WebsiteRootUrl}/users/{user?.Id ?? 0}"; - userFlag.Country = user?.Country ?? default; - userCountryText.Text = (user?.Country ?? default).GetDescription(); + userFlag.CountryCode = user?.CountryCode ?? default; + userCountryText.Text = (user?.CountryCode ?? default).GetDescription(); supporterTag.SupportLevel = user?.SupportLevel ?? 0; titleText.Text = user?.Title ?? string.Empty; titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff"); diff --git a/osu.Game/Overlays/Rankings/CountryFilter.cs b/osu.Game/Overlays/Rankings/CountryFilter.cs index 469d92a771..3ad677e976 100644 --- a/osu.Game/Overlays/Rankings/CountryFilter.cs +++ b/osu.Game/Overlays/Rankings/CountryFilter.cs @@ -16,14 +16,14 @@ using osuTK; namespace osu.Game.Overlays.Rankings { - public class CountryFilter : CompositeDrawable, IHasCurrentValue + public class CountryFilter : CompositeDrawable, IHasCurrentValue { private const int duration = 200; private const int height = 70; - private readonly BindableWithCurrent current = new BindableWithCurrent(); + private readonly BindableWithCurrent current = new BindableWithCurrent(); - public Bindable Current + public Bindable Current { get => current.Current; set => current.Current = value; @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Rankings Current.BindValueChanged(onCountryChanged, true); } - private void onCountryChanged(ValueChangedEvent country) + private void onCountryChanged(ValueChangedEvent country) { if (country.NewValue == default) { diff --git a/osu.Game/Overlays/Rankings/CountryPill.cs b/osu.Game/Overlays/Rankings/CountryPill.cs index 96d677611e..610efbcc60 100644 --- a/osu.Game/Overlays/Rankings/CountryPill.cs +++ b/osu.Game/Overlays/Rankings/CountryPill.cs @@ -22,13 +22,13 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Rankings { - public class CountryPill : CompositeDrawable, IHasCurrentValue + public class CountryPill : CompositeDrawable, IHasCurrentValue { private const int duration = 200; - private readonly BindableWithCurrent current = new BindableWithCurrent(); + private readonly BindableWithCurrent current = new BindableWithCurrent(); - public Bindable Current + public Bindable Current { get => current.Current; set => current.Current = value; @@ -131,12 +131,12 @@ namespace osu.Game.Overlays.Rankings this.FadeOut(duration, Easing.OutQuint); } - private void onCountryChanged(ValueChangedEvent country) + private void onCountryChanged(ValueChangedEvent country) { if (country.NewValue == default) return; - flag.Country = country.NewValue; + flag.CountryCode = country.NewValue; countryName.Text = country.NewValue.GetDescription(); } diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index 9b0b75f663..936545bd49 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Rankings { public Bindable Ruleset => rulesetSelector.Current; - public Bindable Country => countryFilter.Current; + public Bindable Country => countryFilter.Current; private OverlayRulesetSelector rulesetSelector; private CountryFilter countryFilter; diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 27d4a6b6d3..d00a3680d8 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -34,9 +34,9 @@ namespace osu.Game.Overlays.Rankings.Tables new RankingsTableColumn(RankingsStrings.StatAveragePerformance, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), }; - protected override Country GetCountry(CountryStatistics item) => item.Country; + protected override CountryCode GetCountryCode(CountryStatistics item) => item.Code; - protected override Drawable CreateFlagContent(CountryStatistics item) => new CountryName(item.Country); + protected override Drawable CreateFlagContent(CountryStatistics item) => new CountryName(item.Code); protected override Drawable[] CreateAdditionalContent(CountryStatistics item) => new Drawable[] { @@ -71,15 +71,15 @@ namespace osu.Game.Overlays.Rankings.Tables [Resolved(canBeNull: true)] private RankingsOverlay rankings { get; set; } - public CountryName(Country country) + public CountryName(CountryCode countryCode) : base(t => t.Font = OsuFont.GetFont(size: 12)) { AutoSizeAxes = Axes.X; RelativeSizeAxes = Axes.Y; TextAnchor = Anchor.CentreLeft; - if (country != default) - AddLink(country.GetDescription(), () => rankings?.ShowCountry(country)); + if (countryCode != default) + AddLink(countryCode.GetDescription(), () => rankings?.ShowCountry(countryCode)); } } } diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 1e33b96ef1..073bf86a7a 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Rankings.Tables protected sealed override Drawable CreateHeader(int index, TableColumn column) => (column as RankingsTableColumn)?.CreateHeaderText() ?? new HeaderText(column?.Header ?? default, false); - protected abstract Country GetCountry(TModel item); + protected abstract CountryCode GetCountryCode(TModel item); protected abstract Drawable CreateFlagContent(TModel item); @@ -97,7 +97,7 @@ namespace osu.Game.Overlays.Rankings.Tables Margin = new MarginPadding { Bottom = row_spacing }, Children = new[] { - new UpdateableFlag(GetCountry(item)) + new UpdateableFlag(GetCountryCode(item)) { Size = new Vector2(28, 20), ShowPlaceholderOnUnknown = false, diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs index 2edd1b21ba..48185e6083 100644 --- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Rankings.Tables .Concat(GradeColumns.Select(grade => new GradeTableColumn(grade, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)))) .ToArray(); - protected sealed override Country GetCountry(UserStatistics item) => item.User.Country; + protected sealed override CountryCode GetCountryCode(UserStatistics item) => item.User.CountryCode; protected sealed override Drawable CreateFlagContent(UserStatistics item) { diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 9894df1c9d..586b883604 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays { public class RankingsOverlay : TabbableOnlineOverlay { - protected Bindable Country => Header.Country; + protected Bindable Country => Header.Country; private APIRequest lastRequest; @@ -85,7 +85,7 @@ namespace osu.Game.Overlays protected override RankingsOverlayHeader CreateHeader() => new RankingsOverlayHeader(); - public void ShowCountry(Country requested) + public void ShowCountry(CountryCode requested) { if (requested == default) return; @@ -128,7 +128,7 @@ namespace osu.Game.Overlays switch (Header.Current.Value) { case RankingsScope.Performance: - return new GetUserRankingsRequest(ruleset.Value, country: Country.Value); + return new GetUserRankingsRequest(ruleset.Value, countryCode: Country.Value); case RankingsScope.Country: return new GetCountryRankingsRequest(ruleset.Value); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 652a832689..592be2ebac 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -128,7 +128,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Size = new Vector2(28, 20), - Country = user?.Country ?? default + CountryCode = user?.CountryCode ?? default }, new OsuSpriteText { diff --git a/osu.Game/Users/Country.cs b/osu.Game/Users/CountryCode.cs similarity index 99% rename from osu.Game/Users/Country.cs rename to osu.Game/Users/CountryCode.cs index 2070bcab39..11e9806ec5 100644 --- a/osu.Game/Users/Country.cs +++ b/osu.Game/Users/CountryCode.cs @@ -10,7 +10,7 @@ namespace osu.Game.Users { [JsonConverter(typeof(StringEnumConverter))] [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] - public enum Country + public enum CountryCode { [Description("Unknown")] XX = 0, diff --git a/osu.Game/Users/CountryStatistics.cs b/osu.Game/Users/CountryStatistics.cs index eaee13a37b..03d455bc04 100644 --- a/osu.Game/Users/CountryStatistics.cs +++ b/osu.Game/Users/CountryStatistics.cs @@ -10,7 +10,7 @@ namespace osu.Game.Users public class CountryStatistics { [JsonProperty(@"code")] - public Country Country; + public CountryCode Code; [JsonProperty(@"active_users")] public long ActiveUsers; diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index 84b56a8d16..f7718c4f63 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -15,13 +15,13 @@ namespace osu.Game.Users.Drawables { public class DrawableFlag : Sprite, IHasTooltip { - private readonly Country country; + private readonly CountryCode countryCode; - public LocalisableString TooltipText => country == default ? string.Empty : country.GetDescription(); + public LocalisableString TooltipText => countryCode == default ? string.Empty : countryCode.GetDescription(); - public DrawableFlag(Country country) + public DrawableFlag(CountryCode countryCode) { - this.country = country; + this.countryCode = countryCode; } [BackgroundDependencyLoader] @@ -30,7 +30,7 @@ namespace osu.Game.Users.Drawables if (ts == null) throw new ArgumentNullException(nameof(ts)); - string textureName = country == default ? "__" : country.ToString(); + string textureName = countryCode == default ? "__" : countryCode.ToString(); Texture = ts.Get($@"Flags/{textureName}") ?? ts.Get(@"Flags/__"); } } diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index 88c08d4bb1..808bfad565 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -13,9 +13,9 @@ using osu.Game.Overlays; namespace osu.Game.Users.Drawables { - public class UpdateableFlag : ModelBackedDrawable + public class UpdateableFlag : ModelBackedDrawable { - public Country Country + public CountryCode CountryCode { get => Model; set => Model = value; @@ -32,14 +32,14 @@ namespace osu.Game.Users.Drawables /// public Action Action; - public UpdateableFlag(Country country = default) + public UpdateableFlag(CountryCode countryCode = default) { - Country = country; + CountryCode = countryCode; } - protected override Drawable CreateDrawable(Country country) + protected override Drawable CreateDrawable(CountryCode countryCode) { - if (country == default && !ShowPlaceholderOnUnknown) + if (countryCode == default && !ShowPlaceholderOnUnknown) return null; return new Container @@ -47,7 +47,7 @@ namespace osu.Game.Users.Drawables RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new DrawableFlag(country) + new DrawableFlag(countryCode) { RelativeSizeAxes = Axes.Both }, @@ -62,7 +62,7 @@ namespace osu.Game.Users.Drawables protected override bool OnClick(ClickEvent e) { Action?.Invoke(); - rankingsOverlay?.ShowCountry(Country); + rankingsOverlay?.ShowCountry(CountryCode); return true; } } diff --git a/osu.Game/Users/ExtendedUserPanel.cs b/osu.Game/Users/ExtendedUserPanel.cs index 7d475c545a..a4cba8b920 100644 --- a/osu.Game/Users/ExtendedUserPanel.cs +++ b/osu.Game/Users/ExtendedUserPanel.cs @@ -53,7 +53,7 @@ namespace osu.Game.Users protected UpdateableAvatar CreateAvatar() => new UpdateableAvatar(User, false); - protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.Country) + protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.CountryCode) { Size = new Vector2(36, 26), Action = Action, From ef6e16b1cb668451d03962ae42b3d3d17d745efc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 08:40:43 +0300 Subject: [PATCH 643/803] `UserCountry` -> `Country` --- osu.Game/Online/API/Requests/Responses/APIUser.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index e2e1288b8c..6901b4595e 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -34,20 +34,20 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"previous_usernames")] public string[] PreviousUsernames; - private Country? country; + private CountryCode? countryCode; - public Country Country + public CountryCode CountryCode { - get => country ??= (Enum.TryParse(userCountry?.Code, out Country result) ? result : default); - set => country = value; + get => countryCode ??= (Enum.TryParse(country?.Code, out CountryCode result) ? result : default); + set => countryCode = value; } #pragma warning disable 649 [CanBeNull] [JsonProperty(@"country")] - private UserCountry userCountry; + private Country country; - private class UserCountry + private class Country { [JsonProperty(@"code")] public string Code; From 566bad0b5f4a73291628d07ebc9c2ac067b48eab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 14:42:41 +0900 Subject: [PATCH 644/803] Make `SoloScoreInfo.EndedAt` non-null Seems to already be the case for databased scores. Will be assured by https://github.com/ppy/osu-web/issues/9116. I've updated the `osu-score-statistics-processor` with this consideration. --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index cf7aa599f3..d46869c9f5 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -54,7 +54,7 @@ namespace osu.Game.Online.API.Requests.Responses public DateTimeOffset? StartedAt { get; set; } [JsonProperty("ended_at")] - public DateTimeOffset? EndedAt { get; set; } + public DateTimeOffset EndedAt { get; set; } [JsonProperty("mods")] public APIMod[] Mods { get; set; } = Array.Empty(); @@ -128,7 +128,7 @@ namespace osu.Game.Online.API.Requests.Responses MaxCombo = MaxCombo, Rank = Rank, Statistics = Statistics, - Date = EndedAt ?? DateTimeOffset.Now, + Date = EndedAt, Hash = HasReplay ? "online" : string.Empty, // TODO: temporary? Mods = mods, PP = PP, From 05d692bd5539e6d7ab5da778cb48a97ae7908c3e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 08:43:41 +0300 Subject: [PATCH 645/803] Move `Country` to end of class --- osu.Game/Online/API/Requests/Responses/APIUser.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 6901b4595e..5f843e9a7b 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -46,12 +46,6 @@ namespace osu.Game.Online.API.Requests.Responses [CanBeNull] [JsonProperty(@"country")] private Country country; - - private class Country - { - [JsonProperty(@"code")] - public string Code; - } #pragma warning restore 649 public readonly Bindable Status = new Bindable(); @@ -273,5 +267,13 @@ namespace osu.Game.Online.API.Requests.Responses public int OnlineID => Id; public bool Equals(APIUser other) => this.MatchesOnlineID(other); + +#pragma warning disable 649 + private class Country + { + [JsonProperty(@"code")] + public string Code; + } +#pragma warning restore 649 } } From cf99849478b644360afacb0dcf87bb380ae204a7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 08:45:54 +0300 Subject: [PATCH 646/803] `CountryCode.XX` -> `CountryCode.Unknown` --- osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs | 2 +- osu.Game/Users/CountryCode.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index c2766e2142..caa2d2571d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"Somebody", Id = 1, - CountryCode = CountryCode.XX, + CountryCode = CountryCode.Unknown, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", JoinDate = DateTimeOffset.Now.AddDays(-1), LastVisit = DateTimeOffset.Now, diff --git a/osu.Game/Users/CountryCode.cs b/osu.Game/Users/CountryCode.cs index 11e9806ec5..775de2bcf5 100644 --- a/osu.Game/Users/CountryCode.cs +++ b/osu.Game/Users/CountryCode.cs @@ -13,7 +13,7 @@ namespace osu.Game.Users public enum CountryCode { [Description("Unknown")] - XX = 0, + Unknown = 0, [Description("Bangladesh")] BD, From 018da74fe804928e83e249919ef3ed998e0d4854 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 08:54:35 +0300 Subject: [PATCH 647/803] Replace `default` with `CountryCode.Unknown` --- osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs | 2 +- osu.Game.Tournament/TournamentGameBase.cs | 3 ++- osu.Game/Online/API/Requests/GetUserRankingsRequest.cs | 4 ++-- osu.Game/Overlays/Rankings/CountryFilter.cs | 2 +- osu.Game/Overlays/Rankings/CountryPill.cs | 2 +- osu.Game/Overlays/Rankings/Tables/CountriesTable.cs | 2 +- osu.Game/Users/Drawables/DrawableFlag.cs | 4 ++-- osu.Game/Users/Drawables/UpdateableFlag.cs | 4 ++-- 8 files changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs index 7f4b1284a0..5476049882 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Online public void TestFlagScopeDependency() { AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); - AddAssert("Check country is default", () => countryBindable.Value == default); + AddAssert("Check country is default", () => countryBindable.IsDefault); AddStep("Set country", () => countryBindable.Value = CountryCode.US); AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance); } diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index afeb58fde8..063b62bf08 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -21,6 +21,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Tournament.IO; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; +using osu.Game.Users; using osuTK.Input; namespace osu.Game.Tournament @@ -187,7 +188,7 @@ namespace osu.Game.Tournament var playersRequiringPopulation = ladder.Teams .SelectMany(t => t.Players) .Where(p => string.IsNullOrEmpty(p.Username) - || p.CountryCode == default + || p.CountryCode == CountryCode.Unknown || p.Rank == null).ToList(); if (playersRequiringPopulation.Count == 0) diff --git a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs index c55e7d65b6..c27a83b695 100644 --- a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs @@ -15,7 +15,7 @@ namespace osu.Game.Online.API.Requests private readonly CountryCode countryCode; - public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, CountryCode countryCode = default) + public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, CountryCode countryCode = CountryCode.Unknown) : base(ruleset, page) { Type = type; @@ -26,7 +26,7 @@ namespace osu.Game.Online.API.Requests { var req = base.CreateWebRequest(); - if (countryCode != default) + if (countryCode != CountryCode.Unknown) req.AddParameter("country", countryCode.ToString()); return req; diff --git a/osu.Game/Overlays/Rankings/CountryFilter.cs b/osu.Game/Overlays/Rankings/CountryFilter.cs index 3ad677e976..9376bafdff 100644 --- a/osu.Game/Overlays/Rankings/CountryFilter.cs +++ b/osu.Game/Overlays/Rankings/CountryFilter.cs @@ -91,7 +91,7 @@ namespace osu.Game.Overlays.Rankings private void onCountryChanged(ValueChangedEvent country) { - if (country.NewValue == default) + if (Current.Value == CountryCode.Unknown) { countryPill.Collapse(); this.ResizeHeightTo(0, duration, Easing.OutQuint); diff --git a/osu.Game/Overlays/Rankings/CountryPill.cs b/osu.Game/Overlays/Rankings/CountryPill.cs index 610efbcc60..ee4c4f08af 100644 --- a/osu.Game/Overlays/Rankings/CountryPill.cs +++ b/osu.Game/Overlays/Rankings/CountryPill.cs @@ -133,7 +133,7 @@ namespace osu.Game.Overlays.Rankings private void onCountryChanged(ValueChangedEvent country) { - if (country.NewValue == default) + if (Current.Value == CountryCode.Unknown) return; flag.CountryCode = country.NewValue; diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index d00a3680d8..a6f0c7a123 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -78,7 +78,7 @@ namespace osu.Game.Overlays.Rankings.Tables RelativeSizeAxes = Axes.Y; TextAnchor = Anchor.CentreLeft; - if (countryCode != default) + if (countryCode != CountryCode.Unknown) AddLink(countryCode.GetDescription(), () => rankings?.ShowCountry(countryCode)); } } diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index f7718c4f63..253835b415 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -17,7 +17,7 @@ namespace osu.Game.Users.Drawables { private readonly CountryCode countryCode; - public LocalisableString TooltipText => countryCode == default ? string.Empty : countryCode.GetDescription(); + public LocalisableString TooltipText => countryCode == CountryCode.Unknown ? string.Empty : countryCode.GetDescription(); public DrawableFlag(CountryCode countryCode) { @@ -30,7 +30,7 @@ namespace osu.Game.Users.Drawables if (ts == null) throw new ArgumentNullException(nameof(ts)); - string textureName = countryCode == default ? "__" : countryCode.ToString(); + string textureName = countryCode == CountryCode.Unknown ? "__" : countryCode.ToString(); Texture = ts.Get($@"Flags/{textureName}") ?? ts.Get(@"Flags/__"); } } diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index 808bfad565..9b101131b3 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -32,14 +32,14 @@ namespace osu.Game.Users.Drawables /// public Action Action; - public UpdateableFlag(CountryCode countryCode = default) + public UpdateableFlag(CountryCode countryCode = CountryCode.Unknown) { CountryCode = countryCode; } protected override Drawable CreateDrawable(CountryCode countryCode) { - if (countryCode == default && !ShowPlaceholderOnUnknown) + if (countryCode == CountryCode.Unknown && !ShowPlaceholderOnUnknown) return null; return new Container From 21bf7ee448bcbc328707a049292630dcfde1c731 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 15:27:55 +0900 Subject: [PATCH 648/803] Turn on nullability in `ParticipantPanel` --- osu.Game/Online/API/APIMod.cs | 3 ++- .../Participants/ParticipantPanel.cs | 22 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 8346300767..3bd0c91f8e 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Humanizer; using MessagePack; @@ -48,7 +49,7 @@ namespace osu.Game.Online.API } } - public Mod ToMod(Ruleset ruleset) + public Mod ToMod([NotNull] Ruleset ruleset) { Mod resultMod = ruleset.CreateModFromAcronym(Acronym); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index f632951f41..539502e631 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -35,18 +33,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants public readonly MultiplayerRoomUser User; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; [Resolved] - private IRulesetStore rulesets { get; set; } + private IRulesetStore rulesets { get; set; } = null!; - private SpriteIcon crown; + private SpriteIcon crown = null!; - private OsuSpriteText userRankText; - private ModDisplay userModsDisplay; - private StateDisplay userStateDisplay; + private OsuSpriteText userRankText = null!; + private ModDisplay userModsDisplay = null!; + private StateDisplay userStateDisplay = null!; - private IconButton kickButton; + private IconButton kickButton = null!; public ParticipantPanel(MultiplayerRoomUser user) { @@ -135,7 +133,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 18), - Text = user?.Username + Text = user?.Username ?? string.Empty }, userRankText = new OsuSpriteText { @@ -188,7 +186,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; var currentItem = Playlist.GetCurrentItem(); - var ruleset = currentItem != null ? rulesets.GetRuleset(currentItem.RulesetID)?.CreateInstance() : null; + Ruleset? ruleset = currentItem != null ? rulesets.GetRuleset(currentItem.RulesetID)?.CreateInstance() : null; int? currentModeRank = ruleset != null ? User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank : null; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; @@ -208,7 +206,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Schedule(() => userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList()); } - public MenuItem[] ContextMenuItems + public MenuItem[]? ContextMenuItems { get { From a036632c8be8c61ff496e732d0c8bd2ef1667bde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 15:30:21 +0900 Subject: [PATCH 649/803] Fix potential crash when attempting to create mod display using null ruleset --- .../Multiplayer/Participants/ParticipantPanel.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 539502e631..e3a20cd652 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.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.Allocation; @@ -20,6 +21,7 @@ using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play.HUD; using osu.Game.Users; using osu.Game.Users.Drawables; @@ -203,7 +205,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants // If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187 // This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix. - Schedule(() => userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList()); + Schedule(() => + { + userModsDisplay.Current.Value = ruleset != null ? User.Mods.Select(m => m.ToMod(ruleset)).ToList() : Array.Empty(); + }); } public MenuItem[]? ContextMenuItems From e4d11febc538d046d8194f9853127af7d820af74 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 09:47:40 +0300 Subject: [PATCH 650/803] Remove no longer necessary fallback --- .../Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index c5e2b6df0c..5d8f8c8326 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -98,7 +98,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), Colour = colours.Yellow }, - new DrawableDate(Score.EndedAt ?? DateTimeOffset.Now, 12) + new DrawableDate(Score.EndedAt, 12) { Colour = colourProvider.Foreground1 } From e86a35fe334fecd3eb1784284b0b73ab380c7993 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 10:09:14 +0300 Subject: [PATCH 651/803] Fix NRE on footer button mods --- osu.Game/Screens/Select/FooterButtonMods.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 1fb73c2333..21f81995be 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -76,8 +76,11 @@ namespace osu.Game.Screens.Select updateMultiplierText(); - modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); - modSettingChangeTracker.SettingChanged += _ => updateMultiplierText(); + if (mods.NewValue != null) + { + modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); + modSettingChangeTracker.SettingChanged += _ => updateMultiplierText(); + } }, true); } From 51f91fe62e37ebc2ebcb28af62a8b7e82569b979 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 16:16:59 +0900 Subject: [PATCH 652/803] Update naming --- osu.Game/Database/RealmAccess.cs | 2 +- osu.Game/Models/RealmUser.cs | 8 ++++---- osu.Game/Rulesets/Mods/ICreateReplayData.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 6 +++--- osu.Game/Users/IUser.cs | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 0c44436ec8..c4d65f4f10 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -60,7 +60,7 @@ namespace osu.Game.Database /// 14 2022-03-01 Added BeatmapUserSettings to BeatmapInfo. /// 15 2022-07-13 Added LastPlayed to BeatmapInfo. /// 16 2022-07-15 Removed HasReplay from ScoreInfo. - /// 17 2022-07-16 Added Country to RealmUser. + /// 17 2022-07-16 Added CountryCode to RealmUser. /// private const int schema_version = 17; diff --git a/osu.Game/Models/RealmUser.cs b/osu.Game/Models/RealmUser.cs index 1668739bb5..4b9269a922 100644 --- a/osu.Game/Models/RealmUser.cs +++ b/osu.Game/Models/RealmUser.cs @@ -16,14 +16,14 @@ namespace osu.Game.Models public string Username { get; set; } = string.Empty; [Ignored] - public Country Country + public CountryCode CountryCode { - get => Enum.TryParse(CountryString, out Country country) ? country : default; + get => Enum.TryParse(CountryString, out CountryCode country) ? country : default; set => CountryString = value.ToString(); } - [MapTo(nameof(Country))] - public string CountryString { get; set; } = default(Country).ToString(); + [MapTo(nameof(CountryCode))] + public string CountryString { get; set; } = default(CountryCode).ToString(); public bool IsBot => false; diff --git a/osu.Game/Rulesets/Mods/ICreateReplayData.cs b/osu.Game/Rulesets/Mods/ICreateReplayData.cs index 6c195f623c..3ed5c2b7f8 100644 --- a/osu.Game/Rulesets/Mods/ICreateReplayData.cs +++ b/osu.Game/Rulesets/Mods/ICreateReplayData.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Mods public class ModCreatedUser : IUser { public int OnlineID => APIUser.SYSTEM_USER_ID; - public Country Country => default; + public CountryCode CountryCode => default; public bool IsBot => true; public string Username { get; set; } = string.Empty; diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index f5942e4639..d32d611a27 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -87,7 +87,7 @@ namespace osu.Game.Scoring { Id = RealmUser.OnlineID, Username = RealmUser.Username, - Country = RealmUser.Country, + CountryCode = RealmUser.CountryCode, }; set { @@ -97,7 +97,7 @@ namespace osu.Game.Scoring { OnlineID = user.OnlineID, Username = user.Username, - Country = user.Country, + CountryCode = user.CountryCode, }; } } @@ -137,7 +137,7 @@ namespace osu.Game.Scoring { OnlineID = RealmUser.OnlineID, Username = RealmUser.Username, - Country = RealmUser.Country, + CountryCode = RealmUser.CountryCode, }; return clone; diff --git a/osu.Game/Users/IUser.cs b/osu.Game/Users/IUser.cs index a520660c4d..b7f545f68b 100644 --- a/osu.Game/Users/IUser.cs +++ b/osu.Game/Users/IUser.cs @@ -10,7 +10,7 @@ namespace osu.Game.Users { string Username { get; } - Country Country { get; } + CountryCode CountryCode { get; } bool IsBot { get; } From 1e151baae8cbf2e0c6dc60e20aa8dd90658a290f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 16:24:08 +0900 Subject: [PATCH 653/803] Use `Unknown` instead of `default` --- osu.Game/Models/RealmUser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Models/RealmUser.cs b/osu.Game/Models/RealmUser.cs index 4b9269a922..e20ffc0808 100644 --- a/osu.Game/Models/RealmUser.cs +++ b/osu.Game/Models/RealmUser.cs @@ -18,7 +18,7 @@ namespace osu.Game.Models [Ignored] public CountryCode CountryCode { - get => Enum.TryParse(CountryString, out CountryCode country) ? country : default; + get => Enum.TryParse(CountryString, out CountryCode country) ? country : CountryCode.Unknown; set => CountryString = value.ToString(); } From caa44ce01e6ac413a0366b0ab22e1cce363de791 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 16:40:11 +0900 Subject: [PATCH 654/803] Update naming --- osu.Game.Tournament/CountryExtensions.cs | 502 +++++++++--------- .../Screens/Editors/TeamEditorScreen.cs | 2 +- 2 files changed, 252 insertions(+), 252 deletions(-) diff --git a/osu.Game.Tournament/CountryExtensions.cs b/osu.Game.Tournament/CountryExtensions.cs index 180c7a96af..f2a583c8a5 100644 --- a/osu.Game.Tournament/CountryExtensions.cs +++ b/osu.Game.Tournament/CountryExtensions.cs @@ -8,758 +8,758 @@ namespace osu.Game.Tournament { public static class CountryExtensions { - public static string GetAcronym(this Country country) + public static string GetAcronym(this CountryCode country) { switch (country) { - case Country.BD: + case CountryCode.BD: return "BGD"; - case Country.BE: + case CountryCode.BE: return "BEL"; - case Country.BF: + case CountryCode.BF: return "BFA"; - case Country.BG: + case CountryCode.BG: return "BGR"; - case Country.BA: + case CountryCode.BA: return "BIH"; - case Country.BB: + case CountryCode.BB: return "BRB"; - case Country.WF: + case CountryCode.WF: return "WLF"; - case Country.BL: + case CountryCode.BL: return "BLM"; - case Country.BM: + case CountryCode.BM: return "BMU"; - case Country.BN: + case CountryCode.BN: return "BRN"; - case Country.BO: + case CountryCode.BO: return "BOL"; - case Country.BH: + case CountryCode.BH: return "BHR"; - case Country.BI: + case CountryCode.BI: return "BDI"; - case Country.BJ: + case CountryCode.BJ: return "BEN"; - case Country.BT: + case CountryCode.BT: return "BTN"; - case Country.JM: + case CountryCode.JM: return "JAM"; - case Country.BV: + case CountryCode.BV: return "BVT"; - case Country.BW: + case CountryCode.BW: return "BWA"; - case Country.WS: + case CountryCode.WS: return "WSM"; - case Country.BQ: + case CountryCode.BQ: return "BES"; - case Country.BR: + case CountryCode.BR: return "BRA"; - case Country.BS: + case CountryCode.BS: return "BHS"; - case Country.JE: + case CountryCode.JE: return "JEY"; - case Country.BY: + case CountryCode.BY: return "BLR"; - case Country.BZ: + case CountryCode.BZ: return "BLZ"; - case Country.RU: + case CountryCode.RU: return "RUS"; - case Country.RW: + case CountryCode.RW: return "RWA"; - case Country.RS: + case CountryCode.RS: return "SRB"; - case Country.TL: + case CountryCode.TL: return "TLS"; - case Country.RE: + case CountryCode.RE: return "REU"; - case Country.TM: + case CountryCode.TM: return "TKM"; - case Country.TJ: + case CountryCode.TJ: return "TJK"; - case Country.RO: + case CountryCode.RO: return "ROU"; - case Country.TK: + case CountryCode.TK: return "TKL"; - case Country.GW: + case CountryCode.GW: return "GNB"; - case Country.GU: + case CountryCode.GU: return "GUM"; - case Country.GT: + case CountryCode.GT: return "GTM"; - case Country.GS: + case CountryCode.GS: return "SGS"; - case Country.GR: + case CountryCode.GR: return "GRC"; - case Country.GQ: + case CountryCode.GQ: return "GNQ"; - case Country.GP: + case CountryCode.GP: return "GLP"; - case Country.JP: + case CountryCode.JP: return "JPN"; - case Country.GY: + case CountryCode.GY: return "GUY"; - case Country.GG: + case CountryCode.GG: return "GGY"; - case Country.GF: + case CountryCode.GF: return "GUF"; - case Country.GE: + case CountryCode.GE: return "GEO"; - case Country.GD: + case CountryCode.GD: return "GRD"; - case Country.GB: + case CountryCode.GB: return "GBR"; - case Country.GA: + case CountryCode.GA: return "GAB"; - case Country.SV: + case CountryCode.SV: return "SLV"; - case Country.GN: + case CountryCode.GN: return "GIN"; - case Country.GM: + case CountryCode.GM: return "GMB"; - case Country.GL: + case CountryCode.GL: return "GRL"; - case Country.GI: + case CountryCode.GI: return "GIB"; - case Country.GH: + case CountryCode.GH: return "GHA"; - case Country.OM: + case CountryCode.OM: return "OMN"; - case Country.TN: + case CountryCode.TN: return "TUN"; - case Country.JO: + case CountryCode.JO: return "JOR"; - case Country.HR: + case CountryCode.HR: return "HRV"; - case Country.HT: + case CountryCode.HT: return "HTI"; - case Country.HU: + case CountryCode.HU: return "HUN"; - case Country.HK: + case CountryCode.HK: return "HKG"; - case Country.HN: + case CountryCode.HN: return "HND"; - case Country.HM: + case CountryCode.HM: return "HMD"; - case Country.VE: + case CountryCode.VE: return "VEN"; - case Country.PR: + case CountryCode.PR: return "PRI"; - case Country.PS: + case CountryCode.PS: return "PSE"; - case Country.PW: + case CountryCode.PW: return "PLW"; - case Country.PT: + case CountryCode.PT: return "PRT"; - case Country.SJ: + case CountryCode.SJ: return "SJM"; - case Country.PY: + case CountryCode.PY: return "PRY"; - case Country.IQ: + case CountryCode.IQ: return "IRQ"; - case Country.PA: + case CountryCode.PA: return "PAN"; - case Country.PF: + case CountryCode.PF: return "PYF"; - case Country.PG: + case CountryCode.PG: return "PNG"; - case Country.PE: + case CountryCode.PE: return "PER"; - case Country.PK: + case CountryCode.PK: return "PAK"; - case Country.PH: + case CountryCode.PH: return "PHL"; - case Country.PN: + case CountryCode.PN: return "PCN"; - case Country.PL: + case CountryCode.PL: return "POL"; - case Country.PM: + case CountryCode.PM: return "SPM"; - case Country.ZM: + case CountryCode.ZM: return "ZMB"; - case Country.EH: + case CountryCode.EH: return "ESH"; - case Country.EE: + case CountryCode.EE: return "EST"; - case Country.EG: + case CountryCode.EG: return "EGY"; - case Country.ZA: + case CountryCode.ZA: return "ZAF"; - case Country.EC: + case CountryCode.EC: return "ECU"; - case Country.IT: + case CountryCode.IT: return "ITA"; - case Country.VN: + case CountryCode.VN: return "VNM"; - case Country.SB: + case CountryCode.SB: return "SLB"; - case Country.ET: + case CountryCode.ET: return "ETH"; - case Country.SO: + case CountryCode.SO: return "SOM"; - case Country.ZW: + case CountryCode.ZW: return "ZWE"; - case Country.SA: + case CountryCode.SA: return "SAU"; - case Country.ES: + case CountryCode.ES: return "ESP"; - case Country.ER: + case CountryCode.ER: return "ERI"; - case Country.ME: + case CountryCode.ME: return "MNE"; - case Country.MD: + case CountryCode.MD: return "MDA"; - case Country.MG: + case CountryCode.MG: return "MDG"; - case Country.MF: + case CountryCode.MF: return "MAF"; - case Country.MA: + case CountryCode.MA: return "MAR"; - case Country.MC: + case CountryCode.MC: return "MCO"; - case Country.UZ: + case CountryCode.UZ: return "UZB"; - case Country.MM: + case CountryCode.MM: return "MMR"; - case Country.ML: + case CountryCode.ML: return "MLI"; - case Country.MO: + case CountryCode.MO: return "MAC"; - case Country.MN: + case CountryCode.MN: return "MNG"; - case Country.MH: + case CountryCode.MH: return "MHL"; - case Country.MK: + case CountryCode.MK: return "MKD"; - case Country.MU: + case CountryCode.MU: return "MUS"; - case Country.MT: + case CountryCode.MT: return "MLT"; - case Country.MW: + case CountryCode.MW: return "MWI"; - case Country.MV: + case CountryCode.MV: return "MDV"; - case Country.MQ: + case CountryCode.MQ: return "MTQ"; - case Country.MP: + case CountryCode.MP: return "MNP"; - case Country.MS: + case CountryCode.MS: return "MSR"; - case Country.MR: + case CountryCode.MR: return "MRT"; - case Country.IM: + case CountryCode.IM: return "IMN"; - case Country.UG: + case CountryCode.UG: return "UGA"; - case Country.TZ: + case CountryCode.TZ: return "TZA"; - case Country.MY: + case CountryCode.MY: return "MYS"; - case Country.MX: + case CountryCode.MX: return "MEX"; - case Country.IL: + case CountryCode.IL: return "ISR"; - case Country.FR: + case CountryCode.FR: return "FRA"; - case Country.IO: + case CountryCode.IO: return "IOT"; - case Country.SH: + case CountryCode.SH: return "SHN"; - case Country.FI: + case CountryCode.FI: return "FIN"; - case Country.FJ: + case CountryCode.FJ: return "FJI"; - case Country.FK: + case CountryCode.FK: return "FLK"; - case Country.FM: + case CountryCode.FM: return "FSM"; - case Country.FO: + case CountryCode.FO: return "FRO"; - case Country.NI: + case CountryCode.NI: return "NIC"; - case Country.NL: + case CountryCode.NL: return "NLD"; - case Country.NO: + case CountryCode.NO: return "NOR"; - case Country.NA: + case CountryCode.NA: return "NAM"; - case Country.VU: + case CountryCode.VU: return "VUT"; - case Country.NC: + case CountryCode.NC: return "NCL"; - case Country.NE: + case CountryCode.NE: return "NER"; - case Country.NF: + case CountryCode.NF: return "NFK"; - case Country.NG: + case CountryCode.NG: return "NGA"; - case Country.NZ: + case CountryCode.NZ: return "NZL"; - case Country.NP: + case CountryCode.NP: return "NPL"; - case Country.NR: + case CountryCode.NR: return "NRU"; - case Country.NU: + case CountryCode.NU: return "NIU"; - case Country.CK: + case CountryCode.CK: return "COK"; - case Country.XK: + case CountryCode.XK: return "XKX"; - case Country.CI: + case CountryCode.CI: return "CIV"; - case Country.CH: + case CountryCode.CH: return "CHE"; - case Country.CO: + case CountryCode.CO: return "COL"; - case Country.CN: + case CountryCode.CN: return "CHN"; - case Country.CM: + case CountryCode.CM: return "CMR"; - case Country.CL: + case CountryCode.CL: return "CHL"; - case Country.CC: + case CountryCode.CC: return "CCK"; - case Country.CA: + case CountryCode.CA: return "CAN"; - case Country.CG: + case CountryCode.CG: return "COG"; - case Country.CF: + case CountryCode.CF: return "CAF"; - case Country.CD: + case CountryCode.CD: return "COD"; - case Country.CZ: + case CountryCode.CZ: return "CZE"; - case Country.CY: + case CountryCode.CY: return "CYP"; - case Country.CX: + case CountryCode.CX: return "CXR"; - case Country.CR: + case CountryCode.CR: return "CRI"; - case Country.CW: + case CountryCode.CW: return "CUW"; - case Country.CV: + case CountryCode.CV: return "CPV"; - case Country.CU: + case CountryCode.CU: return "CUB"; - case Country.SZ: + case CountryCode.SZ: return "SWZ"; - case Country.SY: + case CountryCode.SY: return "SYR"; - case Country.SX: + case CountryCode.SX: return "SXM"; - case Country.KG: + case CountryCode.KG: return "KGZ"; - case Country.KE: + case CountryCode.KE: return "KEN"; - case Country.SS: + case CountryCode.SS: return "SSD"; - case Country.SR: + case CountryCode.SR: return "SUR"; - case Country.KI: + case CountryCode.KI: return "KIR"; - case Country.KH: + case CountryCode.KH: return "KHM"; - case Country.KN: + case CountryCode.KN: return "KNA"; - case Country.KM: + case CountryCode.KM: return "COM"; - case Country.ST: + case CountryCode.ST: return "STP"; - case Country.SK: + case CountryCode.SK: return "SVK"; - case Country.KR: + case CountryCode.KR: return "KOR"; - case Country.SI: + case CountryCode.SI: return "SVN"; - case Country.KP: + case CountryCode.KP: return "PRK"; - case Country.KW: + case CountryCode.KW: return "KWT"; - case Country.SN: + case CountryCode.SN: return "SEN"; - case Country.SM: + case CountryCode.SM: return "SMR"; - case Country.SL: + case CountryCode.SL: return "SLE"; - case Country.SC: + case CountryCode.SC: return "SYC"; - case Country.KZ: + case CountryCode.KZ: return "KAZ"; - case Country.KY: + case CountryCode.KY: return "CYM"; - case Country.SG: + case CountryCode.SG: return "SGP"; - case Country.SE: + case CountryCode.SE: return "SWE"; - case Country.SD: + case CountryCode.SD: return "SDN"; - case Country.DO: + case CountryCode.DO: return "DOM"; - case Country.DM: + case CountryCode.DM: return "DMA"; - case Country.DJ: + case CountryCode.DJ: return "DJI"; - case Country.DK: + case CountryCode.DK: return "DNK"; - case Country.VG: + case CountryCode.VG: return "VGB"; - case Country.DE: + case CountryCode.DE: return "DEU"; - case Country.YE: + case CountryCode.YE: return "YEM"; - case Country.DZ: + case CountryCode.DZ: return "DZA"; - case Country.US: + case CountryCode.US: return "USA"; - case Country.UY: + case CountryCode.UY: return "URY"; - case Country.YT: + case CountryCode.YT: return "MYT"; - case Country.UM: + case CountryCode.UM: return "UMI"; - case Country.LB: + case CountryCode.LB: return "LBN"; - case Country.LC: + case CountryCode.LC: return "LCA"; - case Country.LA: + case CountryCode.LA: return "LAO"; - case Country.TV: + case CountryCode.TV: return "TUV"; - case Country.TW: + case CountryCode.TW: return "TWN"; - case Country.TT: + case CountryCode.TT: return "TTO"; - case Country.TR: + case CountryCode.TR: return "TUR"; - case Country.LK: + case CountryCode.LK: return "LKA"; - case Country.LI: + case CountryCode.LI: return "LIE"; - case Country.LV: + case CountryCode.LV: return "LVA"; - case Country.TO: + case CountryCode.TO: return "TON"; - case Country.LT: + case CountryCode.LT: return "LTU"; - case Country.LU: + case CountryCode.LU: return "LUX"; - case Country.LR: + case CountryCode.LR: return "LBR"; - case Country.LS: + case CountryCode.LS: return "LSO"; - case Country.TH: + case CountryCode.TH: return "THA"; - case Country.TF: + case CountryCode.TF: return "ATF"; - case Country.TG: + case CountryCode.TG: return "TGO"; - case Country.TD: + case CountryCode.TD: return "TCD"; - case Country.TC: + case CountryCode.TC: return "TCA"; - case Country.LY: + case CountryCode.LY: return "LBY"; - case Country.VA: + case CountryCode.VA: return "VAT"; - case Country.VC: + case CountryCode.VC: return "VCT"; - case Country.AE: + case CountryCode.AE: return "ARE"; - case Country.AD: + case CountryCode.AD: return "AND"; - case Country.AG: + case CountryCode.AG: return "ATG"; - case Country.AF: + case CountryCode.AF: return "AFG"; - case Country.AI: + case CountryCode.AI: return "AIA"; - case Country.VI: + case CountryCode.VI: return "VIR"; - case Country.IS: + case CountryCode.IS: return "ISL"; - case Country.IR: + case CountryCode.IR: return "IRN"; - case Country.AM: + case CountryCode.AM: return "ARM"; - case Country.AL: + case CountryCode.AL: return "ALB"; - case Country.AO: + case CountryCode.AO: return "AGO"; - case Country.AQ: + case CountryCode.AQ: return "ATA"; - case Country.AS: + case CountryCode.AS: return "ASM"; - case Country.AR: + case CountryCode.AR: return "ARG"; - case Country.AU: + case CountryCode.AU: return "AUS"; - case Country.AT: + case CountryCode.AT: return "AUT"; - case Country.AW: + case CountryCode.AW: return "ABW"; - case Country.IN: + case CountryCode.IN: return "IND"; - case Country.AX: + case CountryCode.AX: return "ALA"; - case Country.AZ: + case CountryCode.AZ: return "AZE"; - case Country.IE: + case CountryCode.IE: return "IRL"; - case Country.ID: + case CountryCode.ID: return "IDN"; - case Country.UA: + case CountryCode.UA: return "UKR"; - case Country.QA: + case CountryCode.QA: return "QAT"; - case Country.MZ: + case CountryCode.MZ: return "MOZ"; default: diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 0f806a2403..da27c09e01 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tournament.Screens.Editors { var countries = new List(); - foreach (var country in Enum.GetValues(typeof(Country)).Cast().Skip(1)) + foreach (var country in Enum.GetValues(typeof(CountryCode)).Cast().Skip(1)) { countries.Add(new TournamentTeam { From 18da9ddfbfc5343f37e8bf69be7ef5617c0e389b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 17:01:45 +0900 Subject: [PATCH 655/803] Add test coverage of two beatmaps in same set with different audio files --- .../NonVisual/BeatmapSetInfoEqualityTest.cs | 40 ++++++++++++++++++- osu.Game.Tests/Resources/TestResources.cs | 2 +- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs index c887105da6..461102124a 100644 --- a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs +++ b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs @@ -62,9 +62,45 @@ namespace osu.Game.Tests.NonVisual Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single())); } - private static void addAudioFile(BeatmapSetInfo beatmapSetInfo, string hash = null) + [Test] + public void TestAudioEqualityBeatmapInfoSameHash() { - beatmapSetInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = hash ?? Guid.NewGuid().ToString() }, "audio.mp3")); + var beatmapSet = TestResources.CreateTestBeatmapSetInfo(2); + + addAudioFile(beatmapSet); + + var beatmap1 = beatmapSet.Beatmaps.First(); + var beatmap2 = beatmapSet.Beatmaps.Last(); + + Assert.AreNotEqual(beatmap1, beatmap2); + Assert.IsTrue(beatmap1.AudioEquals(beatmap2)); + } + + [Test] + public void TestAudioEqualityBeatmapInfoDifferentHash() + { + var beatmapSet = TestResources.CreateTestBeatmapSetInfo(2); + + const string filename1 = "audio1.mp3"; + const string filename2 = "audio2.mp3"; + + addAudioFile(beatmapSet, filename: filename1); + addAudioFile(beatmapSet, filename: filename2); + + var beatmap1 = beatmapSet.Beatmaps.First(); + var beatmap2 = beatmapSet.Beatmaps.Last(); + + Assert.AreNotEqual(beatmap1, beatmap2); + + beatmap1.Metadata.AudioFile = filename1; + beatmap2.Metadata.AudioFile = filename2; + + Assert.IsFalse(beatmap1.AudioEquals(beatmap2)); + } + + private static void addAudioFile(BeatmapSetInfo beatmapSetInfo, string hash = null, string filename = null) + { + beatmapSetInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = hash ?? Guid.NewGuid().ToString() }, filename ?? "audio.mp3")); } [Test] diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 41404b2636..ee29cc8644 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -138,7 +138,7 @@ namespace osu.Game.Tests.Resources BPM = bpm, Hash = Guid.NewGuid().ToString().ComputeMD5Hash(), Ruleset = rulesetInfo, - Metadata = metadata, + Metadata = metadata.DeepClone(), Difficulty = new BeatmapDifficulty { OverallDifficulty = diff, From 22a9e7e2753c5e09aad0fcdecba470f913ef5a82 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 16:56:08 +0900 Subject: [PATCH 656/803] Fix audio/background equality not correctly using `BeatmapInfo` local filenames --- osu.Game/Beatmaps/BeatmapInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 45d76259fc..41e89d864e 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -169,8 +169,8 @@ namespace osu.Game.Beatmaps Debug.Assert(x.BeatmapSet != null); Debug.Assert(y.BeatmapSet != null); - string? fileHashX = x.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(x.BeatmapSet.Metadata))?.File.Hash; - string? fileHashY = y.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(y.BeatmapSet.Metadata))?.File.Hash; + string? fileHashX = x.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(x.Metadata))?.File.Hash; + string? fileHashY = y.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(y.Metadata))?.File.Hash; return fileHashX == fileHashY; } From afa831f6fef58e984fecc568378ca88759daa47a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 01:14:53 +0900 Subject: [PATCH 657/803] 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 04b16c5b0f..3b14d85e53 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 1d3cd39cf3..6120d3d600 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 dfd229755f..8a36ad6e3d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From ffa9a83a4f2e12eb18125e2372ca5a766662f718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Jun 2022 14:58:23 +0200 Subject: [PATCH 658/803] Add own fixed copy of defective Humanizer methods --- .../StringDehumanizeExtensionsTest.cs | 85 +++++++++++++++++ .../Extensions/StringDehumanizeExtensions.cs | 94 +++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 osu.Game.Tests/Extensions/StringDehumanizeExtensionsTest.cs create mode 100644 osu.Game/Extensions/StringDehumanizeExtensions.cs diff --git a/osu.Game.Tests/Extensions/StringDehumanizeExtensionsTest.cs b/osu.Game.Tests/Extensions/StringDehumanizeExtensionsTest.cs new file mode 100644 index 0000000000..e7490b461b --- /dev/null +++ b/osu.Game.Tests/Extensions/StringDehumanizeExtensionsTest.cs @@ -0,0 +1,85 @@ +// 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.Globalization; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Extensions; + +namespace osu.Game.Tests.Extensions +{ + [TestFixture] + public class StringDehumanizeExtensionsTest + { + [Test] + [TestCase("single", "Single")] + [TestCase("example word", "ExampleWord")] + [TestCase("mixed Casing test", "MixedCasingTest")] + [TestCase("PascalCase", "PascalCase")] + [TestCase("camelCase", "CamelCase")] + [TestCase("snake_case", "SnakeCase")] + [TestCase("kebab-case", "KebabCase")] + [TestCase("i will not break in a different culture", "IWillNotBreakInADifferentCulture", "tr-TR")] + public void TestToPascalCase(string input, string expectedOutput, string? culture = null) + { + using (temporaryCurrentCulture(culture)) + Assert.That(input.ToPascalCase(), Is.EqualTo(expectedOutput)); + } + + [Test] + [TestCase("single", "single")] + [TestCase("example word", "exampleWord")] + [TestCase("mixed Casing test", "mixedCasingTest")] + [TestCase("PascalCase", "pascalCase")] + [TestCase("camelCase", "camelCase")] + [TestCase("snake_case", "snakeCase")] + [TestCase("kebab-case", "kebabCase")] + [TestCase("I will not break in a different culture", "iWillNotBreakInADifferentCulture", "tr-TR")] + public void TestToCamelCase(string input, string expectedOutput, string? culture = null) + { + using (temporaryCurrentCulture(culture)) + Assert.That(input.ToCamelCase(), Is.EqualTo(expectedOutput)); + } + + [Test] + [TestCase("single", "single")] + [TestCase("example word", "example_word")] + [TestCase("mixed Casing test", "mixed_casing_test")] + [TestCase("PascalCase", "pascal_case")] + [TestCase("camelCase", "camel_case")] + [TestCase("snake_case", "snake_case")] + [TestCase("kebab-case", "kebab_case")] + [TestCase("I will not break in a different culture", "i_will_not_break_in_a_different_culture", "tr-TR")] + public void TestToSnakeCase(string input, string expectedOutput, string? culture = null) + { + using (temporaryCurrentCulture(culture)) + Assert.That(input.ToSnakeCase(), Is.EqualTo(expectedOutput)); + } + + [Test] + [TestCase("single", "single")] + [TestCase("example word", "example-word")] + [TestCase("mixed Casing test", "mixed-casing-test")] + [TestCase("PascalCase", "pascal-case")] + [TestCase("camelCase", "camel-case")] + [TestCase("snake_case", "snake-case")] + [TestCase("kebab-case", "kebab-case")] + [TestCase("I will not break in a different culture", "i-will-not-break-in-a-different-culture", "tr-TR")] + public void TestToKebabCase(string input, string expectedOutput, string? culture = null) + { + using (temporaryCurrentCulture(culture)) + Assert.That(input.ToKebabCase(), Is.EqualTo(expectedOutput)); + } + + private IDisposable temporaryCurrentCulture(string? cultureName) + { + var storedCulture = CultureInfo.CurrentCulture; + + if (cultureName != null) + CultureInfo.CurrentCulture = new CultureInfo(cultureName); + + return new InvokeOnDisposal(() => CultureInfo.CurrentCulture = storedCulture); + } + } +} diff --git a/osu.Game/Extensions/StringDehumanizeExtensions.cs b/osu.Game/Extensions/StringDehumanizeExtensions.cs new file mode 100644 index 0000000000..6f0d7622d3 --- /dev/null +++ b/osu.Game/Extensions/StringDehumanizeExtensions.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. + +// Based on code from the Humanizer library (https://github.com/Humanizr/Humanizer/blob/606e958cb83afc9be5b36716ac40d4daa9fa73a7/src/Humanizer/InflectorExtensions.cs) +// +// Humanizer is licenced under the MIT License (MIT) +// +// Copyright (c) .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.Text.RegularExpressions; + +namespace osu.Game.Extensions +{ + /// + /// Class with extension methods used to turn human-readable strings to casing conventions frequently used in code. + /// Often used for communicating with other systems (web API, spectator server). + /// All of the operations in this class are intentionally culture-invariant. + /// + public static class StringDehumanizeExtensions + { + /// + /// Converts the string to "Pascal case" (also known as "upper camel case"). + /// + /// + /// + /// "this is a test string".ToPascalCase() == "ThisIsATestString" + /// + /// + public static string ToPascalCase(this string input) + { + return Regex.Replace(input, "(?:^|_|-| +)(.)", match => match.Groups[1].Value.ToUpperInvariant()); + } + + /// + /// Converts the string to (lower) "camel case". + /// + /// + /// + /// "this is a test string".ToCamelCase() == "thisIsATestString" + /// + /// + public static string ToCamelCase(this string input) + { + string word = input.ToPascalCase(); + return word.Length > 0 ? word.Substring(0, 1).ToLowerInvariant() + word.Substring(1) : word; + } + + /// + /// Converts the string to "snake case". + /// + /// + /// + /// "this is a test string".ToSnakeCase() == "this_is_a_test_string" + /// + /// + public static string ToSnakeCase(this string input) + { + return Regex.Replace( + Regex.Replace( + Regex.Replace(input, @"([\p{Lu}]+)([\p{Lu}][\p{Ll}])", "$1_$2"), @"([\p{Ll}\d])([\p{Lu}])", "$1_$2"), @"[-\s]", "_").ToLowerInvariant(); + } + + /// + /// Converts the string to "kebab case". + /// + /// + /// + /// "this is a test string".ToKebabCase() == "this-is-a-test-string" + /// + /// + public static string ToKebabCase(this string input) + { + return ToSnakeCase(input).Replace('_', '-'); + } + } +} From 6f37487528a3a5207f73706968d067c5f87a69c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Jun 2022 15:02:15 +0200 Subject: [PATCH 659/803] Replace calls to defective Humanizer methods with correct version --- CodeAnalysis/BannedSymbols.txt | 4 ++++ .../UserInterface/TestSceneBeatmapListingSearchControl.cs | 4 ++-- osu.Game/Beatmaps/BeatmapStatisticIcon.cs | 4 ++-- osu.Game/Extensions/DrawableExtensions.cs | 3 +-- osu.Game/IO/Serialization/SnakeCaseKeyContractResolver.cs | 4 ++-- osu.Game/Online/API/APIMod.cs | 6 +++--- osu.Game/Online/API/Requests/GetCommentsRequest.cs | 4 ++-- osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs | 4 ++-- osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs | 4 ++-- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 3 +-- osu.Game/Online/Rooms/GetRoomsRequest.cs | 4 ++-- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 3 +-- 12 files changed, 24 insertions(+), 23 deletions(-) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index 8b5431e2d6..e779ee6658 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -19,3 +19,7 @@ P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResult M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever. M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString. M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString. +M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToPascalCase() instead. +M:Humanizer.InflectorExtensions.Camelize(System.String);Humanizer's .Camelize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToCamelCase() instead. +M:Humanizer.InflectorExtensions.Underscore(System.String);Humanizer's .Underscore() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToSnakeCase() instead. +M:Humanizer.InflectorExtensions.Kebaberize(System.String);Humanizer's .Kebaberize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToKebabCase() instead. diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index 44f2da2b95..e8454e8d0f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -4,13 +4,13 @@ #nullable disable using System.Linq; -using Humanizer; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Extensions; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; control.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true); - control.General.BindCollectionChanged((_, _) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().Underscore())) : "")}", true); + control.General.BindCollectionChanged((_, _) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().ToSnakeCase())) : "")}", true); control.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true); control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); diff --git a/osu.Game/Beatmaps/BeatmapStatisticIcon.cs b/osu.Game/Beatmaps/BeatmapStatisticIcon.cs index 58d13a3172..8002910b52 100644 --- a/osu.Game/Beatmaps/BeatmapStatisticIcon.cs +++ b/osu.Game/Beatmaps/BeatmapStatisticIcon.cs @@ -3,10 +3,10 @@ #nullable disable -using Humanizer; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Game.Extensions; namespace osu.Game.Beatmaps { @@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps [BackgroundDependencyLoader] private void load(TextureStore textures) { - Texture = textures.Get($"Icons/BeatmapDetails/{iconType.ToString().Kebaberize()}"); + Texture = textures.Get($"Icons/BeatmapDetails/{iconType.ToString().ToKebabCase()}"); } } diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index d1aba2bfe3..35f2d61437 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.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 Humanizer; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -67,7 +66,7 @@ namespace osu.Game.Extensions foreach (var (_, property) in component.GetSettingsSourceProperties()) { - if (!info.Settings.TryGetValue(property.Name.Underscore(), out object settingValue)) + if (!info.Settings.TryGetValue(property.Name.ToSnakeCase(), out object settingValue)) continue; skinnable.CopyAdjustedSetting((IBindable)property.GetValue(component), settingValue); diff --git a/osu.Game/IO/Serialization/SnakeCaseKeyContractResolver.cs b/osu.Game/IO/Serialization/SnakeCaseKeyContractResolver.cs index 4808ac1384..b51a8473ca 100644 --- a/osu.Game/IO/Serialization/SnakeCaseKeyContractResolver.cs +++ b/osu.Game/IO/Serialization/SnakeCaseKeyContractResolver.cs @@ -3,8 +3,8 @@ #nullable disable -using Humanizer; using Newtonsoft.Json.Serialization; +using osu.Game.Extensions; namespace osu.Game.IO.Serialization { @@ -12,7 +12,7 @@ namespace osu.Game.IO.Serialization { protected override string ResolvePropertyName(string propertyName) { - return propertyName.Underscore(); + return propertyName.ToSnakeCase(); } } } diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 3bd0c91f8e..900f59290c 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -7,12 +7,12 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -using Humanizer; using MessagePack; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Game.Configuration; +using osu.Game.Extensions; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -45,7 +45,7 @@ namespace osu.Game.Online.API var bindable = (IBindable)property.GetValue(mod); if (!bindable.IsDefault) - Settings.Add(property.Name.Underscore(), bindable.GetUnderlyingSettingValue()); + Settings.Add(property.Name.ToSnakeCase(), bindable.GetUnderlyingSettingValue()); } } @@ -63,7 +63,7 @@ namespace osu.Game.Online.API { foreach (var (_, property) in resultMod.GetSettingsSourceProperties()) { - if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue)) + if (!Settings.TryGetValue(property.Name.ToSnakeCase(), out object settingValue)) continue; try diff --git a/osu.Game/Online/API/Requests/GetCommentsRequest.cs b/osu.Game/Online/API/Requests/GetCommentsRequest.cs index c63c574124..1aa08f2ed8 100644 --- a/osu.Game/Online/API/Requests/GetCommentsRequest.cs +++ b/osu.Game/Online/API/Requests/GetCommentsRequest.cs @@ -4,7 +4,7 @@ #nullable disable using osu.Framework.IO.Network; -using Humanizer; +using osu.Game.Extensions; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Comments; @@ -32,7 +32,7 @@ namespace osu.Game.Online.API.Requests var req = base.CreateWebRequest(); req.AddParameter("commentable_id", commentableId.ToString()); - req.AddParameter("commentable_type", type.ToString().Underscore().ToLowerInvariant()); + req.AddParameter("commentable_type", type.ToString().ToSnakeCase().ToLowerInvariant()); req.AddParameter("page", page.ToString()); req.AddParameter("sort", sort.ToString().ToLowerInvariant()); diff --git a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs index 3ec60cd06c..d723786f23 100644 --- a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs @@ -3,8 +3,8 @@ #nullable disable -using Humanizer; using System.Collections.Generic; +using osu.Game.Extensions; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests @@ -22,7 +22,7 @@ namespace osu.Game.Online.API.Requests this.type = type; } - protected override string Target => $@"users/{userId}/beatmapsets/{type.ToString().Underscore()}"; + protected override string Target => $@"users/{userId}/beatmapsets/{type.ToString().ToSnakeCase()}"; } public enum BeatmapSetType diff --git a/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs b/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs index 8fefe4d9c2..2def18926f 100644 --- a/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs +++ b/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs @@ -4,8 +4,8 @@ #nullable disable using System; -using Humanizer; using Newtonsoft.Json; +using osu.Game.Extensions; using osu.Game.Scoring; namespace osu.Game.Online.API.Requests.Responses @@ -21,7 +21,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty] private string type { - set => Type = (RecentActivityType)Enum.Parse(typeof(RecentActivityType), value.Pascalize()); + set => Type = (RecentActivityType)Enum.Parse(typeof(RecentActivityType), value.ToPascalCase()); } public RecentActivityType Type; diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 082f9bb371..c303c410ec 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; -using Humanizer; using JetBrains.Annotations; using osu.Framework.IO.Network; using osu.Game.Extensions; @@ -86,7 +85,7 @@ namespace osu.Game.Online.API.Requests req.AddParameter("q", query); if (General != null && General.Any()) - req.AddParameter("c", string.Join('.', General.Select(e => e.ToString().Underscore()))); + req.AddParameter("c", string.Join('.', General.Select(e => e.ToString().ToSnakeCase()))); if (ruleset.OnlineID >= 0) req.AddParameter("m", ruleset.OnlineID.ToString()); diff --git a/osu.Game/Online/Rooms/GetRoomsRequest.cs b/osu.Game/Online/Rooms/GetRoomsRequest.cs index 5ae9d58189..afab83b5be 100644 --- a/osu.Game/Online/Rooms/GetRoomsRequest.cs +++ b/osu.Game/Online/Rooms/GetRoomsRequest.cs @@ -4,8 +4,8 @@ #nullable disable using System.Collections.Generic; -using Humanizer; using osu.Framework.IO.Network; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Screens.OnlinePlay.Lounge.Components; @@ -27,7 +27,7 @@ namespace osu.Game.Online.Rooms var req = base.CreateWebRequest(); if (status != RoomStatusFilter.Open) - req.AddParameter("mode", status.ToString().Underscore().ToLowerInvariant()); + req.AddParameter("mode", status.ToString().ToSnakeCase().ToLowerInvariant()); if (!string.IsNullOrEmpty(category)) req.AddParameter("category", category); diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index f045ab661e..ee29241321 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Humanizer; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -71,7 +70,7 @@ namespace osu.Game.Screens.Play.HUD var bindable = (IBindable)property.GetValue(component); if (!bindable.IsDefault) - Settings.Add(property.Name.Underscore(), bindable.GetUnderlyingSettingValue()); + Settings.Add(property.Name.ToSnakeCase(), bindable.GetUnderlyingSettingValue()); } if (component is Container container) From 5055e327699829b820231598009be8b6e1433468 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jul 2022 03:42:20 +0300 Subject: [PATCH 660/803] Add benchmark for `HitObject` --- osu.Game.Benchmarks/BenchmarkHitObject.cs | 116 ++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 osu.Game.Benchmarks/BenchmarkHitObject.cs diff --git a/osu.Game.Benchmarks/BenchmarkHitObject.cs b/osu.Game.Benchmarks/BenchmarkHitObject.cs new file mode 100644 index 0000000000..d7731e0cfd --- /dev/null +++ b/osu.Game.Benchmarks/BenchmarkHitObject.cs @@ -0,0 +1,116 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using BenchmarkDotNet.Attributes; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Benchmarks +{ + public class BenchmarkHitObject : BenchmarkTest + { + [Params(1, 100, 1000)] + public int Count { get; set; } + + [Params(false, true)] + public bool WithBindableAccess { get; set; } + + [Benchmark] + public HitCircle[] OsuCircle() + { + var circles = new HitCircle[Count]; + + for (int i = 0; i < Count; i++) + { + circles[i] = new HitCircle(); + + if (WithBindableAccess) + { + _ = circles[i].PositionBindable; + _ = circles[i].ScaleBindable; + _ = circles[i].ComboIndexBindable; + _ = circles[i].ComboOffsetBindable; + _ = circles[i].StackHeightBindable; + _ = circles[i].LastInComboBindable; + _ = circles[i].ComboIndexWithOffsetsBindable; + _ = circles[i].IndexInCurrentComboBindable; + _ = circles[i].SamplesBindable; + _ = circles[i].StartTimeBindable; + } + } + + return circles; + } + + [Benchmark] + public Hit[] TaikoHit() + { + var hits = new Hit[Count]; + + for (int i = 0; i < Count; i++) + { + hits[i] = new Hit(); + + if (WithBindableAccess) + { + _ = hits[i].TypeBindable; + _ = hits[i].IsStrongBindable; + _ = hits[i].SamplesBindable; + _ = hits[i].StartTimeBindable; + } + } + + return hits; + } + + [Benchmark] + public Fruit[] CatchFruit() + { + var fruit = new Fruit[Count]; + + for (int i = 0; i < Count; i++) + { + fruit[i] = new Fruit(); + + if (WithBindableAccess) + { + _ = fruit[i].OriginalXBindable; + _ = fruit[i].XOffsetBindable; + _ = fruit[i].ScaleBindable; + _ = fruit[i].ComboIndexBindable; + _ = fruit[i].HyperDashBindable; + _ = fruit[i].LastInComboBindable; + _ = fruit[i].ComboIndexWithOffsetsBindable; + _ = fruit[i].IndexInCurrentComboBindable; + _ = fruit[i].IndexInBeatmapBindable; + _ = fruit[i].SamplesBindable; + _ = fruit[i].StartTimeBindable; + } + } + + return fruit; + } + + [Benchmark] + public Note[] ManiaNote() + { + var notes = new Note[Count]; + + for (int i = 0; i < Count; i++) + { + notes[i] = new Note(); + + if (WithBindableAccess) + { + _ = notes[i].ColumnBindable; + _ = notes[i].SamplesBindable; + _ = notes[i].StartTimeBindable; + } + } + + return notes; + } + } +} From 5ddb5a3d741e2d1f64ad7ddd964502a3767206a3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jul 2022 04:23:30 +0300 Subject: [PATCH 661/803] Introduce `HitObjectProperty` --- .../Rulesets/Objects/HitObjectProperty.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 osu.Game/Rulesets/Objects/HitObjectProperty.cs diff --git a/osu.Game/Rulesets/Objects/HitObjectProperty.cs b/osu.Game/Rulesets/Objects/HitObjectProperty.cs new file mode 100644 index 0000000000..e765dd6cb0 --- /dev/null +++ b/osu.Game/Rulesets/Objects/HitObjectProperty.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using JetBrains.Annotations; +using osu.Framework.Bindables; + +namespace osu.Game.Rulesets.Objects +{ + /// + /// Represents a wrapper containing a lazily-initialised , backed by a temporary field used for storage until initialisation. + /// + public struct HitObjectProperty + { + [CanBeNull] + private Bindable backingBindable; + + /// + /// A temporary field to store the current value to, prior to 's initialisation. + /// + private T backingValue; + + /// + /// The underlying , only initialised on first access. + /// + public Bindable Bindable => backingBindable ??= new Bindable { Value = backingValue }; + + /// + /// The current value, derived from and delegated to if initialised, or a temporary field otherwise. + /// + public T Value + { + get => backingBindable != null ? backingBindable.Value : backingValue; + set + { + if (backingBindable != null) + backingBindable.Value = value; + else + backingValue = value; + } + } + } +} From 1051009827b081d15686d307603b510f9e1e1a4e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jul 2022 04:35:07 +0300 Subject: [PATCH 662/803] Change bindable properties in all `HitObject`s to be lazily initialised --- .../Objects/CatchHitObject.cs | 66 ++++++++++++------- .../Objects/PalpableCatchHitObject.cs | 7 +- .../Objects/ManiaHitObject.cs | 8 ++- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 42 ++++++++---- osu.Game.Rulesets.Taiko/Objects/BarLine.cs | 10 +-- osu.Game.Rulesets.Taiko/Objects/Hit.cs | 11 ++-- 6 files changed, 93 insertions(+), 51 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index f5a3426305..7b871177f1 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -19,7 +19,9 @@ namespace osu.Game.Rulesets.Catch.Objects { public const float OBJECT_RADIUS = 64; - public readonly Bindable OriginalXBindable = new Bindable(); + private HitObjectProperty originalX; + + public Bindable OriginalXBindable => originalX.Bindable; /// /// The horizontal position of the hit object between 0 and . @@ -31,18 +33,20 @@ namespace osu.Game.Rulesets.Catch.Objects [JsonIgnore] public float X { - set => OriginalXBindable.Value = value; + set => originalX.Value = value; } - public readonly Bindable XOffsetBindable = new Bindable(); + private HitObjectProperty xOffset; + + public Bindable XOffsetBindable => xOffset.Bindable; /// /// A random offset applied to the horizontal position, set by the beatmap processing. /// public float XOffset { - get => XOffsetBindable.Value; - set => XOffsetBindable.Value = value; + get => xOffset.Value; + set => xOffset.Value = value; } /// @@ -54,8 +58,8 @@ namespace osu.Game.Rulesets.Catch.Objects /// public float OriginalX { - get => OriginalXBindable.Value; - set => OriginalXBindable.Value = value; + get => originalX.Value; + set => originalX.Value = value; } /// @@ -69,59 +73,71 @@ namespace osu.Game.Rulesets.Catch.Objects public double TimePreempt { get; set; } = 1000; - public readonly Bindable IndexInBeatmapBindable = new Bindable(); + private HitObjectProperty indexInBeatmap; + + public Bindable IndexInBeatmapBindable => indexInBeatmap.Bindable; public int IndexInBeatmap { - get => IndexInBeatmapBindable.Value; - set => IndexInBeatmapBindable.Value = value; + get => indexInBeatmap.Value; + set => indexInBeatmap.Value = value; } public virtual bool NewCombo { get; set; } public int ComboOffset { get; set; } - public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); + private HitObjectProperty indexInCurrentCombo; + + public Bindable IndexInCurrentComboBindable => indexInCurrentCombo.Bindable; public int IndexInCurrentCombo { - get => IndexInCurrentComboBindable.Value; - set => IndexInCurrentComboBindable.Value = value; + get => indexInCurrentCombo.Value; + set => indexInCurrentCombo.Value = value; } - public Bindable ComboIndexBindable { get; } = new Bindable(); + private HitObjectProperty comboIndex; + + public Bindable ComboIndexBindable => comboIndex.Bindable; public int ComboIndex { - get => ComboIndexBindable.Value; - set => ComboIndexBindable.Value = value; + get => comboIndex.Value; + set => comboIndex.Value = value; } - public Bindable ComboIndexWithOffsetsBindable { get; } = new Bindable(); + private HitObjectProperty comboIndexWithOffsets; + + public Bindable ComboIndexWithOffsetsBindable => comboIndexWithOffsets.Bindable; public int ComboIndexWithOffsets { - get => ComboIndexWithOffsetsBindable.Value; - set => ComboIndexWithOffsetsBindable.Value = value; + get => comboIndexWithOffsets.Value; + set => comboIndexWithOffsets.Value = value; } - public Bindable LastInComboBindable { get; } = new Bindable(); + private HitObjectProperty lastInCombo; + + public Bindable LastInComboBindable => lastInCombo.Bindable; /// /// The next fruit starts a new combo. Used for explodey. /// public virtual bool LastInCombo { - get => LastInComboBindable.Value; - set => LastInComboBindable.Value = value; + get => lastInCombo.Value; + set => lastInCombo.Value = value; } - public readonly Bindable ScaleBindable = new Bindable(1); + private HitObjectProperty scale; + + public Bindable ScaleBindable => scale.Bindable; public float Scale { - get => ScaleBindable.Value; - set => ScaleBindable.Value = value; + get => scale.Value; + set => scale.Value = value; } /// diff --git a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs index 1ededa1438..c9bc9ca2ac 100644 --- a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs @@ -5,6 +5,7 @@ using Newtonsoft.Json; using osu.Framework.Bindables; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; using osuTK.Graphics; @@ -24,12 +25,14 @@ namespace osu.Game.Rulesets.Catch.Objects /// public float DistanceToHyperDash { get; set; } - public readonly Bindable HyperDashBindable = new Bindable(); + private HitObjectProperty hyperDash; + + public Bindable HyperDashBindable => hyperDash.Bindable; /// /// Whether this fruit can initiate a hyperdash. /// - public bool HyperDash => HyperDashBindable.Value; + public bool HyperDash => hyperDash.Value; private CatchHitObject hyperDashTarget; diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs index 0efaeac026..ebff5cf4e9 100644 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs @@ -13,12 +13,14 @@ namespace osu.Game.Rulesets.Mania.Objects { public abstract class ManiaHitObject : HitObject, IHasColumn, IHasXPosition { - public readonly Bindable ColumnBindable = new Bindable(); + private HitObjectProperty column; + + public Bindable ColumnBindable => column.Bindable; public virtual int Column { - get => ColumnBindable.Value; - set => ColumnBindable.Value = value; + get => column.Value; + set => column.Value = value; } protected override HitWindows CreateHitWindows() => new ManiaHitWindows(); diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 387342b4a9..6708c061e7 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -7,12 +7,12 @@ using System; using System.Linq; using osu.Framework.Bindables; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; -using osuTK; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects { @@ -36,12 +36,14 @@ namespace osu.Game.Rulesets.Osu.Objects public double TimePreempt = 600; public double TimeFadeIn = 400; - public readonly Bindable PositionBindable = new Bindable(); + private HitObjectProperty position; + + public Bindable PositionBindable => position.Bindable; public virtual Vector2 Position { - get => PositionBindable.Value; - set => PositionBindable.Value = value; + get => position.Value; + set => position.Value = value; } public float X => Position.X; @@ -53,7 +55,9 @@ namespace osu.Game.Rulesets.Osu.Objects public Vector2 StackedEndPosition => EndPosition + StackOffset; - public readonly Bindable StackHeightBindable = new Bindable(); + private HitObjectProperty stackHeightProperty; + + public Bindable StackHeightBindable => stackHeightProperty.Bindable; public int StackHeight { @@ -65,7 +69,9 @@ namespace osu.Game.Rulesets.Osu.Objects public double Radius => OBJECT_RADIUS * Scale; - public readonly Bindable ScaleBindable = new BindableFloat(1); + private HitObjectProperty scaleProperty; + + public Bindable ScaleBindable => scaleProperty.Bindable; public float Scale { @@ -75,7 +81,9 @@ namespace osu.Game.Rulesets.Osu.Objects public virtual bool NewCombo { get; set; } - public readonly Bindable ComboOffsetBindable = new Bindable(); + private HitObjectProperty comboOffsetProperty; + + public Bindable ComboOffsetBindable => comboOffsetProperty.Bindable; public int ComboOffset { @@ -83,7 +91,9 @@ namespace osu.Game.Rulesets.Osu.Objects set => ComboOffsetBindable.Value = value; } - public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); + private HitObjectProperty indexInCurrentComboProperty; + + public Bindable IndexInCurrentComboBindable => indexInCurrentComboProperty.Bindable; public virtual int IndexInCurrentCombo { @@ -91,7 +101,9 @@ namespace osu.Game.Rulesets.Osu.Objects set => IndexInCurrentComboBindable.Value = value; } - public Bindable ComboIndexBindable { get; } = new Bindable(); + private HitObjectProperty comboIndexProperty; + + public Bindable ComboIndexBindable => comboIndexProperty.Bindable; public virtual int ComboIndex { @@ -99,7 +111,9 @@ namespace osu.Game.Rulesets.Osu.Objects set => ComboIndexBindable.Value = value; } - public Bindable ComboIndexWithOffsetsBindable { get; } = new Bindable(); + private HitObjectProperty comboIndexWithOffsetsProperty; + + public Bindable ComboIndexWithOffsetsBindable => comboIndexWithOffsetsProperty.Bindable; public int ComboIndexWithOffsets { @@ -107,7 +121,9 @@ namespace osu.Game.Rulesets.Osu.Objects set => ComboIndexWithOffsetsBindable.Value = value; } - public Bindable LastInComboBindable { get; } = new Bindable(); + private HitObjectProperty lastInComboProperty; + + public Bindable LastInComboBindable => lastInComboProperty.Bindable; public bool LastInCombo { diff --git a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs index 382035119e..d2eba0eb54 100644 --- a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs @@ -11,14 +11,16 @@ namespace osu.Game.Rulesets.Taiko.Objects { public class BarLine : TaikoHitObject, IBarLine { + private HitObjectProperty major; + + public Bindable MajorBindable => major.Bindable; + public bool Major { - get => MajorBindable.Value; - set => MajorBindable.Value = value; + get => major.Value; + set => major.Value = value; } - public readonly Bindable MajorBindable = new BindableBool(); - public override Judgement CreateJudgement() => new IgnoreJudgement(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs index 20f3304c30..787079bfee 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Audio; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osuTK.Graphics; @@ -14,19 +15,21 @@ namespace osu.Game.Rulesets.Taiko.Objects { public class Hit : TaikoStrongableHitObject, IHasDisplayColour { - public readonly Bindable TypeBindable = new Bindable(); + private HitObjectProperty type; - public Bindable DisplayColour { get; } = new Bindable(COLOUR_CENTRE); + public Bindable TypeBindable => type.Bindable; /// /// The that actuates this . /// public HitType Type { - get => TypeBindable.Value; - set => TypeBindable.Value = value; + get => type.Value; + set => type.Value = value; } + public Bindable DisplayColour { get; } = new Bindable(COLOUR_CENTRE); + public static readonly Color4 COLOUR_CENTRE = Color4Extensions.FromHex(@"bb1177"); public static readonly Color4 COLOUR_RIM = Color4Extensions.FromHex(@"2299bb"); From 8f80a22ef9ab510f02e98fcbfa7f9765ee229b61 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jul 2022 06:57:31 +0300 Subject: [PATCH 663/803] Fix osu! and catch hitobjects no longer scaled to 1 by default --- osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 2 +- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 2 +- osu.Game/Rulesets/Objects/HitObjectProperty.cs | 10 +++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 7b871177f1..6e01c44e1f 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Catch.Objects set => lastInCombo.Value = value; } - private HitObjectProperty scale; + private HitObjectProperty scale = new HitObjectProperty(1); public Bindable ScaleBindable => scale.Bindable; diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 6708c061e7..1e7cce162e 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects public double Radius => OBJECT_RADIUS * Scale; - private HitObjectProperty scaleProperty; + private HitObjectProperty scaleProperty = new HitObjectProperty(1); public Bindable ScaleBindable => scaleProperty.Bindable; diff --git a/osu.Game/Rulesets/Objects/HitObjectProperty.cs b/osu.Game/Rulesets/Objects/HitObjectProperty.cs index e765dd6cb0..f1df83f80c 100644 --- a/osu.Game/Rulesets/Objects/HitObjectProperty.cs +++ b/osu.Game/Rulesets/Objects/HitObjectProperty.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Objects /// /// The underlying , only initialised on first access. /// - public Bindable Bindable => backingBindable ??= new Bindable { Value = backingValue }; + public Bindable Bindable => backingBindable ??= new Bindable(defaultValue) { Value = backingValue }; /// /// The current value, derived from and delegated to if initialised, or a temporary field otherwise. @@ -40,5 +40,13 @@ namespace osu.Game.Rulesets.Objects backingValue = value; } } + + private readonly T defaultValue; + + public HitObjectProperty(T value = default) + { + backingValue = defaultValue = value; + backingBindable = null; + } } } From 59018ab5ba28a21e2a2ede263dc2a9df66d5012d Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Mon, 18 Jul 2022 23:21:16 -0500 Subject: [PATCH 664/803] Fix multiplayer queue edit button opening to the wrong beatmap --- .../Multiplayer/TestSceneMultiplayer.cs | 60 ++++++++++--------- .../Multiplayer/MultiplayerMatchSubScreen.cs | 12 +++- 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index a2793acba7..d35887c443 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -402,16 +402,18 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestPlayStartsWithCorrectBeatmapWhileAtSongSelect() { - createRoom(() => new Room + PlaylistItem? item = null; + createRoom(() => { - Name = { Value = "Test Room" }, - Playlist = + item = new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - } - } + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + }; + return new Room + { + Name = { Value = "Test Room" }, + Playlist = { item } + }; }); pressReadyButton(); @@ -419,7 +421,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; - ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(item); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); @@ -440,16 +442,18 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestPlayStartsWithCorrectRulesetWhileAtSongSelect() { - createRoom(() => new Room + PlaylistItem? item = null; + createRoom(() => { - Name = { Value = "Test Room" }, - Playlist = + item = new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - } - } + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + }; + return new Room + { + Name = { Value = "Test Room" }, + Playlist = { item } + }; }); pressReadyButton(); @@ -457,7 +461,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; - ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(item); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); @@ -478,16 +482,18 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestPlayStartsWithCorrectModsWhileAtSongSelect() { - createRoom(() => new Room + PlaylistItem? item = null; + createRoom(() => { - Name = { Value = "Test Room" }, - Playlist = + item = new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - } - } + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + }; + return new Room + { + Name = { Value = "Test Room" }, + Playlist = { item } + }; }); pressReadyButton(); @@ -495,7 +501,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; - ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(item); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4eb16a854b..9ed67bd142 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -49,6 +49,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private MultiplayerClient client { get; set; } + [Resolved] + private BeatmapManager beatmapManager { get; set; } + private readonly IBindable isConnected = new Bindable(); private AddItemButton addItemButton; @@ -145,7 +148,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new MultiplayerPlaylist { RelativeSizeAxes = Axes.Both, - RequestEdit = item => OpenSongSelection(item.ID) + RequestEdit = item => OpenSongSelection(item) } }, new[] @@ -223,12 +226,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer /// Opens the song selection screen to add or edit an item. /// /// An optional playlist item to edit. If null, a new item will be added instead. - internal void OpenSongSelection(long? itemToEdit = null) + internal void OpenSongSelection(PlaylistItem itemToEdit = null) { if (!this.IsCurrentScreen()) return; - this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit)); + var localBeatmap = itemToEdit == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineID == itemToEdit.Beatmap.OnlineID); + var workingBeatmap = localBeatmap == null ? null : beatmapManager.GetWorkingBeatmap(localBeatmap); + + this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit?.ID, workingBeatmap)); } protected override Drawable CreateFooter() => new MultiplayerMatchFooter(); From 46efce8a67c059ee5673682df65ea736fa1fe662 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 13:39:26 +0900 Subject: [PATCH 665/803] Equalise work done in benchmarks to cover accessing normal properties --- osu.Game.Benchmarks/BenchmarkHitObject.cs | 50 +++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/osu.Game.Benchmarks/BenchmarkHitObject.cs b/osu.Game.Benchmarks/BenchmarkHitObject.cs index d7731e0cfd..65c78e39b3 100644 --- a/osu.Game.Benchmarks/BenchmarkHitObject.cs +++ b/osu.Game.Benchmarks/BenchmarkHitObject.cs @@ -39,6 +39,29 @@ namespace osu.Game.Benchmarks _ = circles[i].SamplesBindable; _ = circles[i].StartTimeBindable; } + else + { + _ = circles[i].Position; + _ = circles[i].Scale; + _ = circles[i].ComboIndex; + _ = circles[i].ComboOffset; + _ = circles[i].StackHeight; + _ = circles[i].LastInCombo; + _ = circles[i].ComboIndexWithOffsets; + _ = circles[i].IndexInCurrentCombo; + _ = circles[i].Samples; + _ = circles[i].StartTime; + _ = circles[i].Position; + _ = circles[i].Scale; + _ = circles[i].ComboIndex; + _ = circles[i].ComboOffset; + _ = circles[i].StackHeight; + _ = circles[i].LastInCombo; + _ = circles[i].ComboIndexWithOffsets; + _ = circles[i].IndexInCurrentCombo; + _ = circles[i].Samples; + _ = circles[i].StartTime; + } } return circles; @@ -60,6 +83,13 @@ namespace osu.Game.Benchmarks _ = hits[i].SamplesBindable; _ = hits[i].StartTimeBindable; } + else + { + _ = hits[i].Type; + _ = hits[i].IsStrong; + _ = hits[i].Samples; + _ = hits[i].StartTime; + } } return hits; @@ -88,6 +118,20 @@ namespace osu.Game.Benchmarks _ = fruit[i].SamplesBindable; _ = fruit[i].StartTimeBindable; } + else + { + _ = fruit[i].OriginalX; + _ = fruit[i].XOffset; + _ = fruit[i].Scale; + _ = fruit[i].ComboIndex; + _ = fruit[i].HyperDash; + _ = fruit[i].LastInCombo; + _ = fruit[i].ComboIndexWithOffsets; + _ = fruit[i].IndexInCurrentCombo; + _ = fruit[i].IndexInBeatmap; + _ = fruit[i].Samples; + _ = fruit[i].StartTime; + } } return fruit; @@ -108,6 +152,12 @@ namespace osu.Game.Benchmarks _ = notes[i].SamplesBindable; _ = notes[i].StartTimeBindable; } + else + { + _ = notes[i].Column; + _ = notes[i].Samples; + _ = notes[i].StartTime; + } } return notes; From d8cce5fe363ecad7c6fb80d40e161de4c76497a6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jul 2022 07:52:12 +0300 Subject: [PATCH 666/803] Fix `OsuHitObject` not using property wrapper properly --- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 1e7cce162e..7b98fc48e0 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -55,80 +55,80 @@ namespace osu.Game.Rulesets.Osu.Objects public Vector2 StackedEndPosition => EndPosition + StackOffset; - private HitObjectProperty stackHeightProperty; + private HitObjectProperty stackHeight; - public Bindable StackHeightBindable => stackHeightProperty.Bindable; + public Bindable StackHeightBindable => stackHeight.Bindable; public int StackHeight { - get => StackHeightBindable.Value; - set => StackHeightBindable.Value = value; + get => stackHeight.Value; + set => stackHeight.Value = value; } public virtual Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f); public double Radius => OBJECT_RADIUS * Scale; - private HitObjectProperty scaleProperty = new HitObjectProperty(1); + private HitObjectProperty scale = new HitObjectProperty(1); - public Bindable ScaleBindable => scaleProperty.Bindable; + public Bindable ScaleBindable => scale.Bindable; public float Scale { - get => ScaleBindable.Value; - set => ScaleBindable.Value = value; + get => scale.Value; + set => scale.Value = value; } public virtual bool NewCombo { get; set; } - private HitObjectProperty comboOffsetProperty; + private HitObjectProperty comboOffset; - public Bindable ComboOffsetBindable => comboOffsetProperty.Bindable; + public Bindable ComboOffsetBindable => comboOffset.Bindable; public int ComboOffset { - get => ComboOffsetBindable.Value; - set => ComboOffsetBindable.Value = value; + get => comboOffset.Value; + set => comboOffset.Value = value; } - private HitObjectProperty indexInCurrentComboProperty; + private HitObjectProperty indexInCurrentCombo; - public Bindable IndexInCurrentComboBindable => indexInCurrentComboProperty.Bindable; + public Bindable IndexInCurrentComboBindable => indexInCurrentCombo.Bindable; public virtual int IndexInCurrentCombo { - get => IndexInCurrentComboBindable.Value; - set => IndexInCurrentComboBindable.Value = value; + get => indexInCurrentCombo.Value; + set => indexInCurrentCombo.Value = value; } - private HitObjectProperty comboIndexProperty; + private HitObjectProperty comboIndex; - public Bindable ComboIndexBindable => comboIndexProperty.Bindable; + public Bindable ComboIndexBindable => comboIndex.Bindable; public virtual int ComboIndex { - get => ComboIndexBindable.Value; - set => ComboIndexBindable.Value = value; + get => comboIndex.Value; + set => comboIndex.Value = value; } - private HitObjectProperty comboIndexWithOffsetsProperty; + private HitObjectProperty comboIndexWithOffsets; - public Bindable ComboIndexWithOffsetsBindable => comboIndexWithOffsetsProperty.Bindable; + public Bindable ComboIndexWithOffsetsBindable => comboIndexWithOffsets.Bindable; public int ComboIndexWithOffsets { - get => ComboIndexWithOffsetsBindable.Value; - set => ComboIndexWithOffsetsBindable.Value = value; + get => comboIndexWithOffsets.Value; + set => comboIndexWithOffsets.Value = value; } - private HitObjectProperty lastInComboProperty; + private HitObjectProperty lastInCombo; - public Bindable LastInComboBindable => lastInComboProperty.Bindable; + public Bindable LastInComboBindable => lastInCombo.Bindable; public bool LastInCombo { - get => LastInComboBindable.Value; - set => LastInComboBindable.Value = value; + get => lastInCombo.Value; + set => lastInCombo.Value = value; } protected OsuHitObject() From 2716bd41d9f34d57733943bc7ec10f9fecd2ffbe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 15:33:02 +0900 Subject: [PATCH 667/803] Use more correct json casing in `APIScoresCollection` osu-web API is already returning both of these casings for backwards compatibility, but the former will be removed at some point. https://github.com/ppy/osu-web/blob/e540276721951b72bd1b6625260da5e6b33110b0/app/Http/Controllers/BeatmapsController.php#L314-L315 --- osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs b/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs index 38c67d92f4..4ef39be5e5 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs @@ -13,7 +13,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"scores")] public List Scores; - [JsonProperty(@"userScore")] + [JsonProperty(@"user_score")] public APIScoreWithPosition UserScore; } } From e346624b14e78d4165618e3536de904001941aeb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 15:51:02 +0900 Subject: [PATCH 668/803] Fix animation changes incorrectly applying to successful completion of sliders --- .../Skinning/Default/DefaultFollowCircle.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 07b99560e5..b77d4addee 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -33,8 +34,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default protected override void OnTrackingChanged(ValueChangedEvent tracking) { + Debug.Assert(ParentObject != null); + const float duration = 300f; + if (ParentObject.Judged) + return; + if (tracking.NewValue) { if (Precision.AlmostEquals(0, Alpha)) @@ -52,10 +58,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default protected override void OnSliderEnd() { - const float fade_duration = 450f; + const float fade_duration = 300; // intentionally pile on an extra FadeOut to make it happen much faster - this.FadeOut(fade_duration / 4, Easing.Out); + this.ScaleTo(1, fade_duration, Easing.OutQuint); + this.FadeOut(fade_duration / 2, Easing.OutQuint); } } } From 5008a737747a51ecef4a8df9203767902277550b Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Tue, 19 Jul 2022 02:04:19 -0500 Subject: [PATCH 669/803] Make add item button open to the last beatmap in queue --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 9ed67bd142..04d800a10e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -231,7 +231,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!this.IsCurrentScreen()) return; - var localBeatmap = itemToEdit == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineID == itemToEdit.Beatmap.OnlineID); + int id = itemToEdit?.Beatmap.OnlineID ?? Room.Playlist.LastOrDefault().Beatmap.OnlineID; + var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == id); + var workingBeatmap = localBeatmap == null ? null : beatmapManager.GetWorkingBeatmap(localBeatmap); this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit?.ID, workingBeatmap)); From 12e5bc3f3d2ea4c109f1b77edbd88b12ef15e224 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 17:14:57 +0900 Subject: [PATCH 670/803] Fix `BeginPlayingInternal` firing actual errors when beatmap not available online --- osu.Game/Online/Spectator/OnlineSpectatorClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index 9bbc2a11c7..030ca724c4 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -66,10 +66,10 @@ namespace osu.Game.Online.Spectator await connector.Reconnect(); await BeginPlayingInternal(state); - return; } - throw; + // Exceptions can occur if, for instance, the locally played beatmap doesn't have a server-side counterpart. + // For now, let's ignore these so they don't cause unobserved exceptions to appear to the user (and sentry). } } From 09613f1af3bf92b2fb072b5b909b45b4d8f173ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 17:25:52 +0900 Subject: [PATCH 671/803] Add mention of "compatibility mode" in windows version check error message --- osu.Desktop/Program.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index cebbcb40b7..19cf7f5d46 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -37,9 +37,15 @@ namespace osu.Desktop // See https://www.mongodb.com/docs/realm/sdk/dotnet/#supported-platforms if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2)) { + // If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider + // disabling it ourselves. + // We could also better detect compatibility mode if required: + // https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730 SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, "Your operating system is too old to run osu!", - "This version of osu! requires at least Windows 8.1 to run.\nPlease upgrade your operating system or consider using an older version of osu!.", IntPtr.Zero); + "This version of osu! requires at least Windows 8.1 to run.\n" + + "Please upgrade your operating system or consider using an older version of osu!.\n\n" + + "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!", IntPtr.Zero); return; } From 0bd4aee66cb48b4fdd88937d665af076f7f3beef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 15:23:06 +0900 Subject: [PATCH 672/803] Add ignore rule for `System.ComponentModel.Component` --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 0794095854..b16e309e52 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -809,6 +809,7 @@ See the LICENCE file in the repository root for full licence text. True True True + True True True True From 6ea380d6498583d6a242e2385e6491c057c01b04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 18:59:08 +0900 Subject: [PATCH 673/803] Add new properties to `BeatmapInfo` to track online hash and updates --- osu.Game/Beatmaps/BeatmapInfo.cs | 10 ++++++++++ osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs | 7 +++++-- osu.Game/Beatmaps/BeatmapUpdater.cs | 2 ++ osu.Game/Database/RealmAccess.cs | 3 ++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 41e89d864e..3ee306cc9a 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -92,6 +92,16 @@ namespace osu.Game.Beatmaps [Indexed] public string MD5Hash { get; set; } = string.Empty; + public string OnlineMD5Hash { get; set; } = string.Empty; + + public DateTimeOffset? LastOnlineUpdate { get; set; } + + /// + /// Whether this beatmap matches the online version, based on fetched online metadata. + /// Will return true if no online metadata is available. + /// + public bool MatchesOnlineVersion => LastOnlineUpdate == null || MD5Hash == OnlineMD5Hash; + [JsonIgnore] public bool Hidden { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index a2eb76cafa..e07f18bdfb 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -102,10 +102,13 @@ namespace osu.Game.Beatmaps beatmapInfo.BeatmapSet.Status = res.BeatmapSet?.Status ?? BeatmapOnlineStatus.None; beatmapInfo.BeatmapSet.OnlineID = res.OnlineBeatmapSetID; + beatmapInfo.OnlineMD5Hash = res.MD5Hash; beatmapInfo.OnlineID = res.OnlineID; beatmapInfo.Metadata.Author.OnlineID = res.AuthorID; + beatmapInfo.LastOnlineUpdate = DateTimeOffset.Now; + logForModel(set, $"Online retrieval mapped {beatmapInfo} to {res.OnlineBeatmapSetID} / {res.OnlineID}."); } } @@ -190,7 +193,7 @@ namespace osu.Game.Beatmaps using (var cmd = db.CreateCommand()) { - cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; + cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id, checksum FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmapInfo.MD5Hash)); cmd.Parameters.Add(new SqliteParameter("@OnlineID", beatmapInfo.OnlineID)); @@ -209,8 +212,8 @@ namespace osu.Game.Beatmaps beatmapInfo.BeatmapSet.Status = status; beatmapInfo.BeatmapSet.OnlineID = reader.GetInt32(0); beatmapInfo.OnlineID = reader.GetInt32(1); - beatmapInfo.Metadata.Author.OnlineID = reader.GetInt32(3); + beatmapInfo.OnlineMD5Hash = reader.GetString(4); logForModel(set, $"Cached local retrieval for {beatmapInfo}."); return true; diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 20fa0bc7c6..d1d0cd9623 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -56,6 +56,8 @@ namespace osu.Game.Beatmaps // Before we use below, we want to invalidate. workingBeatmapCache.Invalidate(beatmapSet); + // TODO: this call currently uses the local `online.db` lookup. + // We probably don't want this to happen after initial import (as the data may be stale). onlineLookupQueue.Update(beatmapSet); foreach (var beatmap in beatmapSet.Beatmaps) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index c4d65f4f10..28870617cc 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -61,8 +61,9 @@ namespace osu.Game.Database /// 15 2022-07-13 Added LastPlayed to BeatmapInfo. /// 16 2022-07-15 Removed HasReplay from ScoreInfo. /// 17 2022-07-16 Added CountryCode to RealmUser. + /// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo. /// - private const int schema_version = 17; + private const int schema_version = 18; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. From cd39f444efe4124f7db47276ec4b69c65c1b4453 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 15:18:12 +0900 Subject: [PATCH 674/803] Expose event from `OnlineMetadataClient` rather than calling `BeatmapUpdater` directly --- osu.Game/Online/Metadata/MetadataClient.cs | 10 ++++++++++ osu.Game/Online/Metadata/OnlineMetadataClient.cs | 16 +--------------- osu.Game/OsuGameBase.cs | 2 +- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/osu.Game/Online/Metadata/MetadataClient.cs b/osu.Game/Online/Metadata/MetadataClient.cs index 1e5eeb4eb0..60867da2d7 100644 --- a/osu.Game/Online/Metadata/MetadataClient.cs +++ b/osu.Game/Online/Metadata/MetadataClient.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 System.Linq; using System.Threading.Tasks; using osu.Framework.Graphics; @@ -11,5 +13,13 @@ namespace osu.Game.Online.Metadata public abstract Task BeatmapSetsUpdated(BeatmapUpdates updates); public abstract Task GetChangesSince(int queueId); + + public Action? ChangedBeatmapSetsArrived; + + protected Task ProcessChanges(int[] beatmapSetIDs) + { + ChangedBeatmapSetsArrived?.Invoke(beatmapSetIDs.Distinct().ToArray()); + return Task.CompletedTask; + } } } diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index 1b0d1884dc..95228c380f 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; -using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Online.API; @@ -15,7 +14,6 @@ namespace osu.Game.Online.Metadata { public class OnlineMetadataClient : MetadataClient { - private readonly BeatmapUpdater beatmapUpdater; private readonly string endpoint; private IHubClientConnector? connector; @@ -24,9 +22,8 @@ namespace osu.Game.Online.Metadata private HubConnection? connection => connector?.CurrentConnection; - public OnlineMetadataClient(EndpointConfiguration endpoints, BeatmapUpdater beatmapUpdater) + public OnlineMetadataClient(EndpointConfiguration endpoints) { - this.beatmapUpdater = beatmapUpdater; endpoint = endpoints.MetadataEndpointUrl; } @@ -102,17 +99,6 @@ namespace osu.Game.Online.Metadata await ProcessChanges(updates.BeatmapSetIDs); } - protected Task ProcessChanges(int[] beatmapSetIDs) - { - foreach (int id in beatmapSetIDs) - { - Logger.Log($"Processing {id}..."); - beatmapUpdater.Queue(id); - } - - return Task.CompletedTask; - } - public override Task GetChangesSince(int queueId) { if (connector?.IsConnected.Value != true) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 4b5c9c0815..c060723152 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -287,7 +287,7 @@ namespace osu.Game dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints)); - dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints, beatmapUpdater)); + dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); BeatmapManager.ProcessBeatmap = set => beatmapUpdater.Process(set); From 6adcf82d2ed7270a840b8be7190997830ba8de00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 18:40:05 +0900 Subject: [PATCH 675/803] Add change ingester to handle passing of online changes to correct target components --- .../Beatmaps/BeatmapOnlineChangeIngest.cs | 52 +++++++++++++++++++ osu.Game/Beatmaps/BeatmapUpdater.cs | 12 +---- osu.Game/OsuGameBase.cs | 2 + 3 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs diff --git a/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs b/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs new file mode 100644 index 0000000000..937d4358d5 --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs @@ -0,0 +1,52 @@ +// 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.Linq; +using osu.Framework.Graphics; +using osu.Game.Database; +using osu.Game.Online.Metadata; + +namespace osu.Game.Beatmaps +{ + /// + /// Ingests any changes that happen externally to the client, reprocessing as required. + /// + public class BeatmapOnlineChangeIngest : Component + { + private readonly BeatmapUpdater beatmapUpdater; + private readonly RealmAccess realm; + private readonly MetadataClient metadataClient; + + public BeatmapOnlineChangeIngest(BeatmapUpdater beatmapUpdater, RealmAccess realm, MetadataClient metadataClient) + { + this.beatmapUpdater = beatmapUpdater; + this.realm = realm; + this.metadataClient = metadataClient; + + metadataClient.ChangedBeatmapSetsArrived += changesDetected; + } + + private void changesDetected(int[] beatmapSetIds) + { + // May want to batch incoming updates further if the background realm operations ever becomes a concern. + realm.Run(r => + { + foreach (int id in beatmapSetIds) + { + var matchingSet = r.All().FirstOrDefault(s => s.OnlineID == id); + + if (matchingSet != null) + beatmapUpdater.Queue(matchingSet.ToLive(realm)); + } + }); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + metadataClient.ChangedBeatmapSetsArrived -= changesDetected; + } + } +} diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index d1d0cd9623..d2c5e5616a 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Online.API; @@ -30,21 +31,12 @@ namespace osu.Game.Beatmaps onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); } - /// - /// Queue a beatmap for background processing. - /// - public void Queue(int beatmapSetId) - { - // TODO: implement - } - /// /// Queue a beatmap for background processing. /// public void Queue(Live beatmap) { - // For now, just fire off a task. - // TODO: Add actual queueing probably. + Logger.Log($"Queueing change for local beatmap {beatmap}"); Task.Factory.StartNew(() => beatmap.PerformRead(Process)); } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index c060723152..a53ad48a40 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -289,6 +289,8 @@ namespace osu.Game dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints)); dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); + AddInternal(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient)); + BeatmapManager.ProcessBeatmap = set => beatmapUpdater.Process(set); dependencies.Cache(userCache = new UserLookupCache()); From eaf4f6dbb7fab886f28b1db98346196d06ac691d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 18:06:11 +0900 Subject: [PATCH 676/803] Add beatmap update button --- .../SongSelect/TestSceneBeatmapCarousel.cs | 27 ++++- osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 + .../Carousel/DrawableCarouselBeatmap.cs | 11 +- .../Select/Carousel/SetPanelContent.cs | 7 +- .../Select/Carousel/UpdateRequiredIcon.cs | 105 ++++++++++++++++++ 5 files changed, 141 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Screens/Select/Carousel/UpdateRequiredIcon.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 66b9fa990a..453869f721 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -11,6 +11,7 @@ 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.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -43,6 +44,29 @@ namespace osu.Game.Tests.Visual.SongSelect this.rulesets = rulesets; } + [Test] + public void TestBeatmapWithOnlineUpdates() + { + var testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(); + + createCarousel(new List + { + testBeatmapSetInfo, + }); + + AddAssert("update button not visible", () => !carousel.ChildrenOfType().Any()); + + AddStep("update online hash", () => + { + testBeatmapSetInfo.Beatmaps.First().OnlineMD5Hash = "different hash"; + testBeatmapSetInfo.Beatmaps.First().LastOnlineUpdate = DateTimeOffset.Now; + + carousel.UpdateBeatmapSet(testBeatmapSetInfo); + }); + + AddUntilStep("update button visible", () => carousel.ChildrenOfType().Any()); + } + [Test] public void TestExternalRulesetChange() { @@ -825,7 +849,8 @@ namespace osu.Game.Tests.Visual.SongSelect checkVisibleItemCount(true, 15); } - private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null, int? count = null, bool randomDifficulties = false) + private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null, int? count = null, + bool randomDifficulties = false) { bool changed = false; diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 96d95b1a12..ead280a75e 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -93,5 +93,7 @@ namespace osu.Game.Beatmaps IEnumerable IBeatmapSetInfo.Beatmaps => Beatmaps; IEnumerable IHasNamedFiles.Files => Files; + + public bool AllBeatmapsUpToDate => Beatmaps.All(b => b.MatchesOnlineVersion); } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index a6532ee145..50e30c68d5 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -153,17 +153,12 @@ namespace osu.Game.Screens.Select.Carousel { Direction = FillDirection.Horizontal, Spacing = new Vector2(4, 0), + Scale = new Vector2(0.8f), AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new TopLocalRank(beatmapInfo) - { - Scale = new Vector2(0.8f), - }, - starCounter = new StarCounter - { - Scale = new Vector2(0.8f), - } + new TopLocalRank(beatmapInfo), + starCounter = new StarCounter() } } } diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 577b3f5f64..050425f9f1 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -61,14 +61,15 @@ namespace osu.Game.Screens.Select.Carousel Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Top = 5 }, - Children = new Drawable[] + Spacing = new Vector2(5), + Children = new[] { + beatmapSet.AllBeatmapsUpToDate ? Empty() : new UpdateRequiredIcon(beatmapSet), new BeatmapSetOnlineStatusPill { AutoSizeAxes = Axes.Both, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 5 }, TextSize = 11, TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, Status = beatmapSet.Status @@ -76,6 +77,8 @@ namespace osu.Game.Screens.Select.Carousel new FillFlowContainer { AutoSizeAxes = Axes.Both, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, Spacing = new Vector2(3), ChildrenEnumerable = getDifficultyIcons(), }, diff --git a/osu.Game/Screens/Select/Carousel/UpdateRequiredIcon.cs b/osu.Game/Screens/Select/Carousel/UpdateRequiredIcon.cs new file mode 100644 index 0000000000..42539f2836 --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/UpdateRequiredIcon.cs @@ -0,0 +1,105 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; + +namespace osu.Game.Screens.Select.Carousel +{ + public class UpdateRequiredIcon : OsuAnimatedButton + { + private readonly BeatmapSetInfo beatmapSetInfo; + private SpriteIcon icon; + + public UpdateRequiredIcon(BeatmapSetInfo beatmapSetInfo) + { + this.beatmapSetInfo = beatmapSetInfo; + + AutoSizeAxes = Axes.Both; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + } + + [Resolved] + private BeatmapModelDownloader beatmaps { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + const float icon_size = 14; + + Content.Anchor = Anchor.CentreLeft; + Content.Origin = Anchor.CentreLeft; + + Content.AddRange(new Drawable[] + { + new FillFlowContainer + { + Padding = new MarginPadding { Horizontal = 5, Vertical = 3 }, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(4), + Children = new Drawable[] + { + new Container + { + Size = new Vector2(icon_size), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new Drawable[] + { + icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.SyncAlt, + Size = new Vector2(icon_size), + }, + } + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.Default.With(weight: FontWeight.Bold), + Text = "Update", + } + } + }, + }); + + TooltipText = "Update beatmap with online changes"; + + Action = () => beatmaps.Download(beatmapSetInfo); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + icon.Spin(4000, RotationDirection.Clockwise); + } + + protected override bool OnHover(HoverEvent e) + { + icon.Spin(400, RotationDirection.Clockwise); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + icon.Spin(4000, RotationDirection.Clockwise); + base.OnHoverLost(e); + } + } +} From da360af15af89dc660cbc948a4a195cf0e9515bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 18:01:25 +0900 Subject: [PATCH 677/803] Fix vertical centering of button --- osu.Game/Screens/Select/Carousel/SetPanelContent.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 050425f9f1..8b921fe400 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -64,7 +64,17 @@ namespace osu.Game.Screens.Select.Carousel Spacing = new Vector2(5), Children = new[] { - beatmapSet.AllBeatmapsUpToDate ? Empty() : new UpdateRequiredIcon(beatmapSet), + beatmapSet.AllBeatmapsUpToDate + ? Empty() + : new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + new UpdateRequiredIcon(beatmapSet), + } + }, new BeatmapSetOnlineStatusPill { AutoSizeAxes = Axes.Both, From a16bf35581551befae89f3db8db66b6854b6811e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 18:16:30 +0900 Subject: [PATCH 678/803] Rename button class and add basic progress display --- .../Select/Carousel/SetPanelContent.cs | 2 +- ...uiredIcon.cs => UpdateBeatmapSetButton.cs} | 38 +++++++++++++++++-- 2 files changed, 35 insertions(+), 5 deletions(-) rename osu.Game/Screens/Select/Carousel/{UpdateRequiredIcon.cs => UpdateBeatmapSetButton.cs} (73%) diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 8b921fe400..a95d9078a2 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Select.Carousel RelativeSizeAxes = Axes.Y, Children = new Drawable[] { - new UpdateRequiredIcon(beatmapSet), + new UpdateBeatmapSetButton(beatmapSet), } }, new BeatmapSetOnlineStatusPill diff --git a/osu.Game/Screens/Select/Carousel/UpdateRequiredIcon.cs b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs similarity index 73% rename from osu.Game/Screens/Select/Carousel/UpdateRequiredIcon.cs rename to osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs index 42539f2836..a11a29a5c6 100644 --- a/osu.Game/Screens/Select/Carousel/UpdateRequiredIcon.cs +++ b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Beatmaps; @@ -12,15 +13,18 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Select.Carousel { - public class UpdateRequiredIcon : OsuAnimatedButton + public class UpdateBeatmapSetButton : OsuAnimatedButton { private readonly BeatmapSetInfo beatmapSetInfo; private SpriteIcon icon; - public UpdateRequiredIcon(BeatmapSetInfo beatmapSetInfo) + private Box progressFill; + + public UpdateBeatmapSetButton(BeatmapSetInfo beatmapSetInfo) { this.beatmapSetInfo = beatmapSetInfo; @@ -31,7 +35,7 @@ namespace osu.Game.Screens.Select.Carousel } [Resolved] - private BeatmapModelDownloader beatmaps { get; set; } = null!; + private BeatmapModelDownloader beatmapDownloader { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -43,6 +47,14 @@ namespace osu.Game.Screens.Select.Carousel Content.AddRange(new Drawable[] { + progressFill = new Box + { + Colour = Color4.White, + Alpha = 0.2f, + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + Width = 0, + }, new FillFlowContainer { Padding = new MarginPadding { Horizontal = 5, Vertical = 3 }, @@ -80,7 +92,12 @@ namespace osu.Game.Screens.Select.Carousel TooltipText = "Update beatmap with online changes"; - Action = () => beatmaps.Download(beatmapSetInfo); + Action = () => + { + beatmapDownloader.Download(beatmapSetInfo); + + attachExistingDownload(); + }; } protected override void LoadComplete() @@ -90,6 +107,19 @@ namespace osu.Game.Screens.Select.Carousel icon.Spin(4000, RotationDirection.Clockwise); } + private void attachExistingDownload() + { + var download = beatmapDownloader.GetExistingDownload(beatmapSetInfo); + + if (download != null) + { + Enabled.Value = false; + TooltipText = string.Empty; + + download.DownloadProgressed += progress => progressFill.ResizeWidthTo(progress, 100); + } + } + protected override bool OnHover(HoverEvent e) { icon.Spin(400, RotationDirection.Clockwise); From f6de76e057ba83855b9b662aed1db1751d08f824 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 18:50:27 +0900 Subject: [PATCH 679/803] Move test to stand-alone class and add full ui testing --- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 2 +- .../SongSelect/TestSceneBeatmapCarousel.cs | 24 ---- .../TestSceneUpdateBeatmapSetButton.cs | 104 ++++++++++++++++++ 3 files changed, 105 insertions(+), 25 deletions(-) create mode 100644 osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index fcf69bf6f2..31bc6dacf8 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -246,7 +246,7 @@ namespace osu.Game.Tests.Online => new TestDownloadRequest(set); } - private class TestDownloadRequest : ArchiveDownloadRequest + internal class TestDownloadRequest : ArchiveDownloadRequest { public new void SetProgress(float progress) => base.SetProgress(progress); public new void TriggerSuccess(string filename) => base.TriggerSuccess(filename); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 453869f721..e574ee30fb 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -11,7 +11,6 @@ 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.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -44,29 +43,6 @@ namespace osu.Game.Tests.Visual.SongSelect this.rulesets = rulesets; } - [Test] - public void TestBeatmapWithOnlineUpdates() - { - var testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(); - - createCarousel(new List - { - testBeatmapSetInfo, - }); - - AddAssert("update button not visible", () => !carousel.ChildrenOfType().Any()); - - AddStep("update online hash", () => - { - testBeatmapSetInfo.Beatmaps.First().OnlineMD5Hash = "different hash"; - testBeatmapSetInfo.Beatmaps.First().LastOnlineUpdate = DateTimeOffset.Now; - - carousel.UpdateBeatmapSet(testBeatmapSetInfo); - }); - - AddUntilStep("update button visible", () => carousel.ChildrenOfType().Any()); - } - [Test] public void TestExternalRulesetChange() { diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs new file mode 100644 index 0000000000..03336ef488 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.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; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Carousel; +using osu.Game.Tests.Online; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Visual.SongSelect +{ + [TestFixture] + public class TestSceneUpdateBeatmapSetButton : OsuManualInputManagerTestScene + { + private BeatmapCarousel carousel = null!; + + private TestSceneOnlinePlayBeatmapAvailabilityTracker.TestBeatmapModelDownloader beatmapDownloader = null!; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + var importer = parent.Get(); + + dependencies.CacheAs(beatmapDownloader = new TestSceneOnlinePlayBeatmapAvailabilityTracker.TestBeatmapModelDownloader(importer, API)); + return dependencies; + } + + [Test] + public void TestBeatmapWithOnlineUpdates() + { + ArchiveDownloadRequest? downloadRequest = null; + + UpdateBeatmapSetButton? getUpdateButton() => carousel.ChildrenOfType().SingleOrDefault(); + + var testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(); + + AddStep("create carousel", () => + { + Child = carousel = new BeatmapCarousel + { + RelativeSizeAxes = Axes.Both, + BeatmapSets = new List + { + testBeatmapSetInfo, + } + }; + }); + + AddUntilStep("wait for load", () => carousel.BeatmapSetsLoaded); + + AddAssert("update button not visible", () => getUpdateButton() == null); + + AddStep("update online hash", () => + { + testBeatmapSetInfo.Beatmaps.First().OnlineMD5Hash = "different hash"; + testBeatmapSetInfo.Beatmaps.First().LastOnlineUpdate = DateTimeOffset.Now; + + carousel.UpdateBeatmapSet(testBeatmapSetInfo); + }); + + AddUntilStep("update button visible", () => getUpdateButton() != null); + + AddStep("click button", () => getUpdateButton()?.TriggerClick()); + + AddUntilStep("wait for download started", () => + { + downloadRequest = beatmapDownloader.GetExistingDownload(testBeatmapSetInfo); + return downloadRequest != null; + }); + + AddUntilStep("progress download to completion", () => + { + if (downloadRequest is TestSceneOnlinePlayBeatmapAvailabilityTracker.TestDownloadRequest testRequest) + { + testRequest.SetProgress(testRequest.Progress + 0.1f); + + if (testRequest.Progress >= 1) + { + testRequest.TriggerSuccess(); + + // usually this would be done by the import process. + testBeatmapSetInfo.Beatmaps.First().MD5Hash = "different hash"; + testBeatmapSetInfo.Beatmaps.First().LastOnlineUpdate = DateTimeOffset.Now; + + // usually this would be done by a realm subscription. + carousel.UpdateBeatmapSet(testBeatmapSetInfo); + return true; + } + } + + return false; + }); + } + } +} From 17046b0553ef0240cb91b3289a5cff70fb718082 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 19:01:23 +0900 Subject: [PATCH 680/803] Add basic handling of download failures --- .../TestSceneUpdateBeatmapSetButton.cs | 68 ++++++++++++++++--- osu.Game/Database/ModelDownloader.cs | 2 +- .../Select/Carousel/UpdateBeatmapSetButton.cs | 13 ++-- 3 files changed, 69 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs index 03336ef488..bae3b66ed9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs @@ -24,6 +24,8 @@ namespace osu.Game.Tests.Visual.SongSelect private TestSceneOnlinePlayBeatmapAvailabilityTracker.TestBeatmapModelDownloader beatmapDownloader = null!; + private BeatmapSetInfo testBeatmapSetInfo = null!; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -34,15 +36,11 @@ namespace osu.Game.Tests.Visual.SongSelect return dependencies; } - [Test] - public void TestBeatmapWithOnlineUpdates() + private UpdateBeatmapSetButton? getUpdateButton() => carousel.ChildrenOfType().SingleOrDefault(); + + [SetUpSteps] + public void SetUpSteps() { - ArchiveDownloadRequest? downloadRequest = null; - - UpdateBeatmapSetButton? getUpdateButton() => carousel.ChildrenOfType().SingleOrDefault(); - - var testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(); - AddStep("create carousel", () => { Child = carousel = new BeatmapCarousel @@ -50,7 +48,7 @@ namespace osu.Game.Tests.Visual.SongSelect RelativeSizeAxes = Axes.Both, BeatmapSets = new List { - testBeatmapSetInfo, + (testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo()), } }; }); @@ -58,6 +56,12 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for load", () => carousel.BeatmapSetsLoaded); AddAssert("update button not visible", () => getUpdateButton() == null); + } + + [Test] + public void TestDownloadToCompletion() + { + ArchiveDownloadRequest? downloadRequest = null; AddStep("update online hash", () => { @@ -77,6 +81,8 @@ namespace osu.Game.Tests.Visual.SongSelect return downloadRequest != null; }); + AddUntilStep("wait for button disabled", () => getUpdateButton()?.Enabled.Value == false); + AddUntilStep("progress download to completion", () => { if (downloadRequest is TestSceneOnlinePlayBeatmapAvailabilityTracker.TestDownloadRequest testRequest) @@ -100,5 +106,49 @@ namespace osu.Game.Tests.Visual.SongSelect return false; }); } + + [Test] + public void TestDownloadFailed() + { + ArchiveDownloadRequest? downloadRequest = null; + + AddStep("update online hash", () => + { + testBeatmapSetInfo.Beatmaps.First().OnlineMD5Hash = "different hash"; + testBeatmapSetInfo.Beatmaps.First().LastOnlineUpdate = DateTimeOffset.Now; + + carousel.UpdateBeatmapSet(testBeatmapSetInfo); + }); + + AddUntilStep("update button visible", () => getUpdateButton() != null); + + AddStep("click button", () => getUpdateButton()?.TriggerClick()); + + AddUntilStep("wait for download started", () => + { + downloadRequest = beatmapDownloader.GetExistingDownload(testBeatmapSetInfo); + return downloadRequest != null; + }); + + AddUntilStep("wait for button disabled", () => getUpdateButton()?.Enabled.Value == false); + + AddUntilStep("progress download to failure", () => + { + if (downloadRequest is TestSceneOnlinePlayBeatmapAvailabilityTracker.TestDownloadRequest testRequest) + { + testRequest.SetProgress(testRequest.Progress + 0.1f); + + if (testRequest.Progress >= 0.5f) + { + testRequest.TriggerFailure(new Exception()); + return true; + } + } + + return false; + }); + + AddUntilStep("wait for button enabled", () => getUpdateButton()?.Enabled.Value == true); + } } } diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index 76717fd46f..a4d52426ab 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -101,7 +101,7 @@ namespace osu.Game.Database notification.State = ProgressNotificationState.Cancelled; if (!(error is OperationCanceledException)) - Logger.Error(error, $"{importer.HumanisedModelName.Titleize()} download failed!"); + Logger.Error(error, $"{importer?.HumanisedModelName.Titleize()} download failed!"); } } diff --git a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs index a11a29a5c6..89404b2878 100644 --- a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs +++ b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs @@ -90,12 +90,9 @@ namespace osu.Game.Screens.Select.Carousel }, }); - TooltipText = "Update beatmap with online changes"; - Action = () => { beatmapDownloader.Download(beatmapSetInfo); - attachExistingDownload(); }; } @@ -116,7 +113,15 @@ namespace osu.Game.Screens.Select.Carousel Enabled.Value = false; TooltipText = string.Empty; - download.DownloadProgressed += progress => progressFill.ResizeWidthTo(progress, 100); + download.DownloadProgressed += progress => progressFill.ResizeWidthTo(progress, 100, Easing.OutQuint); + download.Failure += _ => attachExistingDownload(); + } + else + { + Enabled.Value = true; + TooltipText = "Update beatmap with online changes"; + + progressFill.ResizeWidthTo(0, 100, Easing.OutQuint); } } From 07874efa7f732ea1a2abb6a7a5f7f67d4a11a09b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 19:39:51 +0900 Subject: [PATCH 681/803] Set last online update to actual value provided by data source --- osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs | 9 ++++++--- osu.Game/Online/API/Requests/Responses/APIBeatmap.cs | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index e07f18bdfb..580dcee18c 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -102,13 +102,14 @@ namespace osu.Game.Beatmaps beatmapInfo.BeatmapSet.Status = res.BeatmapSet?.Status ?? BeatmapOnlineStatus.None; beatmapInfo.BeatmapSet.OnlineID = res.OnlineBeatmapSetID; + beatmapInfo.OnlineMD5Hash = res.MD5Hash; + beatmapInfo.LastOnlineUpdate = res.LastUpdated; + beatmapInfo.OnlineID = res.OnlineID; beatmapInfo.Metadata.Author.OnlineID = res.AuthorID; - beatmapInfo.LastOnlineUpdate = DateTimeOffset.Now; - logForModel(set, $"Online retrieval mapped {beatmapInfo} to {res.OnlineBeatmapSetID} / {res.OnlineID}."); } } @@ -193,7 +194,7 @@ namespace osu.Game.Beatmaps using (var cmd = db.CreateCommand()) { - cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id, checksum FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; + cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmapInfo.MD5Hash)); cmd.Parameters.Add(new SqliteParameter("@OnlineID", beatmapInfo.OnlineID)); @@ -213,7 +214,9 @@ namespace osu.Game.Beatmaps beatmapInfo.BeatmapSet.OnlineID = reader.GetInt32(0); beatmapInfo.OnlineID = reader.GetInt32(1); beatmapInfo.Metadata.Author.OnlineID = reader.GetInt32(3); + beatmapInfo.OnlineMD5Hash = reader.GetString(4); + beatmapInfo.LastOnlineUpdate = reader.GetDateTimeOffset(5); logForModel(set, $"Cached local retrieval for {beatmapInfo}."); return true; diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index 735fde333d..3fee81cf33 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -81,6 +81,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"max_combo")] public int? MaxCombo { get; set; } + [JsonProperty(@"last_updated")] + public DateTimeOffset LastUpdated { get; set; } + public double BPM { get; set; } #region Implementation of IBeatmapInfo From 30daa0fd447282ec9ecaeb3755bc6ec0d6479332 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 19:37:04 +0900 Subject: [PATCH 682/803] Add ranked and submitted date storage and filtering --- osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs | 6 +++++- osu.Game/Beatmaps/BeatmapSetInfo.cs | 10 ++++++++++ osu.Game/Database/RealmAccess.cs | 3 ++- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 ++ osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 7 +++++++ osu.Game/Screens/Select/Filter/SortMode.cs | 3 +++ 6 files changed, 29 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index 580dcee18c..d3be240d4c 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -102,6 +102,8 @@ namespace osu.Game.Beatmaps beatmapInfo.BeatmapSet.Status = res.BeatmapSet?.Status ?? BeatmapOnlineStatus.None; beatmapInfo.BeatmapSet.OnlineID = res.OnlineBeatmapSetID; + beatmapInfo.BeatmapSet.DateRanked = res.BeatmapSet?.Ranked; + beatmapInfo.BeatmapSet.DateSubmitted = res.BeatmapSet?.Submitted; beatmapInfo.OnlineMD5Hash = res.MD5Hash; beatmapInfo.LastOnlineUpdate = res.LastUpdated; @@ -194,7 +196,8 @@ namespace osu.Game.Beatmaps using (var cmd = db.CreateCommand()) { - cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; + cmd.CommandText = + "SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmapInfo.MD5Hash)); cmd.Parameters.Add(new SqliteParameter("@OnlineID", beatmapInfo.OnlineID)); @@ -212,6 +215,7 @@ namespace osu.Game.Beatmaps beatmapInfo.BeatmapSet.Status = status; beatmapInfo.BeatmapSet.OnlineID = reader.GetInt32(0); + // TODO: DateSubmitted and DateRanked are not provided by local cache. beatmapInfo.OnlineID = reader.GetInt32(1); beatmapInfo.Metadata.Author.OnlineID = reader.GetInt32(3); diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index ead280a75e..b404f0b34d 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -26,6 +26,16 @@ namespace osu.Game.Beatmaps public DateTimeOffset DateAdded { get; set; } + /// + /// The date this beatmap set was first submitted. + /// + public DateTimeOffset? DateSubmitted { get; set; } + + /// + /// The date this beatmap set was ranked. + /// + public DateTimeOffset? DateRanked { get; set; } + [JsonIgnore] public IBeatmapMetadataInfo Metadata => Beatmaps.FirstOrDefault()?.Metadata ?? new BeatmapMetadata(); diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 28870617cc..dff2bdddbd 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -62,8 +62,9 @@ namespace osu.Game.Database /// 16 2022-07-15 Removed HasReplay from ScoreInfo. /// 17 2022-07-16 Added CountryCode to RealmUser. /// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo. + /// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo. /// - private const int schema_version = 18; + private const int schema_version = 19; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index c57cbcfba4..81734745c4 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -55,6 +55,8 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(BeatmapInfo.Metadata.Artist) || criteria.Artist.Matches(BeatmapInfo.Metadata.ArtistUnicode); + match &= criteria.Sort != SortMode.DateRanked || BeatmapInfo.BeatmapSet?.DateRanked != null; + match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarRating); if (match && criteria.SearchTerms.Length > 0) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 94d911692c..bd7b1f12a4 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -81,6 +81,13 @@ namespace osu.Game.Screens.Select.Carousel case SortMode.DateAdded: return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded); + case SortMode.DateRanked: + // Beatmaps which have no ranked date should already be filtered away in this mode. + if (BeatmapSet.DateRanked == null || otherSet.BeatmapSet.DateRanked == null) + return 0; + + return otherSet.BeatmapSet.DateRanked.Value.CompareTo(BeatmapSet.DateRanked.Value); + case SortMode.LastPlayed: return -compareUsingAggregateMax(otherSet, b => (b.LastPlayed ?? DateTimeOffset.MinValue).ToUnixTimeSeconds()); diff --git a/osu.Game/Screens/Select/Filter/SortMode.cs b/osu.Game/Screens/Select/Filter/SortMode.cs index 4227114618..1e60ea3bac 100644 --- a/osu.Game/Screens/Select/Filter/SortMode.cs +++ b/osu.Game/Screens/Select/Filter/SortMode.cs @@ -23,6 +23,9 @@ namespace osu.Game.Screens.Select.Filter [Description("Date Added")] DateAdded, + [Description("Date Ranked")] + DateRanked, + [Description("Last Played")] LastPlayed, From 842fe32003244c5934cf0f1b8bbeb8648c74f08d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 19 Jul 2022 19:57:36 +0900 Subject: [PATCH 683/803] Update test values --- .../OsuDifficultyCalculatorTest.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index bb593c2fb3..46f7c461f8 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -17,18 +17,18 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.6972307565739273d, 206, "diffcalc-test")] - [TestCase(1.4484754139145539d, 45, "zero-length-sliders")] + [TestCase(6.6369583000323935d, 206, "diffcalc-test")] + [TestCase(1.4476531024675374d, 45, "zero-length-sliders")] public void Test(double expectedStarRating, int expectedMaxCombo, string name) => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(8.9382559208689809d, 206, "diffcalc-test")] - [TestCase(1.7548875851757628d, 45, "zero-length-sliders")] + [TestCase(8.8816128335486386d, 206, "diffcalc-test")] + [TestCase(1.7540389962596916d, 45, "zero-length-sliders")] public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime()); - [TestCase(6.6972307218715166d, 239, "diffcalc-test")] - [TestCase(1.4484754139145537d, 54, "zero-length-sliders")] + [TestCase(6.6369583000323935d, 239, "diffcalc-test")] + [TestCase(1.4476531024675374d, 54, "zero-length-sliders")] public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic()); From 6357223341200e1b5ed37c03dd4ae21b7b706952 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 20:06:19 +0900 Subject: [PATCH 684/803] Fix incorrect DI fetch and apply nullability to `ModelDownloader` --- .../TestSceneUpdateBeatmapSetButton.cs | 2 +- osu.Game/Beatmaps/BeatmapModelDownloader.cs | 4 +--- osu.Game/Database/ModelDownloader.cs | 18 ++++++++---------- osu.Game/Scoring/ScoreModelDownloader.cs | 2 -- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs index bae3b66ed9..a95f145897 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.SongSelect { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - var importer = parent.Get(); + var importer = parent.Get(); dependencies.CacheAs(beatmapDownloader = new TestSceneOnlinePlayBeatmapAvailabilityTracker.TestBeatmapModelDownloader(importer, API)); return dependencies; diff --git a/osu.Game/Beatmaps/BeatmapModelDownloader.cs b/osu.Game/Beatmaps/BeatmapModelDownloader.cs index 74d583fe7e..4295def5c3 100644 --- a/osu.Game/Beatmaps/BeatmapModelDownloader.cs +++ b/osu.Game/Beatmaps/BeatmapModelDownloader.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -14,7 +12,7 @@ namespace osu.Game.Beatmaps protected override ArchiveDownloadRequest CreateDownloadRequest(IBeatmapSetInfo set, bool minimiseDownloadSize) => new DownloadBeatmapSetRequest(set, minimiseDownloadSize); - public override ArchiveDownloadRequest GetExistingDownload(IBeatmapSetInfo model) + public override ArchiveDownloadRequest? GetExistingDownload(IBeatmapSetInfo model) => CurrentDownloads.Find(r => r.Model.OnlineID == model.OnlineID); public BeatmapModelDownloader(IModelImporter beatmapImporter, IAPIProvider api) diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index a4d52426ab..02bcb342e4 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -19,18 +17,18 @@ namespace osu.Game.Database where TModel : class, IHasGuidPrimaryKey, ISoftDelete, IEquatable, T where T : class { - public Action PostNotification { protected get; set; } + public Action? PostNotification { protected get; set; } - public event Action> DownloadBegan; + public event Action>? DownloadBegan; - public event Action> DownloadFailed; + public event Action>? DownloadFailed; private readonly IModelImporter importer; - private readonly IAPIProvider api; + private readonly IAPIProvider? api; protected readonly List> CurrentDownloads = new List>(); - protected ModelDownloader(IModelImporter importer, IAPIProvider api) + protected ModelDownloader(IModelImporter importer, IAPIProvider? api) { this.importer = importer; this.api = api; @@ -87,7 +85,7 @@ namespace osu.Game.Database CurrentDownloads.Add(request); PostNotification?.Invoke(notification); - api.PerformAsync(request); + api?.PerformAsync(request); DownloadBegan?.Invoke(request); return true; @@ -101,11 +99,11 @@ namespace osu.Game.Database notification.State = ProgressNotificationState.Cancelled; if (!(error is OperationCanceledException)) - Logger.Error(error, $"{importer?.HumanisedModelName.Titleize()} download failed!"); + Logger.Error(error, $"{importer.HumanisedModelName.Titleize()} download failed!"); } } - public abstract ArchiveDownloadRequest GetExistingDownload(T model); + public abstract ArchiveDownloadRequest? GetExistingDownload(T model); private bool canDownload(T model) => GetExistingDownload(model) == null && api != null; diff --git a/osu.Game/Scoring/ScoreModelDownloader.cs b/osu.Game/Scoring/ScoreModelDownloader.cs index 8625c6c5d0..514b7a57de 100644 --- a/osu.Game/Scoring/ScoreModelDownloader.cs +++ b/osu.Game/Scoring/ScoreModelDownloader.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Database; using osu.Game.Extensions; using osu.Game.Online.API; From 1f9f2b413e2218bc6353ffc496c319f35eb7ea96 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 3 Jul 2022 22:19:35 +0800 Subject: [PATCH 685/803] Remove the nullable disable annotation. Also, mark as nullable for some properties. --- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 4 +--- .../Rulesets/Mods/IApplicableAfterBeatmapConversion.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableFailOverride.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableMod.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToAudio.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs | 2 -- .../Rulesets/Mods/IApplicableToBeatmapConverter.cs | 2 -- .../Rulesets/Mods/IApplicableToBeatmapProcessor.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs | 2 -- .../Rulesets/Mods/IApplicableToDrawableHitObject.cs | 2 -- .../Rulesets/Mods/IApplicableToDrawableHitObjects.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToHUD.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToHealthProcessor.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToHitObject.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToPlayer.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToRate.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToSample.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToTrack.cs | 2 -- osu.Game/Rulesets/Mods/ICreateReplay.cs | 2 -- osu.Game/Rulesets/Mods/ICreateReplayData.cs | 4 +--- osu.Game/Rulesets/Mods/IHasSeed.cs | 2 -- osu.Game/Rulesets/Mods/IMod.cs | 2 -- osu.Game/Rulesets/Mods/IReadFromConfig.cs | 2 -- osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs | 2 -- osu.Game/Rulesets/Mods/MetronomeBeat.cs | 2 -- osu.Game/Rulesets/Mods/Mod.cs | 4 +--- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 4 +--- osu.Game/Rulesets/Mods/ModAutoplay.cs | 2 -- osu.Game/Rulesets/Mods/ModBarrelRoll.cs | 2 -- osu.Game/Rulesets/Mods/ModBlockFail.cs | 2 -- osu.Game/Rulesets/Mods/ModCinema.cs | 2 -- osu.Game/Rulesets/Mods/ModClassic.cs | 2 -- osu.Game/Rulesets/Mods/ModDaycore.cs | 2 -- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 2 -- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 2 -- osu.Game/Rulesets/Mods/ModEasy.cs | 2 -- osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs | 2 -- osu.Game/Rulesets/Mods/ModExtensions.cs | 2 -- osu.Game/Rulesets/Mods/ModFailCondition.cs | 2 -- osu.Game/Rulesets/Mods/ModFlashlight.cs | 2 -- osu.Game/Rulesets/Mods/ModHalfTime.cs | 2 -- osu.Game/Rulesets/Mods/ModHardRock.cs | 2 -- osu.Game/Rulesets/Mods/ModHidden.cs | 2 -- osu.Game/Rulesets/Mods/ModMirror.cs | 2 -- osu.Game/Rulesets/Mods/ModMuted.cs | 2 -- osu.Game/Rulesets/Mods/ModNightcore.cs | 10 ++++------ osu.Game/Rulesets/Mods/ModNoFail.cs | 2 -- osu.Game/Rulesets/Mods/ModNoMod.cs | 2 -- osu.Game/Rulesets/Mods/ModNoScope.cs | 2 -- osu.Game/Rulesets/Mods/ModPerfect.cs | 2 -- osu.Game/Rulesets/Mods/ModRandom.cs | 2 -- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 2 -- osu.Game/Rulesets/Mods/ModRelax.cs | 2 -- osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 2 -- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 4 +--- osu.Game/Rulesets/Mods/ModType.cs | 2 -- osu.Game/Rulesets/Mods/ModWindDown.cs | 2 -- osu.Game/Rulesets/Mods/ModWindUp.cs | 2 -- osu.Game/Rulesets/Mods/ModWithVisibilityAdjustment.cs | 8 +++----- osu.Game/Rulesets/Mods/MultiMod.cs | 2 -- osu.Game/Rulesets/Mods/UnknownMod.cs | 2 -- 63 files changed, 12 insertions(+), 138 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index eb5f97bcf7..34e9fe40a3 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Bindables; using osu.Game.Beatmaps; @@ -29,7 +27,7 @@ namespace osu.Game.Rulesets.Mods /// /// A function that can extract the current value of this setting from a beatmap difficulty for display purposes. /// - public Func ReadCurrentFromDifficulty; + public Func? ReadCurrentFromDifficulty; public float Precision { diff --git a/osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.cs b/osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.cs index 9286f682d1..d45311675d 100644 --- a/osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.cs +++ b/osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs index d0b54f835b..8c99d739cb 100644 --- a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs +++ b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mods { /// diff --git a/osu.Game/Rulesets/Mods/IApplicableMod.cs b/osu.Game/Rulesets/Mods/IApplicableMod.cs index 7675bd89ef..8ca1a3f8a5 100644 --- a/osu.Game/Rulesets/Mods/IApplicableMod.cs +++ b/osu.Game/Rulesets/Mods/IApplicableMod.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mods { /// diff --git a/osu.Game/Rulesets/Mods/IApplicableToAudio.cs b/osu.Game/Rulesets/Mods/IApplicableToAudio.cs index de76790aee..901da7af55 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToAudio.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToAudio.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mods { public interface IApplicableToAudio : IApplicableToTrack, IApplicableToSample diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs index 278b4794c5..cff669bf53 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs index a5ccea1873..8cefb02904 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmapProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmapProcessor.cs index c653a674ef..e23a5d8d99 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToBeatmapProcessor.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmapProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs index 1447511de9..42b520ab26 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs index f559ed04d7..c8a9ff2f9a 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObjects.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObjects.cs index 8bf2c3810e..7f926dd8b8 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObjects.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObjects.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Framework.Extensions.IEnumerableExtensions; diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs index ace3af62a1..b012beb0c0 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; diff --git a/osu.Game/Rulesets/Mods/IApplicableToHUD.cs b/osu.Game/Rulesets/Mods/IApplicableToHUD.cs index b5fe299b24..4fb535a0b3 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToHUD.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToHUD.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToHealthProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableToHealthProcessor.cs index a58f8640fd..2676060efa 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToHealthProcessor.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToHealthProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs index d9fa993393..f7f81c92c0 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToPlayer.cs b/osu.Game/Rulesets/Mods/IApplicableToPlayer.cs index c28935607f..bf78428470 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToPlayer.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToPlayer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToRate.cs b/osu.Game/Rulesets/Mods/IApplicableToRate.cs index c66c8f49a1..f613867132 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToRate.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToRate.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mods { /// diff --git a/osu.Game/Rulesets/Mods/IApplicableToSample.cs b/osu.Game/Rulesets/Mods/IApplicableToSample.cs index 97ed0fbf7e..efd88f2399 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToSample.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToSample.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Audio; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs index 24c1ac9afe..b93e50921f 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; diff --git a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs index 358ef71cc0..deecd4bf1f 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Audio; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/ICreateReplay.cs b/osu.Game/Rulesets/Mods/ICreateReplay.cs index e77f4c49b9..1e5eeca92c 100644 --- a/osu.Game/Rulesets/Mods/ICreateReplay.cs +++ b/osu.Game/Rulesets/Mods/ICreateReplay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Game.Beatmaps; diff --git a/osu.Game/Rulesets/Mods/ICreateReplayData.cs b/osu.Game/Rulesets/Mods/ICreateReplayData.cs index 6058380eb3..d4587b673c 100644 --- a/osu.Game/Rulesets/Mods/ICreateReplayData.cs +++ b/osu.Game/Rulesets/Mods/ICreateReplayData.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; @@ -45,7 +43,7 @@ namespace osu.Game.Rulesets.Mods /// public readonly ModCreatedUser User; - public ModReplayData(Replay replay, ModCreatedUser user = null) + public ModReplayData(Replay replay, ModCreatedUser? user = null) { Replay = replay; User = user ?? new ModCreatedUser(); diff --git a/osu.Game/Rulesets/Mods/IHasSeed.cs b/osu.Game/Rulesets/Mods/IHasSeed.cs index fd2161ac09..001a9d214c 100644 --- a/osu.Game/Rulesets/Mods/IHasSeed.cs +++ b/osu.Game/Rulesets/Mods/IHasSeed.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index 349cc7dd5a..30fa1ea8cb 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game/Rulesets/Mods/IReadFromConfig.cs b/osu.Game/Rulesets/Mods/IReadFromConfig.cs index ee6fb6364f..d66fabce70 100644 --- a/osu.Game/Rulesets/Mods/IReadFromConfig.cs +++ b/osu.Game/Rulesets/Mods/IReadFromConfig.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Configuration; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs b/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs index 3aad858af5..7cf480a11b 100644 --- a/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs +++ b/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/MetronomeBeat.cs b/osu.Game/Rulesets/Mods/MetronomeBeat.cs index b26052a37e..149af1e30a 100644 --- a/osu.Game/Rulesets/Mods/MetronomeBeat.cs +++ b/osu.Game/Rulesets/Mods/MetronomeBeat.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 17093e3033..7fdb03a7f3 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -117,7 +115,7 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual Type[] IncompatibleMods => Array.Empty(); - private IReadOnlyList settingsBacking; + private IReadOnlyList? settingsBacking; /// /// A list of the all settings within this mod. diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index aea6e12a07..54ee4554b1 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -79,7 +77,7 @@ namespace osu.Game.Rulesets.Mods // Apply a fixed rate change when missing, allowing the player to catch up when the rate is too fast. private const double rate_change_on_miss = 0.95d; - private IAdjustableAudioComponent track; + private IAdjustableAudioComponent? track; private double targetRate = 1d; /// diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 6d786fc8e2..0ebe11b393 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs index bd0f2bfe59..bacb953f76 100644 --- a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs +++ b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Bindables; using osu.Framework.Extensions; diff --git a/osu.Game/Rulesets/Mods/ModBlockFail.cs b/osu.Game/Rulesets/Mods/ModBlockFail.cs index a6a8244480..8a9b0cddc8 100644 --- a/osu.Game/Rulesets/Mods/ModBlockFail.cs +++ b/osu.Game/Rulesets/Mods/ModBlockFail.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Game.Configuration; using osu.Game.Screens.Play; diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 6e7bd6350e..99c4e71d1f 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game/Rulesets/Mods/ModClassic.cs b/osu.Game/Rulesets/Mods/ModClassic.cs index b4885ff16e..1159955e11 100644 --- a/osu.Game/Rulesets/Mods/ModClassic.cs +++ b/osu.Game/Rulesets/Mods/ModClassic.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics.Sprites; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 262aeb07ac..9e8e44229e 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index c0e2c75aca..eefa1531c4 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Bindables; diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 389d0db261..1c71f5d055 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index bc5988174b..0f51e2a6d5 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; diff --git a/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs b/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs index 984892de51..2ac0f59d84 100644 --- a/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs +++ b/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Humanizer; using osu.Framework.Bindables; using osu.Game.Beatmaps; diff --git a/osu.Game/Rulesets/Mods/ModExtensions.cs b/osu.Game/Rulesets/Mods/ModExtensions.cs index ad61404972..b22030414b 100644 --- a/osu.Game/Rulesets/Mods/ModExtensions.cs +++ b/osu.Game/Rulesets/Mods/ModExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; diff --git a/osu.Game/Rulesets/Mods/ModFailCondition.cs b/osu.Game/Rulesets/Mods/ModFailCondition.cs index e24746ebd9..4425ece513 100644 --- a/osu.Game/Rulesets/Mods/ModFailCondition.cs +++ b/osu.Game/Rulesets/Mods/ModFailCondition.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Bindables; using osu.Game.Configuration; diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 649d5480bf..b449f3f64d 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Framework.Allocation; diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 48fa7c13b4..13d89e30d6 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index 030520fccc..0a5348a8cf 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 35107762aa..5a8226115f 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Scoring; diff --git a/osu.Game/Rulesets/Mods/ModMirror.cs b/osu.Game/Rulesets/Mods/ModMirror.cs index 00a1d4a9c6..3c4b7d0c60 100644 --- a/osu.Game/Rulesets/Mods/ModMirror.cs +++ b/osu.Game/Rulesets/Mods/ModMirror.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mods { public abstract class ModMirror : Mod diff --git a/osu.Game/Rulesets/Mods/ModMuted.cs b/osu.Game/Rulesets/Mods/ModMuted.cs index 88e49f41b0..84341faab7 100644 --- a/osu.Game/Rulesets/Mods/ModMuted.cs +++ b/osu.Game/Rulesets/Mods/ModMuted.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using osu.Framework.Audio; using osu.Framework.Bindables; diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 7c2201cd98..c4417ec509 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; @@ -57,10 +55,10 @@ namespace osu.Game.Rulesets.Mods public class NightcoreBeatContainer : BeatSyncedContainer { - private PausableSkinnableSound hatSample; - private PausableSkinnableSound clapSample; - private PausableSkinnableSound kickSample; - private PausableSkinnableSound finishSample; + private PausableSkinnableSound? hatSample; + private PausableSkinnableSound? clapSample; + private PausableSkinnableSound? kickSample; + private PausableSkinnableSound? finishSample; private int? firstBeat; diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 27dbb53e6c..5ebae17228 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; diff --git a/osu.Game/Rulesets/Mods/ModNoMod.cs b/osu.Game/Rulesets/Mods/ModNoMod.cs index cc0b38cbc0..1009c5bc42 100644 --- a/osu.Game/Rulesets/Mods/ModNoMod.cs +++ b/osu.Game/Rulesets/Mods/ModNoMod.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics.Sprites; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/ModNoScope.cs b/osu.Game/Rulesets/Mods/ModNoScope.cs index c43ac33b3f..7a935eb38f 100644 --- a/osu.Game/Rulesets/Mods/ModNoScope.cs +++ b/osu.Game/Rulesets/Mods/ModNoScope.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Bindables; using osu.Framework.Graphics; diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index b6daf54fa0..9016a24f8d 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index 6654bff04b..1f7742b075 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 643280623c..7b55ba4ad0 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Audio; using osu.Framework.Bindables; diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs index 4829d10ddb..e5995ff180 100644 --- a/osu.Game/Rulesets/Mods/ModRelax.cs +++ b/osu.Game/Rulesets/Mods/ModRelax.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 48ab888a90..c8b835f78a 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index daa4b7c797..7031489d0e 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Audio; @@ -46,7 +44,7 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - private IAdjustableAudioComponent track; + private IAdjustableAudioComponent? track; protected ModTimeRamp() { diff --git a/osu.Game/Rulesets/Mods/ModType.cs b/osu.Game/Rulesets/Mods/ModType.cs index 5a405b7632..e3c82e42f5 100644 --- a/osu.Game/Rulesets/Mods/ModType.cs +++ b/osu.Game/Rulesets/Mods/ModType.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mods { public enum ModType diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index 2150264a0c..08bd44f7bd 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Bindables; diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index d9ee561ef8..df8f781148 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Bindables; diff --git a/osu.Game/Rulesets/Mods/ModWithVisibilityAdjustment.cs b/osu.Game/Rulesets/Mods/ModWithVisibilityAdjustment.cs index d84695fff8..2e3619ec63 100644 --- a/osu.Game/Rulesets/Mods/ModWithVisibilityAdjustment.cs +++ b/osu.Game/Rulesets/Mods/ModWithVisibilityAdjustment.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Framework.Bindables; using osu.Game.Beatmaps; @@ -21,7 +19,7 @@ namespace osu.Game.Rulesets.Mods /// /// The first adjustable object. /// - protected HitObject FirstObject { get; private set; } + protected HitObject? FirstObject { get; private set; } /// /// Whether the visibility of should be increased. @@ -59,7 +57,7 @@ namespace osu.Game.Rulesets.Mods { FirstObject = getFirstAdjustableObjectRecursive(beatmap.HitObjects); - HitObject getFirstAdjustableObjectRecursive(IReadOnlyList hitObjects) + HitObject? getFirstAdjustableObjectRecursive(IReadOnlyList hitObjects) { foreach (var h in hitObjects) { @@ -93,7 +91,7 @@ namespace osu.Game.Rulesets.Mods /// The to check. /// The which may be equal to or contain as a nested object. /// Whether is equal to or nested within . - private bool isObjectEqualToOrNestedIn(HitObject toCheck, HitObject target) + private bool isObjectEqualToOrNestedIn(HitObject toCheck, HitObject? target) { if (target == null) return false; diff --git a/osu.Game/Rulesets/Mods/MultiMod.cs b/osu.Game/Rulesets/Mods/MultiMod.cs index d62dc34d3b..1c41c6b8b3 100644 --- a/osu.Game/Rulesets/Mods/MultiMod.cs +++ b/osu.Game/Rulesets/Mods/MultiMod.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; diff --git a/osu.Game/Rulesets/Mods/UnknownMod.cs b/osu.Game/Rulesets/Mods/UnknownMod.cs index e058fba566..72de0ad653 100644 --- a/osu.Game/Rulesets/Mods/UnknownMod.cs +++ b/osu.Game/Rulesets/Mods/UnknownMod.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mods { public class UnknownMod : Mod From ce1bb206c860e3ca53bb04ac6f797e308ab68d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 23:16:51 +0800 Subject: [PATCH 686/803] Initialize some bindables for prevent get the null instance. --- osu.Game/Rulesets/Mods/ModBlockFail.cs | 4 ++-- osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs | 4 ++-- osu.Game/Rulesets/Mods/ModMuted.cs | 4 ++-- osu.Game/Rulesets/Mods/ModNoScope.cs | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModBlockFail.cs b/osu.Game/Rulesets/Mods/ModBlockFail.cs index 8a9b0cddc8..cdfb36ebbc 100644 --- a/osu.Game/Rulesets/Mods/ModBlockFail.cs +++ b/osu.Game/Rulesets/Mods/ModBlockFail.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModBlockFail : Mod, IApplicableFailOverride, IApplicableToHUD, IReadFromConfig { - private Bindable showHealthBar; + private readonly Bindable showHealthBar = new Bindable(); /// /// We never fail, 'yo. @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mods public void ReadFromConfig(OsuConfigManager config) { - showHealthBar = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail); + config.BindWith(OsuSetting.ShowHealthDisplayWhenCantFail, showHealthBar); } public void ApplyToHUD(HUDOverlay overlay) diff --git a/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs b/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs index 2ac0f59d84..c4396e440e 100644 --- a/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs +++ b/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mods private int retries; - private BindableNumber health; + private readonly BindableNumber health = new BindableDouble(); public override void ApplyToDifficulty(BeatmapDifficulty difficulty) { @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mods public void ApplyToHealthProcessor(HealthProcessor healthProcessor) { - health = healthProcessor.Health.GetBoundCopy(); + health.BindTo(healthProcessor.Health); } } } diff --git a/osu.Game/Rulesets/Mods/ModMuted.cs b/osu.Game/Rulesets/Mods/ModMuted.cs index 84341faab7..55d5abfa82 100644 --- a/osu.Game/Rulesets/Mods/ModMuted.cs +++ b/osu.Game/Rulesets/Mods/ModMuted.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mods private readonly BindableNumber mainVolumeAdjust = new BindableDouble(0.5); private readonly BindableNumber metronomeVolumeAdjust = new BindableDouble(0.5); - private BindableNumber currentCombo; + private readonly BindableNumber currentCombo = new BindableInt(); [SettingSource("Enable metronome", "Add a metronome beat to help you keep track of the rhythm.")] public BindableBool EnableMetronome { get; } = new BindableBool @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Mods public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { - currentCombo = scoreProcessor.Combo.GetBoundCopy(); + currentCombo.BindTo(scoreProcessor.Combo); currentCombo.BindValueChanged(combo => { double dimFactor = MuteComboCount.Value == 0 ? 1 : (double)combo.NewValue / MuteComboCount.Value; diff --git a/osu.Game/Rulesets/Mods/ModNoScope.cs b/osu.Game/Rulesets/Mods/ModNoScope.cs index 7a935eb38f..1b9ce833ad 100644 --- a/osu.Game/Rulesets/Mods/ModNoScope.cs +++ b/osu.Game/Rulesets/Mods/ModNoScope.cs @@ -28,9 +28,9 @@ namespace osu.Game.Rulesets.Mods protected const float TRANSITION_DURATION = 100; - protected BindableNumber CurrentCombo; + protected readonly BindableNumber CurrentCombo = new BindableInt(); - protected IBindable IsBreakTime; + protected readonly IBindable IsBreakTime = new Bindable(); protected float ComboBasedAlpha; @@ -40,14 +40,14 @@ namespace osu.Game.Rulesets.Mods public void ApplyToPlayer(Player player) { - IsBreakTime = player.IsBreakTime.GetBoundCopy(); + IsBreakTime.BindTo(player.IsBreakTime); } public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { if (HiddenComboCount.Value == 0) return; - CurrentCombo = scoreProcessor.Combo.GetBoundCopy(); + CurrentCombo.BindTo(scoreProcessor.Combo); CurrentCombo.BindValueChanged(combo => { ComboBasedAlpha = Math.Max(MIN_ALPHA, 1 - (float)combo.NewValue / HiddenComboCount.Value); From 3af093cb2776039e9e1a05860865064b59075b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 23:33:31 +0800 Subject: [PATCH 687/803] Remove the null check because bindable should always have the value. --- osu.Game/Rulesets/Mods/Mod.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 7fdb03a7f3..abba83ce59 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -214,8 +214,8 @@ namespace osu.Game.Rulesets.Mods public bool Equals(IBindable x, IBindable y) { - object xValue = x?.GetUnderlyingSettingValue(); - object yValue = y?.GetUnderlyingSettingValue(); + object xValue = x.GetUnderlyingSettingValue(); + object yValue = y.GetUnderlyingSettingValue(); return EqualityComparer.Default.Equals(xValue, yValue); } From 317558f8769dc217fd8caf8ec8c8b3a5e2106181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 23:37:11 +0800 Subject: [PATCH 688/803] Mark the shader as non-nullable because shader should always has the value. And initialize the breaks to avoid get the null instance. --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index b449f3f64d..e8bc6c2026 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -94,13 +94,13 @@ namespace osu.Game.Rulesets.Mods { public readonly BindableInt Combo = new BindableInt(); - private IShader shader; + private IShader shader = null!; protected override DrawNode CreateDrawNode() => new FlashlightDrawNode(this); public override bool RemoveCompletedTransforms => false; - public List Breaks; + public List Breaks = new List(); private readonly float defaultFlashlightSize; private readonly float sizeMultiplier; @@ -205,7 +205,7 @@ namespace osu.Game.Rulesets.Mods { protected new Flashlight Source => (Flashlight)base.Source; - private IShader shader; + private IShader shader = null!; private Quad screenSpaceDrawQuad; private Vector2 flashlightPosition; private Vector2 flashlightSize; @@ -253,7 +253,7 @@ namespace osu.Game.Rulesets.Mods protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - quadBatch?.Dispose(); + quadBatch.Dispose(); } } } From d9addebc93a4b3570c027258bdeebdaf725025a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Mon, 11 Jul 2022 00:07:17 +0800 Subject: [PATCH 689/803] Remove the nullable disable annotation in the test project and fix the api broken. --- osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs | 4 +--- osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs | 2 -- osu.Game.Tests/Mods/ModSettingsTest.cs | 2 -- osu.Game.Tests/Mods/ModUtilsTest.cs | 2 -- osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs | 6 ++---- osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs | 2 -- osu.Game.Tests/Mods/TestCustomisableModRuleset.cs | 4 +--- osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs | 2 -- 8 files changed, 4 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs index a6f68b2836..efb04978a5 100644 --- a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs +++ b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using NUnit.Framework; using osu.Game.Beatmaps; @@ -148,7 +146,7 @@ namespace osu.Game.Tests.Mods yield return new TestModDifficultyAdjust(); } - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) { throw new System.NotImplementedException(); } diff --git a/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs b/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs index e94ee40acd..cd6879cf01 100644 --- a/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs +++ b/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Online.API; using osu.Game.Rulesets.Osu; diff --git a/osu.Game.Tests/Mods/ModSettingsTest.cs b/osu.Game.Tests/Mods/ModSettingsTest.cs index 607b585d33..b9ea1f2567 100644 --- a/osu.Game.Tests/Mods/ModSettingsTest.cs +++ b/osu.Game.Tests/Mods/ModSettingsTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 22be1a3f01..6c9dddf51f 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using Moq; diff --git a/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs b/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs index 3c69adcb59..b8a3828a64 100644 --- a/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs +++ b/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -29,10 +27,10 @@ namespace osu.Game.Tests.Mods [TestCase(typeof(ManiaRuleset))] public void TestAllMultiModsFromRulesetAreIncompatible(Type rulesetType) { - var ruleset = (Ruleset)Activator.CreateInstance(rulesetType); + var ruleset = Activator.CreateInstance(rulesetType) as Ruleset; Assert.That(ruleset, Is.Not.Null); - var allMultiMods = getMultiMods(ruleset); + var allMultiMods = getMultiMods(ruleset!); Assert.Multiple(() => { diff --git a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs index f608d020d4..dd105787fa 100644 --- a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs +++ b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; diff --git a/osu.Game.Tests/Mods/TestCustomisableModRuleset.cs b/osu.Game.Tests/Mods/TestCustomisableModRuleset.cs index 08007503c6..9e3354935a 100644 --- a/osu.Game.Tests/Mods/TestCustomisableModRuleset.cs +++ b/osu.Game.Tests/Mods/TestCustomisableModRuleset.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Framework.Bindables; @@ -33,7 +31,7 @@ namespace osu.Game.Tests.Mods return Array.Empty(); } - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) => throw new NotImplementedException(); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); diff --git a/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs index 2622db464f..51163efd6a 100644 --- a/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs +++ b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Framework.Audio.Track; using osu.Framework.Timing; From ee7e7f2d3a27e5c38b52808d937a9a6072d1bdca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Mon, 11 Jul 2022 00:09:54 +0800 Subject: [PATCH 690/803] Mark the property as non-nullable. --- osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs | 2 +- osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs index efb04978a5..4101652c49 100644 --- a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs +++ b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Mods [TestFixture] public class ModDifficultyAdjustTest { - private TestModDifficultyAdjust testMod; + private TestModDifficultyAdjust testMod = null!; [SetUp] public void Setup() diff --git a/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs index 51163efd6a..4601737558 100644 --- a/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs +++ b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs @@ -17,8 +17,8 @@ namespace osu.Game.Tests.Rulesets.Mods private const double start_time = 1000; private const double duration = 9000; - private TrackVirtual track; - private OsuPlayfield playfield; + private TrackVirtual track = null!; + private OsuPlayfield playfield = null!; [SetUp] public void SetUp() From 2a83404dbe1e19316b3044f7b4c14a4c7cf882fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Mon, 11 Jul 2022 00:20:56 +0800 Subject: [PATCH 691/803] Use array.empty instead of null value. --- osu.Game.Tests/Mods/ModUtilsTest.cs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 6c9dddf51f..3b391f6756 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -162,19 +162,19 @@ namespace osu.Game.Tests.Mods new object[] { new Mod[] { new OsuModHidden(), new InvalidMultiplayerMod() }, - null + Array.Empty() }, // invalid free mod is valid for local. new object[] { new Mod[] { new OsuModHidden(), new InvalidMultiplayerFreeMod() }, - null + Array.Empty() }, // valid pair. new object[] { new Mod[] { new OsuModHidden(), new OsuModHardRock() }, - null + Array.Empty() }, }; @@ -214,13 +214,13 @@ namespace osu.Game.Tests.Mods new object[] { new Mod[] { new OsuModHidden(), new InvalidMultiplayerFreeMod() }, - null + Array.Empty() }, // valid pair. new object[] { new Mod[] { new OsuModHidden(), new OsuModHardRock() }, - null + Array.Empty() }, }; @@ -254,19 +254,19 @@ namespace osu.Game.Tests.Mods new object[] { new Mod[] { new OsuModHidden(), new OsuModApproachDifferent() }, - null, + Array.Empty(), }, // incompatible pair with derived class is valid for free mods. new object[] { new Mod[] { new OsuModDeflate(), new OsuModSpinIn() }, - null, + Array.Empty(), }, // valid pair. new object[] { new Mod[] { new OsuModHidden(), new OsuModHardRock() }, - null + Array.Empty() }, }; @@ -275,12 +275,12 @@ namespace osu.Game.Tests.Mods { bool isValid = ModUtils.CheckValidForGameplay(inputMods, out var invalid); - Assert.That(isValid, Is.EqualTo(expectedInvalid == null)); + Assert.That(isValid, Is.EqualTo(expectedInvalid.Length == 0)); if (isValid) Assert.IsNull(invalid); else - Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); + Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); } [TestCaseSource(nameof(invalid_multiplayer_mod_test_scenarios))] @@ -288,12 +288,12 @@ namespace osu.Game.Tests.Mods { bool isValid = ModUtils.CheckValidRequiredModsForMultiplayer(inputMods, out var invalid); - Assert.That(isValid, Is.EqualTo(expectedInvalid == null)); + Assert.That(isValid, Is.EqualTo(expectedInvalid.Length == 0)); if (isValid) Assert.IsNull(invalid); else - Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); + Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); } [TestCaseSource(nameof(invalid_free_mod_test_scenarios))] @@ -301,12 +301,12 @@ namespace osu.Game.Tests.Mods { bool isValid = ModUtils.CheckValidFreeModsForMultiplayer(inputMods, out var invalid); - Assert.That(isValid, Is.EqualTo(expectedInvalid == null)); + Assert.That(isValid, Is.EqualTo(expectedInvalid.Length == 0)); if (isValid) Assert.IsNull(invalid); else - Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); + Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); } public abstract class CustomMod1 : Mod, IModCompatibilitySpecification From 4164f260b387b3251294d83d237dfa97ba67bcc8 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Tue, 19 Jul 2022 08:12:12 -0500 Subject: [PATCH 692/803] Fix code quality errors --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 04d800a10e..410ac9438f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -148,7 +148,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new MultiplayerPlaylist { RelativeSizeAxes = Axes.Both, - RequestEdit = item => OpenSongSelection(item) + RequestEdit = OpenSongSelection } }, new[] @@ -231,7 +231,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!this.IsCurrentScreen()) return; - int id = itemToEdit?.Beatmap.OnlineID ?? Room.Playlist.LastOrDefault().Beatmap.OnlineID; + int id = itemToEdit?.Beatmap.OnlineID ?? Room.Playlist.LastOrDefault()!.Beatmap.OnlineID; var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == id); var workingBeatmap = localBeatmap == null ? null : beatmapManager.GetWorkingBeatmap(localBeatmap); From 25028bb7fab0edd964d304e6fc00409c2faf8240 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 22:36:07 +0900 Subject: [PATCH 693/803] Fix editor clap/finish buttons being ordered against expectations --- osu.Game/Audio/HitSampleInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index 6aaf3d5cc2..efa5562cb8 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -14,15 +14,15 @@ namespace osu.Game.Audio [Serializable] public class HitSampleInfo : ISampleInfo, IEquatable { + public const string HIT_NORMAL = @"hitnormal"; public const string HIT_WHISTLE = @"hitwhistle"; public const string HIT_FINISH = @"hitfinish"; - public const string HIT_NORMAL = @"hitnormal"; public const string HIT_CLAP = @"hitclap"; /// /// All valid sample addition constants. /// - public static IEnumerable AllAdditions => new[] { HIT_WHISTLE, HIT_CLAP, HIT_FINISH }; + public static IEnumerable AllAdditions => new[] { HIT_WHISTLE, HIT_FINISH, HIT_CLAP }; /// /// The name of the sample to load. From 06ae30a7d270c7f089fb5341cc8dbcfb98e94cf7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 22:54:11 +0900 Subject: [PATCH 694/803] Fix slider velocity not using previous value if slider is not adjacent --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 139bfe7dd3..59be93530c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; using osuTK; using osuTK.Input; @@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { public class SliderPlacementBlueprint : PlacementBlueprint { - public new Objects.Slider HitObject => (Objects.Slider)base.HitObject; + public new Slider HitObject => (Slider)base.HitObject; private SliderBodyPiece bodyPiece; private HitCirclePiece headCirclePiece; @@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private IDistanceSnapProvider snapProvider { get; set; } public SliderPlacementBlueprint() - : base(new Objects.Slider()) + : base(new Slider()) { RelativeSizeAxes = Axes.Both; @@ -82,7 +83,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case SliderPlacementState.Initial: BeginPlacement(); - var nearestDifficultyPoint = editorBeatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.DifficultyControlPoint?.DeepClone() as DifficultyControlPoint; + var nearestDifficultyPoint = editorBeatmap.HitObjects + .LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime)? + .DifficultyControlPoint?.DeepClone() as DifficultyControlPoint; HitObject.DifficultyControlPoint = nearestDifficultyPoint ?? new DifficultyControlPoint(); HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); From 87afa7317b141c37bbc5d0d498c0149be729a9e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 23:12:49 +0900 Subject: [PATCH 695/803] 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 3b14d85e53..013a7d1419 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 6120d3d600..40e01d5f2e 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 8a36ad6e3d..0e2e7d57b7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 7be5c638e46b14db6b8a9257e5e47bd17ebb5d1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 00:22:31 +0900 Subject: [PATCH 696/803] Fix floating mouse position not running correctly in single thread mode Noticed while testing on iOS. Previously, the interpolation was being done in input handling but using the update thread clock, leading to incorrect application. --- osu.Game/Graphics/Cursor/MenuCursor.cs | 34 +++++++++++++++++--------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 10ed76ebdd..862a10208c 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -3,21 +3,21 @@ #nullable disable -using osuTK; +using System; +using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; -using osu.Game.Configuration; -using System; -using JetBrains.Annotations; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Bindables; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Configuration; +using osuTK; namespace osu.Game.Graphics.Cursor { @@ -35,6 +35,7 @@ namespace osu.Game.Graphics.Cursor private Vector2 positionMouseDown; private Sample tapSample; + private Vector2 lastMovePosition; [BackgroundDependencyLoader(true)] private void load([NotNull] OsuConfigManager config, [CanBeNull] ScreenshotManager screenshotManager, AudioManager audio) @@ -47,16 +48,25 @@ namespace osu.Game.Graphics.Cursor tapSample = audio.Samples.Get(@"UI/cursor-tap"); } + protected override void Update() + { + base.Update(); + + if (dragRotationState != DragRotationState.NotDragging + && Vector2.Distance(positionMouseDown, lastMovePosition) > 60) + { + // make the rotation centre point floating. + positionMouseDown = Interpolation.ValueAt(0.04f, positionMouseDown, lastMovePosition, 0, Clock.ElapsedFrameTime); + } + } + protected override bool OnMouseMove(MouseMoveEvent e) { if (dragRotationState != DragRotationState.NotDragging) { - // make the rotation centre point floating. - if (Vector2.Distance(positionMouseDown, e.MousePosition) > 60) - positionMouseDown = Interpolation.ValueAt(0.005f, positionMouseDown, e.MousePosition, 0, Clock.ElapsedFrameTime); + lastMovePosition = e.MousePosition; - var position = e.MousePosition; - float distance = Vector2Extensions.Distance(position, positionMouseDown); + float distance = Vector2Extensions.Distance(lastMovePosition, positionMouseDown); // don't start rotating until we're moved a minimum distance away from the mouse down location, // else it can have an annoying effect. From 89653b74c797e417d88fc0235b29be964d2d70e8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jul 2022 19:21:16 +0300 Subject: [PATCH 697/803] Only add setting tracker when customisation is permitted --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ea152f58ad..afc6775e83 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -246,8 +246,11 @@ namespace osu.Game.Overlays.Mods updateCustomisation(val); updateFromExternalSelection(); - modSettingChangeTracker = new ModSettingChangeTracker(val.NewValue); - modSettingChangeTracker.SettingChanged += _ => updateMultiplier(); + if (AllowCustomisation) + { + modSettingChangeTracker = new ModSettingChangeTracker(val.NewValue); + modSettingChangeTracker.SettingChanged += _ => updateMultiplier(); + } }, true); customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); From a00da279b7bfd02730ddfb52b347ab93847464b6 Mon Sep 17 00:00:00 2001 From: LukynkaCZE <48604271+LukynkaCZE@users.noreply.github.com> Date: Tue, 19 Jul 2022 21:38:23 +0200 Subject: [PATCH 698/803] Beatmap Editor Save Toast --- osu.Game/Localisation/ToastStrings.cs | 11 +++++++++++ osu.Game/Screens/Edit/Editor.cs | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 52e75425bf..7c5fdb4b22 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -34,6 +34,17 @@ namespace osu.Game.Localisation /// public static LocalisableString RestartTrack => new TranslatableString(getKey(@"restart_track"), @"Restart track"); + /// + /// "Beatmap Editor" + /// r + public static LocalisableString BeatmapEditor => new TranslatableString(getKey(@"beatmap_editor"), @"Beatmap Editor"); + + /// + /// "Beatmap Saved" + /// + public static LocalisableString EditorSaveBeatmap => new TranslatableString(getKey(@"beatmap_editor_save"), @"Beatmap Saved"); + + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 48576b81e2..bfc6abfc32 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -18,6 +18,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; @@ -31,10 +32,11 @@ using osu.Game.Database; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; +using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -using osu.Game.Resources.Localisation.Web; +using osu.Game.Overlays.OSD; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -50,6 +52,7 @@ using osu.Game.Screens.Play; using osu.Game.Users; using osuTK.Graphics; using osuTK.Input; +using CommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings; namespace osu.Game.Screens.Edit { @@ -169,6 +172,9 @@ namespace osu.Game.Screens.Edit [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + [Resolved(canBeNull: true)] + private OnScreenDisplay onScreenDisplay { get; set; } + public Editor(EditorLoader loader = null) { this.loader = loader; @@ -405,6 +411,7 @@ namespace osu.Game.Screens.Edit // no longer new after first user-triggered save. isNewBeatmap = false; updateLastSavedHash(); + onScreenDisplay?.Display(new BeatmapEditorToast(ToastStrings.EditorSaveBeatmap, editorBeatmap.BeatmapInfo.GetDisplayTitle())); return true; } @@ -934,5 +941,13 @@ namespace osu.Game.Screens.Edit ControlPointInfo IBeatSyncProvider.ControlPoints => editorBeatmap.ControlPointInfo; IClock IBeatSyncProvider.Clock => clock; ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : null; + + + private class BeatmapEditorToast : Toast + { + public BeatmapEditorToast(LocalisableString value, string beatmapDisplayName) + : base(ToastStrings.BeatmapEditor, value, beatmapDisplayName) { } + + } } } From 51a0b5afdc4950195363efaa50b9ee1890436623 Mon Sep 17 00:00:00 2001 From: LukynkaCZE <48604271+LukynkaCZE@users.noreply.github.com> Date: Tue, 19 Jul 2022 22:18:19 +0200 Subject: [PATCH 699/803] Skin Editor --- osu.Game/Localisation/ToastStrings.cs | 11 +++++++++++ osu.Game/Skinning/Editor/SkinEditor.cs | 16 ++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 7c5fdb4b22..519c47e7c4 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -44,6 +44,17 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorSaveBeatmap => new TranslatableString(getKey(@"beatmap_editor_save"), @"Beatmap Saved"); + /// + /// "Skin Editor" + /// + public static LocalisableString SkinEditor => new TranslatableString(getKey(@"skin_editor"), @"Skin Editor"); + + /// + /// "Skin Saved" + /// + public static LocalisableString EditorSaveSkin => new TranslatableString(getKey(@"skin_editor_save"), @"Skin Saved"); + + private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 649b63dda4..02c0350d2c 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -14,13 +14,16 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; using osu.Game.Overlays; +using osu.Game.Overlays.OSD; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; @@ -68,6 +71,9 @@ namespace osu.Game.Skinning.Editor private EditorSidebar componentsSidebar; private EditorSidebar settingsSidebar; + [Resolved(canBeNull: true)] + private OnScreenDisplay onScreenDisplay { get; set; } + public SkinEditor() { } @@ -316,6 +322,7 @@ namespace osu.Game.Skinning.Editor currentSkin.Value.UpdateDrawableTarget(t); skins.Save(skins.CurrentSkin.Value); + onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.EditorSaveSkin, currentSkin.Value.SkinInfo.ToString())); } protected override bool OnHover(HoverEvent e) => true; @@ -395,5 +402,14 @@ namespace osu.Game.Skinning.Editor game?.UnregisterImportHandler(this); } + + + private class SkinEditorToast : Toast + { + public SkinEditorToast(LocalisableString value, string skinDisplayName) + : base(ToastStrings.SkinEditor, value, skinDisplayName) { } + + } + } } From 5987acfbca821b3e5f71157509382d9bdc0e2a04 Mon Sep 17 00:00:00 2001 From: LukynkaCZE <48604271+LukynkaCZE@users.noreply.github.com> Date: Tue, 19 Jul 2022 22:59:25 +0200 Subject: [PATCH 700/803] Fixed code formatting --- osu.Game/Localisation/ToastStrings.cs | 2 -- osu.Game/Screens/Edit/Editor.cs | 2 -- osu.Game/Skinning/Editor/SkinEditor.cs | 3 --- 3 files changed, 7 deletions(-) diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 519c47e7c4..4169a23798 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -54,8 +54,6 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorSaveSkin => new TranslatableString(getKey(@"skin_editor_save"), @"Skin Saved"); - - private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index bfc6abfc32..bf9785063b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -942,12 +942,10 @@ namespace osu.Game.Screens.Edit IClock IBeatSyncProvider.Clock => clock; ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : null; - private class BeatmapEditorToast : Toast { public BeatmapEditorToast(LocalisableString value, string beatmapDisplayName) : base(ToastStrings.BeatmapEditor, value, beatmapDisplayName) { } - } } } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 02c0350d2c..326574f2da 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -403,13 +403,10 @@ namespace osu.Game.Skinning.Editor game?.UnregisterImportHandler(this); } - private class SkinEditorToast : Toast { public SkinEditorToast(LocalisableString value, string skinDisplayName) : base(ToastStrings.SkinEditor, value, skinDisplayName) { } - } - } } From 1270abdf42214e144ad5cf260d0ec9d8a3e46470 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 20 Jul 2022 00:50:28 +0300 Subject: [PATCH 701/803] Highlight perfect slider tick/end values in beatmap info leaderboards --- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 6acc9bf002..c46c5cde43 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -23,6 +23,7 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Localisation; using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics.Cursor; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapSet.Scores @@ -38,8 +39,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly FillFlowContainer backgroundFlow; - private Color4 highAccuracyColour; - public ScoreTable() { RelativeSizeAxes = Axes.X; @@ -57,12 +56,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }); } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - highAccuracyColour = colours.GreenLight; - } - /// /// The statistics that appear in the table, in order of appearance. /// @@ -158,12 +151,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Current = scoreManager.GetBindableTotalScoreString(score), Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) }, - new OsuSpriteText + new StatisticText(score.Accuracy, 1, showTooltip: false) { Margin = new MarginPadding { Right = horizontal_inset }, Text = score.DisplayAccuracy, - Font = OsuFont.GetFont(size: text_size), - Colour = score.Accuracy == 1 ? highAccuracyColour : Color4.White }, new UpdateableFlag(score.User.CountryCode) { @@ -171,14 +162,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores ShowPlaceholderOnUnknown = false, }, username, - new OsuSpriteText - { - Text = score.MaxCombo.ToLocalisableString(@"0\x"), - Font = OsuFont.GetFont(size: text_size), #pragma warning disable 618 - Colour = score.MaxCombo == score.BeatmapInfo.MaxCombo ? highAccuracyColour : Color4.White + new StatisticText(score.MaxCombo, score.BeatmapInfo.MaxCombo, @"0\x"), #pragma warning restore 618 - } }; var availableStatistics = score.GetStatisticsForDisplay().ToDictionary(tuple => tuple.Result); @@ -188,23 +174,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (!availableStatistics.TryGetValue(result.result, out var stat)) stat = new HitResultDisplayStatistic(result.result, 0, null, result.displayName); - content.Add(new OsuSpriteText - { - Text = stat.MaxCount == null ? stat.Count.ToLocalisableString(@"N0") : (LocalisableString)$"{stat.Count}/{stat.MaxCount}", - Font = OsuFont.GetFont(size: text_size), - Colour = stat.Count == 0 ? Color4.Gray : Color4.White - }); + content.Add(new StatisticText(stat.Count, stat.MaxCount, @"N0") { Colour = stat.Count == 0 ? Color4.Gray : Color4.White }); } if (showPerformancePoints) { Debug.Assert(score.PP != null); - - content.Add(new OsuSpriteText - { - Text = score.PP.ToLocalisableString(@"N0"), - Font = OsuFont.GetFont(size: text_size) - }); + content.Add(new StatisticText(score.PP.Value, format: @"N0")); } content.Add(new ScoreboardTime(score.Date, text_size) @@ -243,5 +219,31 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Colour = colourProvider.Foreground1; } } + + private class StatisticText : OsuSpriteText, IHasTooltip + { + private readonly double count; + private readonly double? maxCount; + private readonly bool showTooltip; + + public LocalisableString TooltipText => maxCount == null || !showTooltip ? string.Empty : $"{count}/{maxCount}"; + + public StatisticText(double count, double? maxCount = null, string format = null, bool showTooltip = true) + { + this.count = count; + this.maxCount = maxCount; + this.showTooltip = showTooltip; + + Text = count.ToLocalisableString(format); + Font = OsuFont.GetFont(size: text_size); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + if (count == maxCount) + Colour = colours.GreenLight; + } + } } } From 4d1f9a13296a1d8115f2fd22354a133f74762dba Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 20 Jul 2022 00:50:37 +0300 Subject: [PATCH 702/803] Adjust test scene to cover slider ticks --- .../Visual/Online/TestSceneScoresContainer.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index beca3a8700..864b2b6878 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -255,18 +255,25 @@ namespace osu.Game.Tests.Visual.Online }; const int initial_great_count = 2000; + const int initial_tick_count = 100; int greatCount = initial_great_count; + int tickCount = initial_tick_count; foreach (var s in scores.Scores) { s.Statistics = new Dictionary { - { HitResult.Great, greatCount -= 100 }, + { HitResult.Great, greatCount }, + { HitResult.LargeTickHit, tickCount }, { HitResult.Ok, RNG.Next(100) }, { HitResult.Meh, RNG.Next(100) }, - { HitResult.Miss, initial_great_count - greatCount } + { HitResult.Miss, initial_great_count - greatCount }, + { HitResult.LargeTickMiss, initial_tick_count - tickCount }, }; + + greatCount -= 100; + tickCount -= RNG.Next(1, 5); } return scores; From cecf654a7b1c265a3bbdfbbb965bf3808b29d5ee Mon Sep 17 00:00:00 2001 From: Adam Baker <42323315+Cwazywierdo@users.noreply.github.com> Date: Tue, 19 Jul 2022 18:58:59 -0500 Subject: [PATCH 703/803] Update osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs Co-authored-by: Salman Ahmed --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 285dfc2b60..ceadfa1527 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -227,7 +227,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!this.IsCurrentScreen()) return; - int id = itemToEdit?.Beatmap.OnlineID ?? Room.Playlist.LastOrDefault()!.Beatmap.OnlineID; + int id = itemToEdit?.Beatmap.OnlineID ?? Room.Playlist.Last().Beatmap.OnlineID; var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == id); var workingBeatmap = localBeatmap == null ? null : beatmapManager.GetWorkingBeatmap(localBeatmap); From 2a76a046198adb09379e78743852a6910a4f0588 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 13:41:36 +0900 Subject: [PATCH 704/803] 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 013a7d1419..c83b7872ac 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 40e01d5f2e..4fa4b804ab 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 0e2e7d57b7..dc012ab2fa 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From e7f35701dbdc4203636edbffef9f17ca1ac160c7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 20 Jul 2022 08:47:20 +0300 Subject: [PATCH 705/803] Add failing test case --- .../Visual/Online/TestSceneWikiOverlay.cs | 46 ++++++++++++++++--- .../Online/API/Requests/GetWikiRequest.cs | 7 +-- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs index 8889cb3e37..558bff2f3c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Net; using NUnit.Framework; using osu.Game.Online.API; @@ -25,24 +26,40 @@ namespace osu.Game.Tests.Visual.Online public void TestMainPage() { setUpWikiResponse(responseMainPage); - AddStep("Show Main Page", () => wiki.Show()); + AddStep("Show main page", () => wiki.Show()); } [Test] public void TestArticlePage() { setUpWikiResponse(responseArticlePage); - AddStep("Show Article Page", () => wiki.ShowPage("Article_styling_criteria/Formatting")); + AddStep("Show article page", () => wiki.ShowPage("Article_styling_criteria/Formatting")); + } + + [Test] + public void TestRedirection() + { + const string redirection_path = "Redirection_path_for_article"; + + setUpWikiResponse(responseArticlePage, redirection_path); + AddStep("Show article page", () => wiki.ShowPage(redirection_path)); + + AddUntilStep("Current page is article", () => wiki.Header.Current.Value == "Formatting"); + + setUpWikiResponse(responseArticleParentPage); + AddStep("Show parent page", () => wiki.Header.ShowParentPage?.Invoke()); + + AddUntilStep("Current page is parent", () => wiki.Header.Current.Value == "Article styling criteria"); } [Test] public void TestErrorPage() { - setUpWikiResponse(null, true); + setUpWikiResponse(responseArticlePage); AddStep("Show Error Page", () => wiki.ShowPage("Error")); } - private void setUpWikiResponse(APIWikiPage r, bool isFailed = false) + private void setUpWikiResponse(APIWikiPage r, string redirectionPath = null) => AddStep("set up response", () => { dummyAPI.HandleRequest = request => @@ -50,10 +67,13 @@ namespace osu.Game.Tests.Visual.Online if (!(request is GetWikiRequest getWikiRequest)) return false; - if (isFailed) - getWikiRequest.TriggerFailure(new WebException()); - else + if (getWikiRequest.Path.Equals(r.Path, StringComparison.OrdinalIgnoreCase) || + getWikiRequest.Path.Equals(redirectionPath, StringComparison.OrdinalIgnoreCase)) + { getWikiRequest.TriggerSuccess(r); + } + else + getWikiRequest.TriggerFailure(new WebException()); return true; }; @@ -82,5 +102,17 @@ namespace osu.Game.Tests.Visual.Online Markdown = "# Formatting\n\n*For the writing standards, see: [Article style criteria/Writing](../Writing)*\n\n*Notice: This article uses [RFC 2119](https://tools.ietf.org/html/rfc2119 \"IETF Tools\") to describe requirement levels.*\n\n## Locales\n\nListed below are the properly-supported locales for the wiki:\n\n| File Name | Locale Name | Native Script |\n| :-- | :-- | :-- |\n| `en.md` | English | English |\n| `ar.md` | Arabic | اَلْعَرَبِيَّةُ |\n| `be.md` | Belarusian | Беларуская мова |\n| `bg.md` | Bulgarian | Български |\n| `cs.md` | Czech | Česky |\n| `da.md` | Danish | Dansk |\n| `de.md` | German | Deutsch |\n| `gr.md` | Greek | Ελληνικά |\n| `es.md` | Spanish | Español |\n| `fi.md` | Finnish | Suomi |\n| `fr.md` | French | Français |\n| `hu.md` | Hungarian | Magyar |\n| `id.md` | Indonesian | Bahasa Indonesia |\n| `it.md` | Italian | Italiano |\n| `ja.md` | Japanese | 日本語 |\n| `ko.md` | Korean | 한국어 |\n| `nl.md` | Dutch | Nederlands |\n| `no.md` | Norwegian | Norsk |\n| `pl.md` | Polish | Polski |\n| `pt.md` | Portuguese | Português |\n| `pt-br.md` | Brazilian Portuguese | Português (Brasil) |\n| `ro.md` | Romanian | Română |\n| `ru.md` | Russian | Русский |\n| `sk.md` | Slovak | Slovenčina |\n| `sv.md` | Swedish | Svenska |\n| `th.md` | Thai | ไทย |\n| `tr.md` | Turkish | Türkçe |\n| `uk.md` | Ukrainian | Українська мова |\n| `vi.md` | Vietnamese | Tiếng Việt |\n| `zh.md` | Chinese (Simplified) | 简体中文 |\n| `zh-tw.md` | Traditional Chinese (Taiwan) | 繁體中文(台灣) |\n\n*Note: The website will give readers their selected language's version of an article. If it is not available, the English version will be given.*\n\n### Content parity\n\nTranslations are subject to strict content parity with their English article, in the sense that they must have the same message, regardless of grammar and syntax. Any changes to the translations' meanings must be accompanied by equivalent changes to the English article.\n\nThere are some cases where the content is allowed to differ:\n\n- Articles originally written in a language other than English (in this case, English should act as the translation)\n- Explanations of English words that are common terms in the osu! community\n- External links\n- Tags\n- Subcommunity-specific explanations\n\n## Front matter\n\nFront matter must be placed at the very top of the file. It is written in [YAML](https://en.wikipedia.org/wiki/YAML#Example \"YAML Wikipedia article\") and describes additional information about the article. This must be surrounded by three hyphens (`---`) on the lines above and below it, and an empty line must follow it before the title heading.\n\n### Articles that need help\n\n*Note: Avoid translating English articles with this tag. In addition to this, this tag should be added when the translation needs its own clean up.*\n\nThe `needs_cleanup` tag may be added to articles that need rewriting or formatting help. It is also acceptable to open an issue on GitHub for this purpose. This tag must be written as shown below:\n\n```yaml\nneeds_cleanup: true\n```\n\nWhen adding this tag to an article, [comments](#comments) should also be added to explain what needs to be done to remove the tag.\n\n### Outdated articles\n\n*Note: Avoid translating English articles with this tag. If the English article has this tag, the translation must also have this tag.*\n\nTranslated articles that are outdated must use the `outdated` tag when the English variant is updated. English articles may also become outdated when the content they contain is misleading or no longer relevant. This tag must be written as shown below:\n\n```yaml\noutdated: true\n```\n\nWhen adding this tag to an article, [comments](#comments) should also be added to explain what needs to be updated to remove the tag.\n\n### Tagging articles\n\nTags help the website's search engine query articles better. Tags should be written in the same language as the article and include the original list of tags. Tags should use lowercase letters where applicable.\n\nFor example, an article called \"Beatmap discussion\" may include the following tags:\n\n```yaml\ntags:\n - beatmap discussions\n - modding V2\n - MV2\n```\n\n### Translations without reviews\n\n*Note: Wiki maintainers will determine and apply this mark prior to merging.*\n\nSometimes, translations are added to the wiki without review from other native speakers of the language. In this case, the `no_native_review` mark is added to let future translators know that it may need to be checked again. This tag must be written as shown below:\n\n```yaml\nno_native_review: true\n```\n\n## Article naming\n\n*See also: [Folder names](#folder-names) and [Titles](#titles)*\n\nArticle titles should be singular and use sentence case. See [Wikipedia's naming conventions article](https://en.wikipedia.org/wiki/Wikipedia:Naming_conventions_(plurals) \"Wikipedia\") for more details.\n\nArticle titles should match the folder name it is in (spaces may replace underscores (`_`) where appropriate). If the folder name changes, the article title should be changed to match it and vice versa.\n\n---\n\nContest and tournament articles are an exception. The folder name must use abbreviations, acronyms, or initialisms. The article's title must be the full name of the contest or tournament.\n\n## Folder and file structure\n\n### Folder names\n\n*See also: [Article naming](#article-naming)*\n\nFolder names must be in English and use sentence case.\n\nFolder names must only use these characters:\n\n- uppercase and lowercase letters\n- numbers\n- underscores (`_`)\n- hyphens (`-`)\n- exclamation marks (`!`)\n\n### Article file names\n\nThe file name of an article can be found in the `File Name` column of the [locales section](#locales). The location of a translated article must be placed in the same folder as the English article.\n\n### Index articles\n\nAn index article must be created if the folder is intended to only hold other articles. Index articles must contain a list of articles that are inside its own folder. They may also contain other information, such as a lead paragraph or descriptions of the linked articles.\n\n### Disambiguation articles\n\n[Disambiguation](/wiki/Disambiguation) articles must be placed in the `/wiki/Disambiguation` folder. The main page must be updated to include the disambiguation article. Refer to [Disambiguation/Mod](/wiki/Disambiguation/Mod) as an example.\n\nRedirects must be updated to have the ambiguous keyword(s) redirect to the disambiguation article.\n\nArticles linked from a disambiguation article must have a [For other uses](#for-other-uses) hatnote.\n\n## HTML\n\nHTML must not be used, with exception for [comments](#comments). The structure of the article must be redone if HTML is used.\n\n### Comments\n\nHTML comments should be used for marking to-dos, but may also be used to annotate text. They should be on their own line, but can be placed inline in a paragraph. If placed inline, the start of the comment must not have a space.\n\nBad example:\n\n```markdown\nHTML comments should be used for marking to-dos or annotate text.\n```\n\nGood example:\n\n```markdown\nHTML comments should be used for marking to-dos or annotate text.\n```\n\n## Editing\n\n### End of line sequence\n\n*Caution: Uploading Markdown files using `CRLF` (carriage return and line feed) via GitHub will result in those files using `CRLF`. To prevent this, set the line ending to `LF` (line feed) before uploading.*\n\nMarkdown files must be checked in using the `LF` end of line sequence.\n\n### Escaping\n\nMarkdown syntax should be escaped as needed. However, article titles are parsed as plain text and so must not be escaped.\n\n### Paragraphs\n\nEach paragraph must be followed by one empty line.\n\n### Line breaks\n\nLine breaks must use a backslash (`\\`).\n\nLine breaks must be used sparingly.\n\n## Hatnote\n\n*Not to be confused with [Notice](#notice).*\n\nHatnotes are short notes placed at the top of an article or section to help readers navigate to related articles or inform them about related topics.\n\nHatnotes must be italicised and be placed immediately after the heading. If multiple hatnotes are used, they must be on the same paragraph separated with a line break.\n\n### Main page\n\n*Main page* hatnotes direct the reader to the main article of a topic. When this hatnote is used, it implies that the section it is on is a summary of what the linked page is about. This hatnote should have only one link. These must be formatted as follows:\n\n```markdown\n*Main page: {article}*\n\n*Main pages: {article} and {article}*\n```\n\n### See also\n\n*See also* hatnotes suggest to readers other points of interest from a given article or section. These must be formatted as follows:\n\n```markdown\n*See also: {article}*\n\n*See also: {article} and {article}*\n```\n\n### For see\n\n*For see* hatnotes are similar to *see also* hatnotes, but are generally more descriptive and direct. This hatnote may use more than one link if necessary. These must be formatted as follows:\n\n```markdown\n*For {description}, see: {article}`*\n\n*For {description}, see: {article} and {article}`*\n```\n\n### Not to be confused with\n\n*Not to be confused with* hatnotes help distinguish ambiguous or misunderstood article titles or sections. This hatnote may use more than one link if necessary. These must be formatted as follows:\n\n```markdown\n*Not to be confused with {article}.*\n\n*Not to be confused with {article} or {article}.*\n```\n\n### For other uses\n\n*For other uses* hatnotes are similar to *not to be confused with* hatnotes, but links directly to the [disambiguation article](#disambiguation-articles). This hatnote must only link to the disambiguation article. These must be formatted as follows:\n\n```markdown\n*For other uses, see {disambiguation article}.*\n```\n\n## Notice\n\n*Not to be confused with [Hatnote](#hatnote).*\n\nA notice should be placed where appropriate in a section, but must start off the paragraph and use italics. Notices may contain bolding where appropriate, but should be kept to a minimum. Notices must be written as complete sentences. Thus, unlike most [hatnotes](#hatnotes), must use a full stop (`.`) or an exclamation mark (`!`) if appropriate. Anything within the same paragraph of a notice must also be italicised. These must be formatted as follows:\n\n```markdown\n*Note: {note}.*\n\n*Notice: {notice}.*\n\n*Caution: {caution}.*\n\n*Warning: {warning}.*\n```\n\n- `Note` should be used for factual or trivial details.\n- `Notice` should be used for reminders or to draw attention to something that the reader should be made aware of.\n- `Caution` should be used to warn the reader to avoid unintended consequences.\n- `Warning` should be used to warn the reader that action may be taken against them.\n\n## Emphasising\n\n### Bold\n\nBold must use double asterisks (`**`).\n\nLead paragraphs may bold the first occurrence of the article's title.\n\n### Italics\n\nItalics must use single asterisks (`*`).\n\nNames of work or video games should be italicised. osu!—the game—is exempt from this.\n\nThe first occurrence of an abbreviation, acronym, or initialism may be italicised.\n\nItalics may also be used to provide emphasis or help with readability.\n\n## Headings\n\nAll headings must use sentence case.\n\nHeadings must use the [ATX (hash) style](https://github.github.com/gfm/#atx-headings \"GitHub\") and must have an empty line before and after the heading. The title heading is an exception when it is on the first line. If this is the case, there only needs to be an empty line after the title heading.\n\nHeadings must not exceed a heading level of 5 and must not be used to style or format text.\n\n### Titles\n\n*See also: [Article naming](#article-naming)*\n\n*Caution: Titles are parsed as plain text; they must not be escaped.*\n\nThe first heading in all articles must be a level 1 heading, being the article's title. All headings afterwards must be [section headings](#sections). Titles must not contain formatting, links, or images.\n\nThe title heading must be on the first line, unless [front matter](#front-matter) is being used. If that is the case, the title heading must go after it and have an empty line before the title heading.\n\n### Sections\n\nSection headings must use levels 2 to 5. The section heading proceeding the [title heading](#titles) must be a level 2 heading. Unlike titles, section headings may have small image icons.\n\nSection headings must not skip a heading level (i.e. do not go from a level 2 heading to a level 4 heading) and must not contain formatting or links.\n\n*Notice: On the website, heading levels 4 and 5 will not appear in the table of contents. They cannot be linked to directly either.*\n\n## Lists\n\nLists should not go over 4 levels of indentation and should not have an empty line in between each item.\n\nFor nested lists, bullets or numbers must align with the item content of their parent lists.\n\nThe following example was done incorrectly (take note of the spacing before the bullet):\n\n```markdown\n1. Fly a kite\n - Don't fly a kite if it's raining\n```\n\nThe following example was done correctly:\n\n```markdown\n1. Fly a kite\n - Don't fly a kite if it's raining\n```\n\n### Bulleted\n\nBulleted lists must use a hyphen (`-`). These must then be followed by one space. (Example shown below.)\n\n```markdown\n- osu!\n - Hit circle\n - Combo number\n - Approach circle\n - Slider\n - Hit circles\n - Slider body\n - Slider ticks\n - Spinner\n- osu!taiko\n```\n\n### Numbered\n\nThe numbers in a numbered list must be incremented to represent their step.\n\n```markdown\n1. Download the osu! installer.\n2. Run the installer.\n 1. To change the installation location, click the text underneath the progression bar.\n 2. The installer will prompt for a new location, choose the installation folder.\n3. osu! will start up once installation is complete.\n4. Sign in.\n```\n\n### Mixed\n\nCombining both bulleted and numbered lists should be done sparingly.\n\n```markdown\n1. Download a skin from the forums.\n2. Load the skin file into osu!.\n - If the file is a `.zip`, unzip it and move the contents into the `Skins/` folder (found in your osu! installation folder).\n - If the file is a `.osk`, open it on your desktop or drag-and-drop it into the game client.\n3. Open osu!, if it is not opened, and select the skin in the options.\n - This may have been completed if you opened the `.osk` file or drag-and-dropped it into the game client.\n```\n\n## Code\n\nThe markup for code is a grave mark (`` ` ``). To put grave marks in code, use double grave marks instead. If the grave mark is at the start or end, pad it with one space. (Example shown below.)\n\n```markdown\n`` ` ``\n`` `Space` ``\n```\n\n### Keyboard keys\n\n*Notice: When denoting the letter itself, and not the keyboard key, use quotation marks instead.*\n\nWhen representing keyboard keys, use capital letters for single characters and title case for modifiers. Use the plus symbol (`+`) (without code) to represent key combinations. (Example shown below.)\n\n```markdown\npippi is spelt with a lowercase \"p\" like peppy.\n\nPress `Ctrl` + `O` to open the open dialog.\n```\n\nWhen representing a space or the spacebar, use `` `Space` ``.\n\n### Button and menu text\n\nWhen copying the text from a menu or button, the letter casing should be copied as it appears. (Example shown below.)\n\n```markdown\nThe `osu!direct` button is visible in the main menu on the right side, if you have an active osu!supporter tag.\n```\n\n### Folder and directory names\n\nWhen copying the name of a folder or directory, the letter casing should be copied as it appears, but prefer lowercased paths when possible. Directory paths must not be absolute (i.e. do not start the directory name from the drive letter or from the root folder). (Example shown below.)\n\n```markdown\nosu! is installed in the `AppData/Local` folder by default, unless specified otherwise during installation.\n```\n\n### Keywords and commands\n\nWhen copying a keyword or command, the letter casing should be copied as it appears or how someone normally would type it. If applicable, prefer lowercase letters. (Example shown below.)\n\n```markdown\nAs of now, the `Name` and `Author` commands in the skin configuration file (`skin.ini`) do nothing.\n```\n\n### File names\n\nWhen copying the name of a file, the letter casing should be copied as it appears. If applicable, prefer lowercase letters. (Example shown below.)\n\n```markdown\nTo play osu!, double click the `osu!.exe` icon.\n```\n\n### File extensions\n\n*Notice: File formats (not to be confused with file extensions) must be written in capital letters without the prefixed fullstop (`.`).*\n\nFile extensions must be prefixed with a fullstop (`.`) and be followed by the file extension in lowercase letters. (Example shown below.)\n\n```markdown\nThe JPG (or JPEG) file format has the `.jpg` (or `.jpeg`) extension.\n```\n\n### Chat channels\n\nWhen copying the name of a chat channel, start it with a hash (`#`), followed by the channel name in lowercase letters. (Example shown below.)\n\n```markdown\n`#lobby` is where you can advertise your multi room.\n```\n\n## Preformatted text (code blocks)\n\n*Notice: Syntax highlighting for preformatted text is not implemented on the website yet.*\n\nPreformatted text (also known as code blocks) must be fenced using three grave marks. They should set the language identifier for syntax highlighting.\n\n## Links\n\nThere are two types of links: inline and reference. Inline has two styles.\n\nThe following is an example of both inline styles:\n\n```markdown\n[Game Modifiers](/wiki/Game_Modifiers)\n\n\n```\n\nThe following is an example of the reference style:\n\n```markdown\n[Game Modifiers][game mods link]\n\n[game mods link]: /wiki/Game_Modifiers\n```\n\n---\n\nLinks must use the inline style if they are only referenced once. The inline angle brackets style should be avoided. References to reference links must be placed at the bottom of the article.\n\n### Internal links\n\n*Note: Internal links refer to links that stay inside the `https://osu.ppy.sh/` domain.*\n\n#### Wiki links\n\nAll links that point to an wiki article should start with `/wiki/` followed by the path to get to the article you are targeting. Relative links may also be used. Some examples include the following:\n\n```markdown\n[FAQ](/wiki/FAQ)\n[pippi](/wiki/Mascots#-pippi)\n[Beatmaps](../)\n[Pattern](./Pattern)\n```\n\nWiki links must not use redirects and must not have a trailing forward slash (`/`).\n\nBad examples include the following:\n\n```markdown\n[Article styling criteria](/wiki/ASC)\n[Developers](/wiki/Developers/)\n[Developers](/wiki/Developers/#game-client-developers)\n```\n\nGood examples include the following:\n\n```markdown\n[Article styling criteria](/wiki/Article_styling_criteria)\n[Developers](/wiki/Developers)\n[Developers](/wiki/Developers#game-client-developers)\n```\n\n##### Sub-article links\n\nWiki links that point to a sub-article should include the parent article's folder name in its link text. See the following example:\n\n```markdown\n*See also: [Beatmap Editor/Design](/wiki/Beatmap_Editor/Design)*\n```\n\n##### Section links\n\n*Notice: On the website, heading levels 4 and 5 are not given the id attribute. This means that they can not be linked to directly.*\n\nWiki links that point to a section of an article may use the section sign symbol (`§`). See the following example:\n\n```markdown\n*For timing rules, see: [Ranking Criteria § Timing](/wiki/Ranking_Criteria#timing)*\n```\n\n#### Other osu! links\n\nThe URL from the address bar of your web browser should be copied as it is when linking to other osu! web pages. The `https://osu.ppy.sh` part of the URL must be kept.\n\n##### User profiles\n\nAll usernames must be linked on first occurrence. Other occurrences are optional, but must be consistent throughout the entire article for all usernames. If it is difficult to determine the user's id, it may be skipped over.\n\nWhen linking to a user profile, the user's id number must be used. Use the new website (`https://osu.ppy.sh/users/{username})`) to get the user's id.\n\nThe link text of the user link should be the user's current name.\n\n##### Difficulties\n\nWhenever linking to a single difficulty, use this format as the link text:\n\n```\n{artist} - {title} ({creator}) [{difficuty_name}]\n```\n\nThe link must actually link to that difficulty. Beatmap difficulty URLs must be formatted as follows:\n\n```\nhttps://osu.ppy.sh/beatmapsets/{BeatmapSetID}#{mode}/{BeatmapID}\n```\n\nThe difficulty name may be left outside of the link text, but doing so must be consistent throughout the entire article.\n\n##### Beatmaps\n\nWhenever linking to a beatmap, use this format as the link text:\n\n```\n{artist} - {title} ({creator})\n```\n\nAll beatmap URLs must be formatted as follows:\n\n```\nhttps://osu.ppy.sh/beatmapsets/{BeatmapSetID}\n```\n\n### External links\n\n*Notice: External links refers to links that go outside the `https://osu.ppy.sh/` domain.*\n\nThe `https` protocol must be used, unless the site does not support it. External links must be a clean and direct link to a reputable source. The link text should be the title of the page it is linking to. The URL from the address bar of your web browser should be copied as it is when linking to other external pages.\n\nThere are no visual differences between external and osu! web links. Due to this, the website name should be included in the title text. See the following example:\n\n```markdown\n*For more information about music theory, see: [Music theory](https://en.wikipedia.org/wiki/Music_theory \"Wikipedia\")*\n```\n\n## Images\n\nThere are two types of image links: inline and reference. Examples:\n\n**Inline style:**\n\n```markdown\n![](/wiki/shared/flag/AU.gif)\n```\n\n**Reference style:**\n\n```markdown\n![][flag_AU]\n\n[flag_AU]: /wiki/shared/flag/AU.gif\n```\n\nImages should use the inline linking style. References to reference links must be placed at the bottom of the article.\n\nImages must be placed in a folder named `img`, located in the article's folder. Images that are used in multiple articles should be stored in the `/wiki/shared/` folder.\n\n### Image caching\n\nImages on the website are cached for up to 60 days. The cached image is matched with the image link's URL.\n\nWhen updating an image, either change the image's name or append a query string to the URL. In both cases, all translations linking to the updated image should also be updated.\n\n### Formats and quality\n\nImages should use the JPG format at quality 8 (80 or 80%, depending on the program). If the image contains transparency or has text that must be readable, use the PNG format instead. If the image contains an animation, the GIF format can be used; however, this should be used sparingly as these may take longer to load or can be bigger then the [max file size](#file-size).\n\n### File size\n\nImages must be under 1 megabyte, otherwise they will fail to load. Downscaling and using JPG at 80% is almost always under the size limit.\n\nAll images should be optimised as much as possible. Use [jpeg-archive](https://github.com/danielgtaylor/jpeg-archive \"GitHub\") to compress JPEG images. For consistency, use the following command for jpeg-archive:\n\n```sh\njpeg-recompress -am smallfry \n```\n\nWhere `` is the file name to be compressed and `` is the compressed file name.\n\n### File names\n\n*Notice: File extensions must use lowercase letters, otherwise they will fail to load!*\n\nUse hyphens (`-`) when spacing words. When naming an image, the file name should be meaningful or descriptive but short.\n\n### Formatting and positioning\n\n*Note: It is currently not possible to float an image or have text wrap around it.*\n\nImages on the website will be centred when it is on a single line, by themself. Otherwise, they will be positioned inline with the paragraph. The following example will place the image in the center:\n\n```markdown\nInstalling osu! is easy. First, download the installer from the download page.\n\n![](img/download-page.jpg)\n\nThen locate the installer and run it.\n```\n\n### Alt text\n\nImages should have alt text unless it is for decorative purposes.\n\n### Captions\n\nImages are given captions on the website if they fulfill these conditions:\n\n1. The image is by itself.\n2. The image is not inside a heading.\n3. The image has title text.\n\nCaptions are assumed via the title text, which must be in plain text. Images with captions are also centred with the image on the website.\n\n### Max image width\n\nThe website's max image width is the width of the article body. Images should be no wider than 800 pixels.\n\n### Annotating images\n\nWhen annotating images, use *Torus Regular*. For Chinese, Korean, Japanese characters, use *Microsoft YaHei*.\n\nAnnotating images should be avoided, as it is difficult for translators (and other editors) to edit them.\n\n#### Translating annotated images\n\nWhen translating annotated images, the localised image version must be placed in the same directory as the original version (i.e. the English version). The filename of a localised image version must start with the original version's name, followed by a hyphen, followed by the locale name (in capital letters). See the following examples:\n\n- `hardrock-mod-vs-easy-mod.jpg` for English\n- `hardrock-mod-vs-easy-mod-DE.jpg` for German\n- `hardrock-mod-vs-easy-mod-ZH-TW.jpg` for Traditional Chinese\n\n### Screenshots of gameplay\n\nAll screenshots of gameplay must be done in the stable build, unless it is for a specific feature that is unavailable in the stable build. You should use the in-game screenshot feature (`F12`).\n\n#### Game client settings\n\n*Note: If you do not want to change your current settings for the wiki, you can move your `osu!..cfg` out of the osu! folder and move it back later.*\n\nYou must set these settings before taking a screenshot of the game client (settings not stated below are assumed to be at their defaults):\n\n- Select language: `English`\n- Prefer metadata in original language: `Enabled`\n- Resolution: `1280x720`\n- Fullscreen mode: `Disabled`\n- Parallax: `Disabled`\n- Menu tips: `Disabled`\n- Seasonal backgrounds: `Never`\n- Always show key overlay: `Enabled`\n- Current skin: `Default` (first option)\n\n*Notice to translators: If you are translating an article containing screenshots of the game, you may set the game client's language to the language you are translating in.*\n\n### Image links\n\nImages must not be part of a link text.\n\nFlag icons next to user links must be separate from the link text. See the following example:\n\n```markdown\n![][flag_AU] [peppy](https://osu.ppy.sh/users/2)\n```\n\n### Flag icons\n\n*For a list of flag icons, see: [issue \\#328](https://github.com/ppy/osu-wiki/issues/328 \"GitHub\")*\n\nThe flag icons use the two letter code (in all capital letters) and end with `.gif`. When adding a flag inline, use this format:\n\n```markdown\n![](/wiki/shared/flag/xx.gif)\n```\n\nWhere `xx` is the [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 \"Wikipedia\") two-lettered country code for the flag.\n\nThe full country name should be added in the title text. The country code in the alternate text is optional, but must be applied to all flag icons in the article.\n\n## Tables\n\nTables on the website only support headings along the first row.\n\nTables must not be beautified (do not pad cells with extra spaces to make their widths uniform). They must have a vertical bar (`|`) on the left and right sides and the text of each cell must be padded with one space on both sides. Empty cells must use a vertical bar (`|`) followed by two spaces then another vertical bar (`|`).\n\nThe delimiter row (the next line after the table heading) must use only three characters per column (and be padded with a space on both sides), which must look like one of the following:\n\n- `:--` (for left align)\n- `:-:` (for centre align)\n- `--:` (for right align)\n\n---\n\nThe following is an example of what a table should look like:\n\n```markdown\n| Team \"Picturesque\" Red | Score | Team \"Statuesque\" Blue | Average Beatmap Stars |\n| :-- | :-: | --: | :-- |\n| **peppy** | 5 - 2 | pippi | 9.3 stars |\n| Aiko | 1 - 6 | **Alisa** | 4.2 stars |\n| Ryūta | 3 - 4 | **Yuzu** | 5.1 stars |\n| **Taikonator** | 7 - 0 | Tama | 13.37 stars |\n| Maria | No Contest | Mocha | |\n```\n\n## Blockquotes\n\nThe blockquote is limited to quoting text from someone. It must not be used to format text otherwise.\n\n## Thematic breaks\n\nThe thematic break (also known as the horizontal rule or line) should be used sparingly. A few uses of the thematic break may include (but is not limited to):\n\n- separating images from text\n- separating multiple images that follow one another\n- shifting the topic within a section\n\nThese must have an empty line before and after the markup. Thematic breaks must use only three hyphens, as depicted below:\n\n```markdown\n---\n```\n" }; + + // From https://osu.ppy.sh/api/v2/wiki/en/Article_styling_criteria + private APIWikiPage responseArticleParentPage => new APIWikiPage + { + Title = "Article styling criteria", + Layout = "markdown_page", + Path = "Article_styling_criteria", + Locale = "en", + Subtitle = null, + Markdown = + "---\ntags:\n - wiki standards\n---\n\n# Article styling criteria\n\n*For news posts, see: [News Styling Criteria](/wiki/News_styling_criteria)*\n\nThe article styling criteria (ASC) serve as the osu! wiki's enforced styling standards to keep consistency in clarity, formatting, and layout in all articles, and to help them strive for proper grammar, correct spelling, and correct information.\n\nThese articles are primarily tools to aid in reviewing and represent the consensus of osu! wiki contributors formed over the years. Since the wiki is a collaborative effort through the review process, it is not necessary to read or memorise all of the ASC at once. If you are looking to contribute, read the [contribution guide](/wiki/osu!_wiki/Contribution_guide).\n\nTo suggest changes regarding the article styling criteria, [open an issue on GitHub](https://github.com/ppy/osu-wiki/issues/new).\n\n## Standards\n\n*Notice: The articles below use [RFC 2119](https://tools.ietf.org/html/rfc2119) to describe requirement levels.*\n\nThe article styling criteria are split up into two articles:\n\n- [Formatting](Formatting): includes Markdown and other formatting rules\n- [Writing](Writing): includes writing practices and other grammar rules\n" + }; } } diff --git a/osu.Game/Online/API/Requests/GetWikiRequest.cs b/osu.Game/Online/API/Requests/GetWikiRequest.cs index e0f967ec15..7c84e1f790 100644 --- a/osu.Game/Online/API/Requests/GetWikiRequest.cs +++ b/osu.Game/Online/API/Requests/GetWikiRequest.cs @@ -11,15 +11,16 @@ namespace osu.Game.Online.API.Requests { public class GetWikiRequest : APIRequest { - private readonly string path; + public readonly string Path; + private readonly Language language; public GetWikiRequest(string path, Language language = Language.en) { - this.path = path; + Path = path; this.language = language; } - protected override string Target => $"wiki/{language.ToCultureCode()}/{path}"; + protected override string Target => $"wiki/{language.ToCultureCode()}/{Path}"; } } From 474c1a8a7a9f2a1d91d60182596604333890f202 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 20 Jul 2022 08:58:24 +0300 Subject: [PATCH 706/803] Fix wiki overlay not handling path redirection properly --- osu.Game/Overlays/WikiOverlay.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 3c5cb82a88..b54aeb4aa3 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -100,6 +100,11 @@ namespace osu.Game.Overlays private void onPathChanged(ValueChangedEvent e) { + // the path could change as a result of redirecting to another path cof the same page. + // we already have the correct wiki data, so we can safely return here. + if (e.NewValue == wikiData.Value?.Path) + return; + cancellationToken?.Cancel(); request?.Cancel(); @@ -121,6 +126,7 @@ namespace osu.Game.Overlays private void onSuccess(APIWikiPage response) { wikiData.Value = response; + path.Value = response.Path; if (response.Layout == index_path) { From de29078db2b869f2af498fb2a33ba5b4443d361a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 20 Jul 2022 15:16:40 +0900 Subject: [PATCH 707/803] Remove nullable disables --- osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs | 2 -- osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs | 6 ++---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs b/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs index 937d4358d5..b6968f4e06 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable enable - using System.Linq; using osu.Framework.Graphics; using osu.Game.Database; diff --git a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs index 89404b2878..b80eb40018 100644 --- a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs +++ b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.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. -#nullable disable using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,9 +19,8 @@ namespace osu.Game.Screens.Select.Carousel public class UpdateBeatmapSetButton : OsuAnimatedButton { private readonly BeatmapSetInfo beatmapSetInfo; - private SpriteIcon icon; - - private Box progressFill; + private SpriteIcon icon = null!; + private Box progressFill = null!; public UpdateBeatmapSetButton(BeatmapSetInfo beatmapSetInfo) { From 5e933cb4668946a2d8a34540b83c5345aba220f0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 20 Jul 2022 09:43:32 +0300 Subject: [PATCH 708/803] Improve comment wording Co-authored-by: Dean Herbert --- osu.Game/Overlays/WikiOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index b54aeb4aa3..148d2977c7 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -100,7 +100,7 @@ namespace osu.Game.Overlays private void onPathChanged(ValueChangedEvent e) { - // the path could change as a result of redirecting to another path cof the same page. + // the path could change as a result of redirecting to a newer location of the same page. // we already have the correct wiki data, so we can safely return here. if (e.NewValue == wikiData.Value?.Path) return; From d2a3c2594d04a15d575f33e7afff637abaec8e80 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 20 Jul 2022 16:33:52 +0900 Subject: [PATCH 709/803] Fix inspections --- osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs | 2 +- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs index d0176da0e9..e7a6e9a543 100644 --- a/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs +++ b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Online { AddStep("download beatmap", () => beatmaps.Download(test_db_model)); - AddStep("cancel download from request", () => beatmaps.GetExistingDownload(test_db_model).Cancel()); + AddStep("cancel download from request", () => beatmaps.GetExistingDownload(test_db_model)!.Cancel()); AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_db_model) == null); AddAssert("is notification cancelled", () => recentNotification.State == ProgressNotificationState.Cancelled); diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 31bc6dacf8..536322805b 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -126,10 +126,10 @@ namespace osu.Game.Tests.Online AddStep("start downloading", () => beatmapDownloader.Download(testBeatmapSet)); addAvailabilityCheckStep("state downloading 0%", () => BeatmapAvailability.Downloading(0.0f)); - AddStep("set progress 40%", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet)).SetProgress(0.4f)); + AddStep("set progress 40%", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.SetProgress(0.4f)); addAvailabilityCheckStep("state downloading 40%", () => BeatmapAvailability.Downloading(0.4f)); - AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet)).TriggerSuccess(testBeatmapFile)); + AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.TriggerSuccess(testBeatmapFile)); addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing); AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); From 0c3d43026d956be5b9e6ba8ed0534eaf99a0f378 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 02:53:29 +0900 Subject: [PATCH 710/803] Add initial structure for fps counter --- .../UserInterface/TestSceneFPSCounter.cs | 51 ++++++ osu.Game/Graphics/UserInterface/FPSCounter.cs | 149 ++++++++++++++++++ .../UserInterface/FPSCounterTooltip.cs | 96 +++++++++++ 3 files changed, 296 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneFPSCounter.cs create mode 100644 osu.Game/Graphics/UserInterface/FPSCounter.cs create mode 100644 osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFPSCounter.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFPSCounter.cs new file mode 100644 index 0000000000..d78707045b --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFPSCounter.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 NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneFPSCounter : OsuTestScene + { + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create display", () => + { + Children = new Drawable[] + { + new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new FPSCounter(), + new FPSCounter { Scale = new Vector2(2) }, + new FPSCounter { Scale = new Vector2(4) }, + } + }, + }; + }); + } + + [Test] + public void TestBasic() + { + } + } +} diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs new file mode 100644 index 0000000000..222c30c1dc --- /dev/null +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -0,0 +1,149 @@ +// 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.Cursor; +using osu.Framework.Localisation; +using osu.Framework.Platform; +using osu.Framework.Threading; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + public class FPSCounter : CompositeDrawable, IHasCustomTooltip + { + private RollingCounter msCounter = null!; + private RollingCounter fpsCounter = null!; + + private Container mainContent = null!; + + public FPSCounter() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChildren = new Drawable[] + { + mainContent = new Container + { + Alpha = 0, + Size = new Vector2(30), + Children = new Drawable[] + { + msCounter = new FrameTimeCounter + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Colour = colours.Orange2, + }, + fpsCounter = new FramesPerSecondCounter + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Y = 11, + Scale = new Vector2(0.8f), + Colour = colours.Lime3, + } + } + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + displayTemporarily(); + } + + private bool isDisplayed; + + private ScheduledDelegate? fadeOutDelegate; + + private void displayTemporarily() + { + if (!isDisplayed) + mainContent.FadeTo(1, 300, Easing.OutQuint); + + fadeOutDelegate?.Cancel(); + fadeOutDelegate = Scheduler.AddDelayed(() => + { + mainContent.FadeTo(0, 1000, Easing.In); + isDisplayed = false; + }, 2000); + } + + [Resolved] + private GameHost gameHost { get; set; } = null!; + + protected override void Update() + { + base.Update(); + + // TODO: this is wrong (elapsed clock time, not actual run time). + double newFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; + double newFps = gameHost.DrawThread.Clock.FramesPerSecond; + + bool hasSignificantChanges = + Math.Abs(msCounter.Current.Value - newFrameTime) > 5 || + Math.Abs(fpsCounter.Current.Value - newFps) > 10; + + if (hasSignificantChanges) + displayTemporarily(); + + msCounter.Current.Value = newFrameTime; + fpsCounter.Current.Value = newFps; + } + + public ITooltip GetCustomTooltip() => new FPSCounterTooltip(); + + public object TooltipContent => this; + + public class FramesPerSecondCounter : RollingCounter + { + protected override double RollingDuration => 400; + + protected override OsuSpriteText CreateSpriteText() + { + return new OsuSpriteText + { + Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), + Spacing = new Vector2(-2), + }; + } + + protected override LocalisableString FormatCount(double count) + { + return $"{count:#,0}fps"; + } + } + + public class FrameTimeCounter : RollingCounter + { + protected override double RollingDuration => 1000; + + protected override OsuSpriteText CreateSpriteText() + { + return new OsuSpriteText + { + Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), + Spacing = new Vector2(-1), + }; + } + + protected override LocalisableString FormatCount(double count) + { + if (count < 1) + return $"{count:N1}ms"; + + return $"{count:N0}ms"; + } + } + } +} diff --git a/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs new file mode 100644 index 0000000000..af95fbe556 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs @@ -0,0 +1,96 @@ +// 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.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Platform; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + public class FPSCounterTooltip : CompositeDrawable, ITooltip + { + private OsuTextFlowContainer textFlow = null!; + + [Resolved] + private GameHost gameHost { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.Both; + + CornerRadius = 15; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Gray1, + Alpha = 1, + RelativeSizeAxes = Axes.Both, + }, + new OsuTextFlowContainer(cp => + { + cp.Font = OsuFont.Default.With(weight: FontWeight.SemiBold); + cp.Spacing = new Vector2(-1); + }) + { + AutoSizeAxes = Axes.Both, + TextAnchor = Anchor.TopRight, + Margin = new MarginPadding { Left = 5, Vertical = 10 }, + Text = string.Join('\n', gameHost.Threads.Select(t => t.Name)) + }, + textFlow = new OsuTextFlowContainer(cp => + { + cp.Font = OsuFont.Default.With(fixedWidth: true, weight: FontWeight.Regular); + cp.Spacing = new Vector2(-1); + }) + { + Width = 190, + Margin = new MarginPadding { Left = 35, Right = 10, Vertical = 10 }, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.TopRight, + }, + }; + } + + private int lastUpdate; + + protected override void Update() + { + int currentSecond = (int)(Clock.CurrentTime / 100); + + if (currentSecond != lastUpdate) + { + lastUpdate = currentSecond; + + textFlow.Clear(); + + foreach (var thread in gameHost.Threads) + { + var clock = thread.Clock; + + string maximum = $"{(clock.MaximumUpdateHz > 0 && clock.MaximumUpdateHz < 10000 ? clock.MaximumUpdateHz.ToString("0") : "∞"),4}"; + + textFlow.AddParagraph($"{clock.FramesPerSecond:0}/{maximum}fps ({clock.ElapsedFrameTime:0.00}ms)"); + } + } + } + + public void SetContent(object content) + { + } + + public void Move(Vector2 pos) + { + Position = pos; + } + } +} From 03e644e548a066efe5c957b6f536b951883fcb8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 20:28:58 +0900 Subject: [PATCH 711/803] Choose colours based on relative performance goals --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 222c30c1dc..a999c1338a 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -4,11 +4,13 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Graphics.Sprites; using osuTK; @@ -21,13 +23,16 @@ namespace osu.Game.Graphics.UserInterface private Container mainContent = null!; + [Resolved] + private OsuColour colours { get; set; } = null!; + public FPSCounter() { AutoSizeAxes = Axes.Both; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { InternalChildren = new Drawable[] { @@ -41,7 +46,6 @@ namespace osu.Game.Graphics.UserInterface { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Colour = colours.Orange2, }, fpsCounter = new FramesPerSecondCounter { @@ -49,7 +53,6 @@ namespace osu.Game.Graphics.UserInterface Origin = Anchor.TopRight, Y = 11, Scale = new Vector2(0.8f), - Colour = colours.Lime3, } } }, @@ -97,8 +100,27 @@ namespace osu.Game.Graphics.UserInterface if (hasSignificantChanges) displayTemporarily(); - msCounter.Current.Value = newFrameTime; + // If the frame time spikes up, make sure it shows immediately on the counter. + if (msCounter.Current.Value < 20 && newFrameTime > 20) + msCounter.SetCountWithoutRolling(newFrameTime); + else + msCounter.Current.Value = newFrameTime; + fpsCounter.Current.Value = newFps; + + fpsCounter.Colour = getColour(fpsCounter.DisplayedCount / gameHost.DrawThread.Clock.MaximumUpdateHz); + + double equivalentHz = 1000 / msCounter.DisplayedCount; + + msCounter.Colour = getColour(equivalentHz / gameHost.UpdateThread.Clock.MaximumUpdateHz); + } + + private ColourInfo getColour(double performanceRatio) + { + if (performanceRatio < 0.5f) + return Interpolation.ValueAt(performanceRatio, colours.Red, colours.Orange2, 0, 0.5, Easing.Out); + + return Interpolation.ValueAt(performanceRatio, colours.Orange2, colours.Lime3, 0.5, 1, Easing.Out); } public ITooltip GetCustomTooltip() => new FPSCounterTooltip(); From 0fb959a565023b4939588b4d97e713bbd6da6606 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 20:29:18 +0900 Subject: [PATCH 712/803] Stay displayed while hovering --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 49 +++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index a999c1338a..58be2ff5a2 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Framework.Threading; @@ -23,6 +25,10 @@ namespace osu.Game.Graphics.UserInterface private Container mainContent = null!; + private Container background = null!; + + private const float idle_background_alpha = 0.4f; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -39,9 +45,24 @@ namespace osu.Game.Graphics.UserInterface mainContent = new Container { Alpha = 0, - Size = new Vector2(30), + AutoSizeAxes = Axes.Both, Children = new Drawable[] { + background = new Container + { + RelativeSizeAxes = Axes.Both, + CornerRadius = 5, + Masking = true, + Alpha = idle_background_alpha, + Children = new Drawable[] + { + new Box + { + Colour = colours.Gray0, + RelativeSizeAxes = Axes.Both, + }, + } + }, msCounter = new FrameTimeCounter { Anchor = Anchor.TopRight, @@ -65,6 +86,20 @@ namespace osu.Game.Graphics.UserInterface displayTemporarily(); } + protected override bool OnHover(HoverEvent e) + { + background.FadeTo(1, 200); + displayTemporarily(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + background.FadeTo(idle_background_alpha, 200); + displayTemporarily(); + base.OnHoverLost(e); + } + private bool isDisplayed; private ScheduledDelegate? fadeOutDelegate; @@ -75,11 +110,15 @@ namespace osu.Game.Graphics.UserInterface mainContent.FadeTo(1, 300, Easing.OutQuint); fadeOutDelegate?.Cancel(); - fadeOutDelegate = Scheduler.AddDelayed(() => + + if (!IsHovered) { - mainContent.FadeTo(0, 1000, Easing.In); - isDisplayed = false; - }, 2000); + fadeOutDelegate = Scheduler.AddDelayed(() => + { + mainContent.FadeTo(0, 1000, Easing.In); + isDisplayed = false; + }, 2000); + } } [Resolved] From 0a1744facaffd299e72d2c8ec8fe30ce29412ae1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 20:49:57 +0900 Subject: [PATCH 713/803] Add to game and bind with configuration setting --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 15 ++++++++++++++- osu.Game/OsuGame.cs | 7 +++++++ osu.Game/OsuGameBase.cs | 16 ---------------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 58be2ff5a2..2dd945d375 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -13,6 +14,7 @@ using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Graphics.Sprites; using osuTK; @@ -29,6 +31,8 @@ namespace osu.Game.Graphics.UserInterface private const float idle_background_alpha = 0.4f; + private Bindable showFpsDisplay = null!; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -38,7 +42,7 @@ namespace osu.Game.Graphics.UserInterface } [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { InternalChildren = new Drawable[] { @@ -78,12 +82,21 @@ namespace osu.Game.Graphics.UserInterface } }, }; + + showFpsDisplay = config.GetBindable(OsuSetting.ShowFpsDisplay); } protected override void LoadComplete() { base.LoadComplete(); + displayTemporarily(); + + showFpsDisplay.BindValueChanged(showFps => + { + this.FadeTo(showFps.NewValue ? 1 : 0, 100); + displayTemporarily(); + }, true); } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index bd0a2680ae..23af401dbd 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -814,6 +814,13 @@ namespace osu.Game ScreenStack.ScreenPushed += screenPushed; ScreenStack.ScreenExited += screenExited; + loadComponentSingleFile(new FPSCounter + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding(10), + }, topMostOverlayContent.Add); + if (!args?.Any(a => a == @"--no-version-overlay") ?? true) loadComponentSingleFile(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 4b5c9c0815..d07d9379f4 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -17,7 +17,6 @@ using osu.Framework.Development; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Input.Handlers; @@ -192,8 +191,6 @@ namespace osu.Game private DependencyContainer dependencies; - private Bindable fpsDisplayVisible; - private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(global_track_volume_adjust); /// @@ -404,19 +401,6 @@ namespace osu.Game AddFont(Resources, @"Fonts/Venera/Venera-Black"); } - protected override void LoadComplete() - { - base.LoadComplete(); - - // TODO: This is temporary until we reimplement the local FPS display. - // It's just to allow end-users to access the framework FPS display without knowing the shortcut key. - fpsDisplayVisible = LocalConfig.GetBindable(OsuSetting.ShowFpsDisplay); - fpsDisplayVisible.ValueChanged += visible => { FrameStatistics.Value = visible.NewValue ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; - fpsDisplayVisible.TriggerChange(); - - FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None; - } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); From f54aff2ecef798c940a5579ad451ce0ed1b31f8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 21:05:20 +0900 Subject: [PATCH 714/803] Add global key binding for FPS toggle --- osu.Game/Configuration/OsuConfigManager.cs | 6 ++++++ osu.Game/Graphics/UserInterface/FPSCounter.cs | 17 ++++++++++++----- .../Input/Bindings/GlobalActionContainer.cs | 4 ++++ .../GlobalActionKeyBindingStrings.cs | 5 +++++ osu.Game/OsuGame.cs | 8 +++++++- 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a523507205..e816fd50f3 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -224,6 +224,12 @@ namespace osu.Game.Configuration return new TrackedSettings { + new TrackedSetting(OsuSetting.ShowFpsDisplay, state => new SettingDescription( + rawValue: state, + name: GlobalActionKeyBindingStrings.ToggleFPSCounter, + value: state ? CommonStrings.Enabled.ToLower() : CommonStrings.Disabled.ToLower(), + shortcut: LookupKeyBindings(GlobalAction.ToggleFPSDisplay)) + ), new TrackedSetting(OsuSetting.MouseDisableButtons, disabledState => new SettingDescription( rawValue: !disabledState, name: GlobalActionKeyBindingStrings.ToggleGameplayMouseButtons, diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 2dd945d375..1d72f772db 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -20,7 +20,7 @@ using osuTK; namespace osu.Game.Graphics.UserInterface { - public class FPSCounter : CompositeDrawable, IHasCustomTooltip + public class FPSCounter : VisibilityContainer, IHasCustomTooltip { private RollingCounter msCounter = null!; private RollingCounter fpsCounter = null!; @@ -31,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface private const float idle_background_alpha = 0.4f; - private Bindable showFpsDisplay = null!; + private readonly BindableBool showFpsDisplay = new BindableBool(true); [Resolved] private OsuColour colours { get; set; } = null!; @@ -83,7 +83,7 @@ namespace osu.Game.Graphics.UserInterface }, }; - showFpsDisplay = config.GetBindable(OsuSetting.ShowFpsDisplay); + config.BindWith(OsuSetting.ShowFpsDisplay, showFpsDisplay); } protected override void LoadComplete() @@ -94,11 +94,18 @@ namespace osu.Game.Graphics.UserInterface showFpsDisplay.BindValueChanged(showFps => { - this.FadeTo(showFps.NewValue ? 1 : 0, 100); - displayTemporarily(); + State.Value = showFps.NewValue ? Visibility.Visible : Visibility.Hidden; + if (showFps.NewValue) + displayTemporarily(); }, true); + + State.BindValueChanged(state => showFpsDisplay.Value = state.NewValue == Visibility.Visible); } + protected override void PopIn() => this.FadeIn(100); + + protected override void PopOut() => this.FadeOut(100); + protected override bool OnHover(HoverEvent e) { background.FadeTo(1, 200); diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 21b49286ea..1ee03a6964 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -45,6 +45,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial), new KeyBinding(InputKey.F10, GlobalAction.ToggleGameplayMouseButtons), new KeyBinding(InputKey.F12, GlobalAction.TakeScreenshot), + new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.F }, GlobalAction.ToggleFPSDisplay), new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings), new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), @@ -328,5 +329,8 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTapForBPM))] EditorTapForBPM, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleFPSCounter))] + ToggleFPSDisplay, } } diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 82d03dbb5b..de1a5b189c 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -274,6 +274,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ToggleSkinEditor => new TranslatableString(getKey(@"toggle_skin_editor"), @"Toggle skin editor"); + /// + /// "Toggle FPS counter" + /// + public static LocalisableString ToggleFPSCounter => new TranslatableString(getKey(@"toggle_fps_counter"), @"Toggle FPS counter"); + /// /// "Previous volume meter" /// diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 23af401dbd..c90b4c2403 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -159,6 +159,8 @@ namespace osu.Game protected FirstRunSetupOverlay FirstRunOverlay { get; private set; } + private FPSCounter fpsCounter; + private VolumeOverlay volume; private OsuLogo osuLogo; @@ -814,7 +816,7 @@ namespace osu.Game ScreenStack.ScreenPushed += screenPushed; ScreenStack.ScreenExited += screenExited; - loadComponentSingleFile(new FPSCounter + loadComponentSingleFile(fpsCounter = new FPSCounter { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, @@ -1121,6 +1123,10 @@ namespace osu.Game switch (e.Action) { + case GlobalAction.ToggleFPSDisplay: + fpsCounter.ToggleVisibility(); + return true; + case GlobalAction.ToggleSkinEditor: skinEditor.ToggleVisibility(); return true; From 75453b78c0a39086c12ad04041503863c6d747e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 23:59:09 +0900 Subject: [PATCH 715/803] Adjust colours and metrics --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 14 +++++++++----- osu.Game/OsuGame.cs | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 1d72f772db..001eedff39 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -49,13 +49,14 @@ namespace osu.Game.Graphics.UserInterface mainContent = new Container { Alpha = 0, - AutoSizeAxes = Axes.Both, + Size = new Vector2(42, 26), Children = new Drawable[] { background = new Container { RelativeSizeAxes = Axes.Both, CornerRadius = 5, + CornerExponent = 5f, Masking = true, Alpha = idle_background_alpha, Children = new Drawable[] @@ -71,12 +72,15 @@ namespace osu.Game.Graphics.UserInterface { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + Margin = new MarginPadding(1), + Y = -1, }, fpsCounter = new FramesPerSecondCounter { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Y = 11, + Margin = new MarginPadding(1), + Y = 10, Scale = new Vector2(0.8f), } } @@ -135,7 +139,7 @@ namespace osu.Game.Graphics.UserInterface { fadeOutDelegate = Scheduler.AddDelayed(() => { - mainContent.FadeTo(0, 1000, Easing.In); + mainContent.FadeTo(0, 300, Easing.OutQuint); isDisplayed = false; }, 2000); } @@ -177,9 +181,9 @@ namespace osu.Game.Graphics.UserInterface private ColourInfo getColour(double performanceRatio) { if (performanceRatio < 0.5f) - return Interpolation.ValueAt(performanceRatio, colours.Red, colours.Orange2, 0, 0.5, Easing.Out); + return Interpolation.ValueAt(performanceRatio, colours.Red, colours.Orange2, 0, 0.5); - return Interpolation.ValueAt(performanceRatio, colours.Orange2, colours.Lime3, 0.5, 1, Easing.Out); + return Interpolation.ValueAt(performanceRatio, colours.Orange2, colours.Lime0, 0.5, 0.9); } public ITooltip GetCustomTooltip() => new FPSCounterTooltip(); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c90b4c2403..69d02b95ff 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -820,7 +820,7 @@ namespace osu.Game { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Margin = new MarginPadding(10), + Margin = new MarginPadding(5), }, topMostOverlayContent.Add); if (!args?.Any(a => a == @"--no-version-overlay") ?? true) From c7313b4198179801e1ebdec47da70063cf5d052e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 11:58:28 +0900 Subject: [PATCH 716/803] Fix alignment glitching due to non-matching anchor/origin Co-authored-by: Salman Ahmed --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 001eedff39..aaaf6b5e9b 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -198,6 +198,8 @@ namespace osu.Game.Graphics.UserInterface { return new OsuSpriteText { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), Spacing = new Vector2(-2), }; @@ -217,6 +219,8 @@ namespace osu.Game.Graphics.UserInterface { return new OsuSpriteText { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), Spacing = new Vector2(-1), }; From 57ecc3a6dfea9b8c504da70329558d973d830d59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 11:59:26 +0900 Subject: [PATCH 717/803] Remove unnecessary negative spacing from thread names Co-authored-by: Salman Ahmed --- osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs index af95fbe556..5a26d34f8d 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs @@ -39,7 +39,6 @@ namespace osu.Game.Graphics.UserInterface new OsuTextFlowContainer(cp => { cp.Font = OsuFont.Default.With(weight: FontWeight.SemiBold); - cp.Spacing = new Vector2(-1); }) { AutoSizeAxes = Axes.Both, From e1a577ea48642cdf459309ce71ca4b3414deef4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 11:59:51 +0900 Subject: [PATCH 718/803] Adjust spacing to make things feel more even Co-authored-by: Salman Ahmed --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index aaaf6b5e9b..a73c3afaf4 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -73,13 +73,13 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Margin = new MarginPadding(1), - Y = -1, + Y = -2, }, fpsCounter = new FramesPerSecondCounter { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Margin = new MarginPadding(1), + Margin = new MarginPadding(2), Y = 10, Scale = new Vector2(0.8f), } From c4089b71bd038c7694f358120b34bdd27bd83e2f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 06:00:41 +0300 Subject: [PATCH 719/803] Store maximum score results from simulated autoplay --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 56bbe031e6..031c14a091 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -126,6 +126,9 @@ namespace osu.Game.Rulesets.Scoring private bool beatmapApplied; private readonly Dictionary scoreResultCounts = new Dictionary(); + + private Dictionary? maximumResultCounts; + private readonly List hitEvents = new List(); private HitObject? lastHitObject; @@ -410,12 +413,16 @@ namespace osu.Game.Rulesets.Scoring { base.Reset(storeResults); - scoreResultCounts.Clear(); hitEvents.Clear(); lastHitObject = null; if (storeResults) + { maximumScoringValues = currentScoringValues; + maximumResultCounts = new Dictionary(scoreResultCounts); + } + + scoreResultCounts.Clear(); currentScoringValues = default; currentMaximumScoringValues = default; From 0f0b19da4a78801d12c1242ee8b4cd4b33260768 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 06:01:13 +0300 Subject: [PATCH 720/803] Populate score with remaining "miss" statistics on fail/exit --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 27 +++++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 20 ++++++++------- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 031c14a091..63ad97376f 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -430,6 +430,7 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = 0; Accuracy.Value = 1; Combo.Value = 0; + Rank.Disabled = false; Rank.Value = ScoreRank.X; HighestCombo.Value = 0; } @@ -452,6 +453,32 @@ namespace osu.Game.Rulesets.Scoring score.TotalScore = (long)Math.Round(ComputeFinalScore(ScoringMode.Standardised, score)); } + /// + /// Populates the given score with remaining statistics as "missed" and marks it with rank. + /// + public void FailScore(ScoreInfo score) + { + if (Rank.Value == ScoreRank.F) + return; + + Rank.Value = ScoreRank.F; + Rank.Disabled = true; + + Debug.Assert(maximumResultCounts != null); + + if (maximumResultCounts.TryGetValue(HitResult.LargeTickHit, out int maximumLargeTick)) + scoreResultCounts[HitResult.LargeTickMiss] = maximumLargeTick - scoreResultCounts.GetValueOrDefault(HitResult.LargeTickHit); + + if (maximumResultCounts.TryGetValue(HitResult.SmallTickHit, out int maximumSmallTick)) + scoreResultCounts[HitResult.SmallTickMiss] = maximumSmallTick - scoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit); + + int maximumBasic = maximumResultCounts.Single(kvp => kvp.Key.IsBasic()).Value; + int currentBasic = scoreResultCounts.Where(kvp => kvp.Key.IsBasic() && kvp.Key != HitResult.Miss).Sum(kvp => kvp.Value); + scoreResultCounts[HitResult.Miss] = maximumBasic - currentBasic; + + PopulateScore(score); + } + public override void ResetFromReplayFrame(ReplayFrame frame) { base.ResetFromReplayFrame(frame); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 516364e13a..490252ff2b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -267,12 +267,7 @@ namespace osu.Game.Screens.Play }, FailOverlay = new FailOverlay { - SaveReplay = () => - { - Score.ScoreInfo.Passed = false; - Score.ScoreInfo.Rank = ScoreRank.F; - return prepareAndImportScore(); - }, + SaveReplay = prepareAndImportScore, OnRetry = Restart, OnQuit = () => PerformExit(true), }, @@ -831,7 +826,6 @@ namespace osu.Game.Screens.Play return false; GameplayState.HasFailed = true; - Score.ScoreInfo.Passed = false; updateGameplayState(); @@ -849,9 +843,17 @@ namespace osu.Game.Screens.Play return true; } - // Called back when the transform finishes + /// + /// Invoked when the fail animation has finished. + /// private void onFailComplete() { + // fail completion is a good point to mark a score as failed, + // since the last judgement that caused the fail only applies to score processor after onFail. + // todo: this should probably be handled better. + Score.ScoreInfo.Passed = false; + ScoreProcessor.FailScore(Score.ScoreInfo); + GameplayClockContainer.Stop(); FailOverlay.Retries = RestartCount; @@ -1030,7 +1032,7 @@ namespace osu.Game.Screens.Play if (prepareScoreForDisplayTask == null) { Score.ScoreInfo.Passed = false; - Score.ScoreInfo.Rank = ScoreRank.F; + ScoreProcessor.FailScore(Score.ScoreInfo); } // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. From 3c2a885872865ba46d4d99c1cc89beacaa5c07a0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 06:01:48 +0300 Subject: [PATCH 721/803] Add test coverage --- .../Gameplay/TestSceneScoreProcessor.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index 9e1d786d87..dc6c9158d7 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -15,6 +16,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Tests.Visual; namespace osu.Game.Tests.Gameplay @@ -91,6 +93,41 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0)); } + [Test] + public void TestFailScore() + { + var beatmap = new Beatmap + { + HitObjects = + { + new TestHitObject(), + new TestHitObject(HitResult.LargeTickHit), + new TestHitObject(HitResult.SmallTickHit), + new TestHitObject(), + new TestHitObject(HitResult.LargeTickHit), + new TestHitObject(HitResult.SmallTickHit), + } + }; + + var scoreProcessor = new ScoreProcessor(new OsuRuleset()); + scoreProcessor.ApplyBeatmap(beatmap); + + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.LargeTickHit }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].CreateJudgement()) { Type = HitResult.SmallTickMiss }); + + var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo }; + scoreProcessor.FailScore(score); + + Assert.That(score.Rank, Is.EqualTo(ScoreRank.F)); + Assert.That(score.Statistics.Count(kvp => kvp.Value > 0), Is.EqualTo(5)); + Assert.That(score.Statistics[HitResult.Ok], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.Miss], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.LargeTickHit], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.LargeTickMiss], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.SmallTickMiss], Is.EqualTo(2)); + } + private class TestJudgement : Judgement { public override HitResult MaxResult { get; } @@ -100,5 +137,17 @@ namespace osu.Game.Tests.Gameplay MaxResult = maxResult; } } + + private class TestHitObject : HitObject + { + private readonly HitResult maxResult; + + public TestHitObject(HitResult maxResult = HitResult.Perfect) + { + this.maxResult = maxResult; + } + + public override Judgement CreateJudgement() => new TestJudgement(maxResult); + } } } From 728e22fbcecf36b76eb085fe822e8210ada63a22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 12:06:22 +0900 Subject: [PATCH 722/803] Improve tooltip display when running single thread --- osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs index 5a26d34f8d..bf53bff9b4 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs @@ -76,9 +76,11 @@ namespace osu.Game.Graphics.UserInterface { var clock = thread.Clock; - string maximum = $"{(clock.MaximumUpdateHz > 0 && clock.MaximumUpdateHz < 10000 ? clock.MaximumUpdateHz.ToString("0") : "∞"),4}"; + string maximum = clock.Throttling + ? $"/{(clock.MaximumUpdateHz > 0 && clock.MaximumUpdateHz < 10000 ? clock.MaximumUpdateHz.ToString("0") : "∞"),4}" + : string.Empty; - textFlow.AddParagraph($"{clock.FramesPerSecond:0}/{maximum}fps ({clock.ElapsedFrameTime:0.00}ms)"); + textFlow.AddParagraph($"{clock.FramesPerSecond:0}{maximum}fps ({clock.ElapsedFrameTime:0.00}ms)"); } } } From ed8e065a86fc963b18fe60771ad5ed748ea5446c Mon Sep 17 00:00:00 2001 From: TacoGuyAT Date: Thu, 21 Jul 2022 06:13:45 +0300 Subject: [PATCH 723/803] Logo triangles speed and beat sync tweaks --- .../Containers/BeatSyncedContainer.cs | 8 ++++--- osu.Game/Screens/Menu/OsuLogo.cs | 23 +++++++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 4b40add87f..41fbd2bbac 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -28,7 +28,8 @@ namespace osu.Game.Graphics.Containers public class BeatSyncedContainer : Container { private int lastBeat; - private TimingControlPoint lastTimingPoint; + protected TimingControlPoint LastTimingPoint; + protected EffectControlPoint LastEffectPoint; /// /// The amount of time before a beat we should fire . @@ -127,7 +128,7 @@ namespace osu.Game.Graphics.Containers TimeSinceLastBeat = beatLength - TimeUntilNextBeat; - if (ReferenceEquals(timingPoint, lastTimingPoint) && beatIndex == lastBeat) + if (ReferenceEquals(timingPoint, LastTimingPoint) && beatIndex == lastBeat) return; // as this event is sometimes used for sound triggers where `BeginDelayedSequence` has no effect, avoid firing it if too far away from the beat. @@ -139,7 +140,8 @@ namespace osu.Game.Graphics.Containers } lastBeat = beatIndex; - lastTimingPoint = timingPoint; + LastTimingPoint = timingPoint; + LastEffectPoint = effectPoint; } } } diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index ddeb544bc5..9864fe8bed 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -90,6 +90,8 @@ namespace osu.Game.Screens.Menu private const double early_activation = 60; + private const float triangles_paused_velocity = 0.5f; + public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; public OsuLogo() @@ -319,6 +321,15 @@ namespace osu.Game.Screens.Menu .FadeTo(visualizer_default_alpha * 1.8f * amplitudeAdjust, early_activation, Easing.Out).Then() .FadeTo(visualizer_default_alpha, beatLength); } + + if (amplitudes.Maximum > 0.3f) + this.Delay(early_activation / 2).Schedule(() => + triangles.Velocity = (float)Interpolation.Damp( + triangles.Velocity, + triangles_paused_velocity * (effectPoint.KiaiMode ? 6 : 2) + amplitudeAdjust * (effectPoint.KiaiMode ? 8 : 4), + 0.3f, + Time.Elapsed + )); } public void PlayIntro() @@ -340,22 +351,17 @@ namespace osu.Game.Screens.Menu base.Update(); const float scale_adjust_cutoff = 0.4f; - const float velocity_adjust_cutoff = 0.98f; - const float paused_velocity = 0.5f; if (musicController.CurrentTrack.IsRunning) { float maxAmplitude = lastBeatIndex >= 0 ? musicController.CurrentTrack.CurrentAmplitudes.Maximum : 0; logoAmplitudeContainer.Scale = new Vector2((float)Interpolation.Damp(logoAmplitudeContainer.Scale.X, 1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 0.9f, Time.Elapsed)); - if (maxAmplitude > velocity_adjust_cutoff) - triangles.Velocity = 1 + Math.Max(0, maxAmplitude - velocity_adjust_cutoff) * 50; - else - triangles.Velocity = (float)Interpolation.Damp(triangles.Velocity, 1, 0.995f, Time.Elapsed); + triangles.Velocity = (float)Interpolation.Damp(triangles.Velocity, triangles_paused_velocity * (LastEffectPoint.KiaiMode ? 6 : 2), 0.995f, Time.Elapsed); } else { - triangles.Velocity = paused_velocity; + triangles.Velocity = (float)Interpolation.Damp(triangles.Velocity, triangles_paused_velocity, 0.9f, Time.Elapsed); } } @@ -378,6 +384,9 @@ namespace osu.Game.Screens.Menu protected override bool OnClick(ClickEvent e) { + //triangles.AccentColours = this.FindClosestParent()!.GetBackgroundColours(2).Select(x => Graphics.Backgrounds.Triangles.InRange(x, 0)).ToArray(); + //background.Colour = this.FindClosestParent()!.GetBackgroundColours()[0]; + if (Action?.Invoke() ?? true) sampleClick.Play(); From d6c3a524948267653372766bd41136f5056959e7 Mon Sep 17 00:00:00 2001 From: TacoGuyAT Date: Thu, 21 Jul 2022 06:38:33 +0300 Subject: [PATCH 724/803] Added missing braces --- osu.Game/Screens/Menu/OsuLogo.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 9864fe8bed..b538629923 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -323,13 +323,17 @@ namespace osu.Game.Screens.Menu } if (amplitudes.Maximum > 0.3f) + { this.Delay(early_activation / 2).Schedule(() => + { triangles.Velocity = (float)Interpolation.Damp( triangles.Velocity, triangles_paused_velocity * (effectPoint.KiaiMode ? 6 : 2) + amplitudeAdjust * (effectPoint.KiaiMode ? 8 : 4), 0.3f, Time.Elapsed - )); + ); + }); + } } public void PlayIntro() From 285516b11185c4b62874181e1141996d713b5626 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 12:40:27 +0900 Subject: [PATCH 725/803] Fix `isDisplayed` never actually being set --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index a73c3afaf4..e0a4decf7f 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -131,9 +131,13 @@ namespace osu.Game.Graphics.UserInterface private void displayTemporarily() { if (!isDisplayed) + { mainContent.FadeTo(1, 300, Easing.OutQuint); + isDisplayed = true; + } fadeOutDelegate?.Cancel(); + fadeOutDelegate = null; if (!IsHovered) { From 705ff06ea5fcb09d175b21c938608f3c80937cf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 12:50:39 +0900 Subject: [PATCH 726/803] Better handle spikes and significant changes --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 68 ++++++++++++------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index e0a4decf7f..524a055903 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -22,8 +21,8 @@ namespace osu.Game.Graphics.UserInterface { public class FPSCounter : VisibilityContainer, IHasCustomTooltip { - private RollingCounter msCounter = null!; - private RollingCounter fpsCounter = null!; + private RollingCounter counterUpdateFrameTime = null!; + private RollingCounter counterDrawFPS = null!; private Container mainContent = null!; @@ -68,14 +67,14 @@ namespace osu.Game.Graphics.UserInterface }, } }, - msCounter = new FrameTimeCounter + counterUpdateFrameTime = new FrameTimeCounter { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Margin = new MarginPadding(1), Y = -2, }, - fpsCounter = new FramesPerSecondCounter + counterDrawFPS = new FramesPerSecondCounter { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -156,30 +155,47 @@ namespace osu.Game.Graphics.UserInterface { base.Update(); - // TODO: this is wrong (elapsed clock time, not actual run time). - double newFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; - double newFps = gameHost.DrawThread.Clock.FramesPerSecond; + double aimDrawFPS = gameHost.DrawThread.Clock.MaximumUpdateHz; + double aimUpdateFPS = gameHost.UpdateThread.Clock.MaximumUpdateHz; - bool hasSignificantChanges = - Math.Abs(msCounter.Current.Value - newFrameTime) > 5 || - Math.Abs(fpsCounter.Current.Value - newFps) > 10; + if (!gameHost.UpdateThread.Clock.Throttling) + { + aimUpdateFPS = aimDrawFPS = gameHost.InputThread.Clock.MaximumUpdateHz; + } + + // TODO: this is wrong (elapsed clock time, not actual run time). + double newUpdateFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; + // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. + double newDrawFps = 1000 / gameHost.DrawThread.Clock.ElapsedFrameTime; + + const double spike_time_ms = 20; + + bool hasUpdateSpike = counterUpdateFrameTime.Current.Value < spike_time_ms && newUpdateFrameTime > spike_time_ms; + bool hasDrawSpike = counterDrawFPS.Current.Value > (1000 / spike_time_ms) && newDrawFps <= (1000 / spike_time_ms); + + // If the frame time spikes up, make sure it shows immediately on the counter. + if (hasUpdateSpike) + counterUpdateFrameTime.SetCountWithoutRolling(newUpdateFrameTime); + else + counterUpdateFrameTime.Current.Value = newUpdateFrameTime; + + if (hasDrawSpike) + counterDrawFPS.SetCountWithoutRolling(newDrawFps); + else + counterDrawFPS.Current.Value = newDrawFps; + + counterDrawFPS.Colour = getColour(counterDrawFPS.DisplayedCount / aimDrawFPS); + + double displayedUpdateFPS = 1000 / counterUpdateFrameTime.DisplayedCount; + counterUpdateFrameTime.Colour = getColour(displayedUpdateFPS / aimUpdateFPS); + + bool hasSignificantChanges = hasDrawSpike + || hasUpdateSpike + || counterDrawFPS.DisplayedCount < aimDrawFPS * 0.8 + || displayedUpdateFPS < aimUpdateFPS * 0.8; if (hasSignificantChanges) displayTemporarily(); - - // If the frame time spikes up, make sure it shows immediately on the counter. - if (msCounter.Current.Value < 20 && newFrameTime > 20) - msCounter.SetCountWithoutRolling(newFrameTime); - else - msCounter.Current.Value = newFrameTime; - - fpsCounter.Current.Value = newFps; - - fpsCounter.Colour = getColour(fpsCounter.DisplayedCount / gameHost.DrawThread.Clock.MaximumUpdateHz); - - double equivalentHz = 1000 / msCounter.DisplayedCount; - - msCounter.Colour = getColour(equivalentHz / gameHost.UpdateThread.Clock.MaximumUpdateHz); } private ColourInfo getColour(double performanceRatio) @@ -196,7 +212,7 @@ namespace osu.Game.Graphics.UserInterface public class FramesPerSecondCounter : RollingCounter { - protected override double RollingDuration => 400; + protected override double RollingDuration => 1000; protected override OsuSpriteText CreateSpriteText() { From 311a0a3de019657adb600798e72261ea6db76cde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 12:57:38 +0900 Subject: [PATCH 727/803] Always show counter temporarily when aim FPS changes --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 524a055903..40aa223ddc 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -127,6 +127,9 @@ namespace osu.Game.Graphics.UserInterface private ScheduledDelegate? fadeOutDelegate; + private double aimDrawFPS; + private double aimUpdateFPS; + private void displayTemporarily() { if (!isDisplayed) @@ -155,13 +158,9 @@ namespace osu.Game.Graphics.UserInterface { base.Update(); - double aimDrawFPS = gameHost.DrawThread.Clock.MaximumUpdateHz; - double aimUpdateFPS = gameHost.UpdateThread.Clock.MaximumUpdateHz; - - if (!gameHost.UpdateThread.Clock.Throttling) - { - aimUpdateFPS = aimDrawFPS = gameHost.InputThread.Clock.MaximumUpdateHz; - } + // Handle the case where the window has become inactive or the user changed the + // frame limiter (we want to show the FPS as it's changing, even if it isn't an outlier). + bool aimRatesChanged = updateAimFPS(); // TODO: this is wrong (elapsed clock time, not actual run time). double newUpdateFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; @@ -189,7 +188,8 @@ namespace osu.Game.Graphics.UserInterface double displayedUpdateFPS = 1000 / counterUpdateFrameTime.DisplayedCount; counterUpdateFrameTime.Colour = getColour(displayedUpdateFPS / aimUpdateFPS); - bool hasSignificantChanges = hasDrawSpike + bool hasSignificantChanges = aimRatesChanged + || hasDrawSpike || hasUpdateSpike || counterDrawFPS.DisplayedCount < aimDrawFPS * 0.8 || displayedUpdateFPS < aimUpdateFPS * 0.8; @@ -198,6 +198,34 @@ namespace osu.Game.Graphics.UserInterface displayTemporarily(); } + private bool updateAimFPS() + { + if (gameHost.UpdateThread.Clock.Throttling) + { + double newAimDrawFPS = gameHost.DrawThread.Clock.MaximumUpdateHz; + double newAimUpdateFPS = gameHost.UpdateThread.Clock.MaximumUpdateHz; + + if (aimDrawFPS != newAimDrawFPS || aimUpdateFPS != newAimUpdateFPS) + { + aimDrawFPS = newAimDrawFPS; + aimUpdateFPS = newAimUpdateFPS; + return true; + } + } + else + { + double newAimFPS = gameHost.InputThread.Clock.MaximumUpdateHz; + + if (aimDrawFPS != newAimFPS || aimUpdateFPS != newAimFPS) + { + aimUpdateFPS = aimDrawFPS = newAimFPS; + return true; + } + } + + return false; + } + private ColourInfo getColour(double performanceRatio) { if (performanceRatio < 0.5f) From 56106e43d2244b03472ade2e6e375599c0ed0c5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 13:06:43 +0900 Subject: [PATCH 728/803] Avoid div-by-zero --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 40aa223ddc..9d5aa525d7 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -165,7 +166,7 @@ namespace osu.Game.Graphics.UserInterface // TODO: this is wrong (elapsed clock time, not actual run time). double newUpdateFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. - double newDrawFps = 1000 / gameHost.DrawThread.Clock.ElapsedFrameTime; + double newDrawFps = 1000 / Math.Max(0.001, gameHost.DrawThread.Clock.ElapsedFrameTime); const double spike_time_ms = 20; From c1bcbd9c8a01c37a4df9aa9f3c1bb455ea83aaec Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 07:20:58 +0300 Subject: [PATCH 729/803] Fix fail score not handling bonus/tick-only beatmaps --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 63ad97376f..7fd21ddc54 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -472,7 +472,7 @@ namespace osu.Game.Rulesets.Scoring if (maximumResultCounts.TryGetValue(HitResult.SmallTickHit, out int maximumSmallTick)) scoreResultCounts[HitResult.SmallTickMiss] = maximumSmallTick - scoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit); - int maximumBasic = maximumResultCounts.Single(kvp => kvp.Key.IsBasic()).Value; + int maximumBasic = maximumResultCounts.SingleOrDefault(kvp => kvp.Key.IsBasic()).Value; int currentBasic = scoreResultCounts.Where(kvp => kvp.Key.IsBasic() && kvp.Key != HitResult.Miss).Sum(kvp => kvp.Value); scoreResultCounts[HitResult.Miss] = maximumBasic - currentBasic; From 5513a8b6b4e732e10608424099e0ab43f5c9c1e2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 07:21:27 +0300 Subject: [PATCH 730/803] Fix changelog overlay tests failing due to missing `CreatedAt` date --- osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index 4c39dc34d5..c5ac3dd442 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -108,6 +108,7 @@ namespace osu.Game.Tests.Visual.Online Version = "2018.712.0", DisplayVersion = "2018.712.0", UpdateStream = streams[OsuGameBase.CLIENT_STREAM_NAME], + CreatedAt = new DateTime(2018, 7, 12), ChangelogEntries = new List { new APIChangelogEntry @@ -171,6 +172,7 @@ namespace osu.Game.Tests.Visual.Online { Version = "2019.920.0", DisplayVersion = "2019.920.0", + CreatedAt = new DateTime(2019, 9, 20), UpdateStream = new APIUpdateStream { Name = "Test", From 2f16174d3dc2ed1e0bd6653160f99a75d308f415 Mon Sep 17 00:00:00 2001 From: TacoGuyAT Date: Thu, 21 Jul 2022 07:25:44 +0300 Subject: [PATCH 731/803] Changed control points set to private; Cleanup --- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 4 ++-- osu.Game/Screens/Menu/OsuLogo.cs | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 41fbd2bbac..e1998a1d7f 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -28,8 +28,8 @@ namespace osu.Game.Graphics.Containers public class BeatSyncedContainer : Container { private int lastBeat; - protected TimingControlPoint LastTimingPoint; - protected EffectControlPoint LastEffectPoint; + protected TimingControlPoint LastTimingPoint { get; private set; } + protected EffectControlPoint LastEffectPoint { get; private set; } /// /// The amount of time before a beat we should fire . diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index b538629923..332a0da01b 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -388,9 +388,6 @@ namespace osu.Game.Screens.Menu protected override bool OnClick(ClickEvent e) { - //triangles.AccentColours = this.FindClosestParent()!.GetBackgroundColours(2).Select(x => Graphics.Backgrounds.Triangles.InRange(x, 0)).ToArray(); - //background.Colour = this.FindClosestParent()!.GetBackgroundColours()[0]; - if (Action?.Invoke() ?? true) sampleClick.Play(); From ad09e728fd5dd5c1b1f94672f8486ec066b778f3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 08:12:06 +0300 Subject: [PATCH 732/803] Move `Passed` assignment inside `FailScore` --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 ++ osu.Game/Screens/Play/Player.cs | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 7fd21ddc54..ce9e06ce9c 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -461,6 +461,8 @@ namespace osu.Game.Rulesets.Scoring if (Rank.Value == ScoreRank.F) return; + score.Passed = false; + Rank.Value = ScoreRank.F; Rank.Disabled = true; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 490252ff2b..9a058e45c5 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -851,7 +851,6 @@ namespace osu.Game.Screens.Play // fail completion is a good point to mark a score as failed, // since the last judgement that caused the fail only applies to score processor after onFail. // todo: this should probably be handled better. - Score.ScoreInfo.Passed = false; ScoreProcessor.FailScore(Score.ScoreInfo); GameplayClockContainer.Stop(); @@ -1030,10 +1029,7 @@ namespace osu.Game.Screens.Play // 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; ScoreProcessor.FailScore(Score.ScoreInfo); - } // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. // To resolve test failures, forcefully end playing synchronously when this screen exits. From 9df49db45ff3a5a740221eab9bf618a3b98100dc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 08:12:46 +0300 Subject: [PATCH 733/803] Include bonus/ignore judgements in statistics fill logic --- osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs | 8 +++++++- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index dc6c9158d7..4123412ab6 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -103,9 +103,11 @@ namespace osu.Game.Tests.Gameplay new TestHitObject(), new TestHitObject(HitResult.LargeTickHit), new TestHitObject(HitResult.SmallTickHit), + new TestHitObject(HitResult.SmallBonus), new TestHitObject(), new TestHitObject(HitResult.LargeTickHit), new TestHitObject(HitResult.SmallTickHit), + new TestHitObject(HitResult.LargeBonus), } }; @@ -115,17 +117,21 @@ namespace osu.Game.Tests.Gameplay scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok }); scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.LargeTickHit }); scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].CreateJudgement()) { Type = HitResult.SmallTickMiss }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], beatmap.HitObjects[3].CreateJudgement()) { Type = HitResult.SmallBonus }); var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo }; scoreProcessor.FailScore(score); Assert.That(score.Rank, Is.EqualTo(ScoreRank.F)); - Assert.That(score.Statistics.Count(kvp => kvp.Value > 0), Is.EqualTo(5)); + Assert.That(score.Passed, Is.False); + Assert.That(score.Statistics.Count(kvp => kvp.Value > 0), Is.EqualTo(7)); Assert.That(score.Statistics[HitResult.Ok], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.Miss], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.LargeTickHit], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.LargeTickMiss], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.SmallTickMiss], Is.EqualTo(2)); + Assert.That(score.Statistics[HitResult.SmallBonus], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.IgnoreMiss], Is.EqualTo(1)); } private class TestJudgement : Judgement diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index ce9e06ce9c..56d025e9a5 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -474,6 +474,10 @@ namespace osu.Game.Rulesets.Scoring if (maximumResultCounts.TryGetValue(HitResult.SmallTickHit, out int maximumSmallTick)) scoreResultCounts[HitResult.SmallTickMiss] = maximumSmallTick - scoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit); + int maximumBonusOrIgnore = maximumResultCounts.Where(kvp => kvp.Key.IsBonus() || kvp.Key == HitResult.IgnoreHit).Sum(kvp => kvp.Value); + int currentBonusOrIgnore = scoreResultCounts.Where(kvp => kvp.Key.IsBonus() || kvp.Key == HitResult.IgnoreHit).Sum(kvp => kvp.Value); + scoreResultCounts[HitResult.IgnoreMiss] = maximumBonusOrIgnore - currentBonusOrIgnore; + int maximumBasic = maximumResultCounts.SingleOrDefault(kvp => kvp.Key.IsBasic()).Value; int currentBasic = scoreResultCounts.Where(kvp => kvp.Key.IsBasic() && kvp.Key != HitResult.Miss).Sum(kvp => kvp.Value); scoreResultCounts[HitResult.Miss] = maximumBasic - currentBasic; From a97170a2723e6de2f436b08485c54734c7108829 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 08:16:17 +0300 Subject: [PATCH 734/803] Keep `Rank` bindable enabled on score fail --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 56d025e9a5..59fee0f97b 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -462,9 +462,7 @@ namespace osu.Game.Rulesets.Scoring return; score.Passed = false; - Rank.Value = ScoreRank.F; - Rank.Disabled = true; Debug.Assert(maximumResultCounts != null); From 07e1763a7079a9e20713dd9ce14cbac758900510 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 14:24:56 +0900 Subject: [PATCH 735/803] Tweak velocity a bit more (and simplify in multiple places) --- osu.Game/Screens/Menu/OsuLogo.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 332a0da01b..0909f615f2 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -322,18 +322,10 @@ namespace osu.Game.Screens.Menu .FadeTo(visualizer_default_alpha, beatLength); } - if (amplitudes.Maximum > 0.3f) + this.Delay(early_activation).Schedule(() => { - this.Delay(early_activation / 2).Schedule(() => - { - triangles.Velocity = (float)Interpolation.Damp( - triangles.Velocity, - triangles_paused_velocity * (effectPoint.KiaiMode ? 6 : 2) + amplitudeAdjust * (effectPoint.KiaiMode ? 8 : 4), - 0.3f, - Time.Elapsed - ); - }); - } + triangles.Velocity += amplitudeAdjust * (effectPoint.KiaiMode ? 6 : 3); + }); } public void PlayIntro() @@ -361,7 +353,7 @@ namespace osu.Game.Screens.Menu float maxAmplitude = lastBeatIndex >= 0 ? musicController.CurrentTrack.CurrentAmplitudes.Maximum : 0; logoAmplitudeContainer.Scale = new Vector2((float)Interpolation.Damp(logoAmplitudeContainer.Scale.X, 1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 0.9f, Time.Elapsed)); - triangles.Velocity = (float)Interpolation.Damp(triangles.Velocity, triangles_paused_velocity * (LastEffectPoint.KiaiMode ? 6 : 2), 0.995f, Time.Elapsed); + triangles.Velocity = (float)Interpolation.Damp(triangles.Velocity, triangles_paused_velocity * (LastEffectPoint.KiaiMode ? 4 : 2), 0.995f, Time.Elapsed); } else { From a05d7f4d8ca28dd7022ee94d280e267133d7e174 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 16:06:06 +0900 Subject: [PATCH 736/803] Change carousel terminology to not use `Children` / `InternalChildren` --- osu.Game/Screens/Select/BeatmapCarousel.cs | 20 ++++++------- .../Select/Carousel/CarouselBeatmapSet.cs | 12 ++++---- .../Screens/Select/Carousel/CarouselGroup.cs | 30 ++++++++++--------- .../Carousel/CarouselGroupEagerSelect.cs | 18 +++++------ .../Carousel/DrawableCarouselBeatmapSet.cs | 2 +- .../Select/Carousel/DrawableCarouselItem.cs | 4 +-- 6 files changed, 44 insertions(+), 42 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 72e6c7d159..8b8a85b243 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Select private readonly NoResultsPlaceholder noResultsPlaceholder; - private IEnumerable beatmapSets => root.Children.OfType(); + private IEnumerable beatmapSets => root.Items.OfType(); // todo: only used for testing, maybe remove. private bool loadedTestBeatmaps; @@ -300,7 +300,7 @@ namespace osu.Game.Screens.Select if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSet)) return; - root.RemoveChild(existingSet); + root.RemoveItem(existingSet); itemsCache.Invalidate(); if (!Scroll.UserScrolling) @@ -321,7 +321,7 @@ namespace osu.Game.Screens.Select if (newSet != null) { - root.AddChild(newSet); + root.AddItem(newSet); // check if we can/need to maintain our current selection. if (previouslySelectedID != null) @@ -415,7 +415,7 @@ namespace osu.Game.Screens.Select if (selectedBeatmap == null) return; - var unfilteredDifficulties = selectedBeatmapSet.Children.Where(s => !s.Filtered.Value).ToList(); + var unfilteredDifficulties = selectedBeatmapSet.Items.Where(s => !s.Filtered.Value).ToList(); int index = unfilteredDifficulties.IndexOf(selectedBeatmap); @@ -798,7 +798,7 @@ namespace osu.Game.Screens.Select scrollTarget = null; - foreach (CarouselItem item in root.Children) + foreach (CarouselItem item in root.Items) { if (item.Filtered.Value) continue; @@ -964,26 +964,26 @@ namespace osu.Game.Screens.Select this.carousel = carousel; } - public override void AddChild(CarouselItem i) + public override void AddItem(CarouselItem i) { CarouselBeatmapSet set = (CarouselBeatmapSet)i; BeatmapSetsByID.Add(set.BeatmapSet.ID, set); - base.AddChild(i); + base.AddItem(i); } public void RemoveChild(Guid beatmapSetID) { if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSet)) - RemoveChild(carouselBeatmapSet); + RemoveItem(carouselBeatmapSet); } - public override void RemoveChild(CarouselItem i) + public override void RemoveItem(CarouselItem i) { CarouselBeatmapSet set = (CarouselBeatmapSet)i; BeatmapSetsByID.Remove(set.BeatmapSet.ID); - base.RemoveChild(i); + base.RemoveItem(i); } protected override void PerformSelection() diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index bd7b1f12a4..59d9318962 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Select.Carousel switch (State.Value) { case CarouselItemState.Selected: - return DrawableCarouselBeatmapSet.HEIGHT + Children.Count(c => c.Visible) * DrawableCarouselBeatmap.HEIGHT; + return DrawableCarouselBeatmapSet.HEIGHT + Items.Count(c => c.Visible) * DrawableCarouselBeatmap.HEIGHT; default: return DrawableCarouselBeatmapSet.HEIGHT; @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select.Carousel } } - public IEnumerable Beatmaps => InternalChildren.OfType(); + public IEnumerable Beatmaps => Items.OfType(); public BeatmapSetInfo BeatmapSet; @@ -44,15 +44,15 @@ namespace osu.Game.Screens.Select.Carousel .OrderBy(b => b.Ruleset) .ThenBy(b => b.StarRating) .Select(b => new CarouselBeatmap(b)) - .ForEach(AddChild); + .ForEach(AddItem); } protected override CarouselItem GetNextToSelect() { if (LastSelected == null || LastSelected.Filtered.Value) { - if (GetRecommendedBeatmap?.Invoke(Children.OfType().Where(b => !b.Filtered.Value).Select(b => b.BeatmapInfo)) is BeatmapInfo recommended) - return Children.OfType().First(b => b.BeatmapInfo.Equals(recommended)); + if (GetRecommendedBeatmap?.Invoke(Items.OfType().Where(b => !b.Filtered.Value).Select(b => b.BeatmapInfo)) is BeatmapInfo recommended) + return Items.OfType().First(b => b.BeatmapInfo.Equals(recommended)); } return base.GetNextToSelect(); @@ -122,7 +122,7 @@ namespace osu.Game.Screens.Select.Carousel public override void Filter(FilterCriteria criteria) { base.Filter(criteria); - Filtered.Value = InternalChildren.All(i => i.Filtered.Value); + Filtered.Value = Items.All(i => i.Filtered.Value); } public override string ToString() => BeatmapSet.ToString(); diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 1cd9674b72..e430ff3d3a 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -13,9 +13,9 @@ namespace osu.Game.Screens.Select.Carousel { public override DrawableCarouselItem? CreateDrawableRepresentation() => null; - public IReadOnlyList Children => InternalChildren; + public IReadOnlyList Items => items; - protected List InternalChildren = new List(); + private List items = new List(); /// /// Used to assign a monotonically increasing ID to children as they are added. This member is @@ -27,16 +27,18 @@ namespace osu.Game.Screens.Select.Carousel private FilterCriteria? lastCriteria; - public virtual void RemoveChild(CarouselItem i) + protected int GetIndexOfItem(CarouselItem lastSelected) => items.IndexOf(lastSelected); + + public virtual void RemoveItem(CarouselItem i) { - InternalChildren.Remove(i); + items.Remove(i); // it's important we do the deselection after removing, so any further actions based on // State.ValueChanged make decisions post-removal. i.State.Value = CarouselItemState.Collapsed; } - public virtual void AddChild(CarouselItem i) + public virtual void AddItem(CarouselItem i) { i.State.ValueChanged += state => ChildItemStateChanged(i, state.NewValue); i.ChildID = ++currentChildID; @@ -45,21 +47,21 @@ namespace osu.Game.Screens.Select.Carousel { i.Filter(lastCriteria); - int index = InternalChildren.BinarySearch(i, criteriaComparer); + int index = items.BinarySearch(i, criteriaComparer); if (index < 0) index = ~index; // BinarySearch hacks multiple return values with 2's complement. - InternalChildren.Insert(index, i); + items.Insert(index, i); } else { // criteria may be null for initial population. the filtering will be applied post-add. - InternalChildren.Add(i); + items.Add(i); } } public CarouselGroup(List? items = null) { - if (items != null) InternalChildren = items; + if (items != null) this.items = items; State.ValueChanged += state => { @@ -67,11 +69,11 @@ namespace osu.Game.Screens.Select.Carousel { case CarouselItemState.Collapsed: case CarouselItemState.NotSelected: - InternalChildren.ForEach(c => c.State.Value = CarouselItemState.Collapsed); + this.items.ForEach(c => c.State.Value = CarouselItemState.Collapsed); break; case CarouselItemState.Selected: - InternalChildren.ForEach(c => + this.items.ForEach(c => { if (c.State.Value == CarouselItemState.Collapsed) c.State.Value = CarouselItemState.NotSelected; }); @@ -84,11 +86,11 @@ namespace osu.Game.Screens.Select.Carousel { base.Filter(criteria); - InternalChildren.ForEach(c => c.Filter(criteria)); + items.ForEach(c => c.Filter(criteria)); // IEnumerable.OrderBy() is used instead of List.Sort() to ensure sorting stability criteriaComparer = Comparer.Create((x, y) => x.CompareTo(criteria, y)); - InternalChildren = InternalChildren.OrderBy(c => c, criteriaComparer).ToList(); + items = items.OrderBy(c => c, criteriaComparer).ToList(); lastCriteria = criteria; } @@ -98,7 +100,7 @@ namespace osu.Game.Screens.Select.Carousel // ensure we are the only item selected if (value == CarouselItemState.Selected) { - foreach (var b in InternalChildren) + foreach (var b in items) { if (item == b) continue; diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 4805c7e2a4..26d64a2619 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -49,9 +49,9 @@ namespace osu.Game.Screens.Select.Carousel attemptSelection(); } - public override void RemoveChild(CarouselItem i) + public override void RemoveItem(CarouselItem i) { - base.RemoveChild(i); + base.RemoveItem(i); if (i != LastSelected) updateSelectedIndex(); @@ -64,16 +64,16 @@ namespace osu.Game.Screens.Select.Carousel addingChildren = true; foreach (var i in items) - AddChild(i); + AddItem(i); addingChildren = false; attemptSelection(); } - public override void AddChild(CarouselItem i) + public override void AddItem(CarouselItem i) { - base.AddChild(i); + base.AddItem(i); if (!addingChildren) attemptSelection(); } @@ -103,15 +103,15 @@ namespace osu.Game.Screens.Select.Carousel if (State.Value != CarouselItemState.Selected) return; // we only perform eager selection if none of our children are in a selected state already. - if (Children.Any(i => i.State.Value == CarouselItemState.Selected)) return; + if (Items.Any(i => i.State.Value == CarouselItemState.Selected)) return; PerformSelection(); } protected virtual CarouselItem GetNextToSelect() { - return Children.Skip(lastSelectedIndex).FirstOrDefault(i => !i.Filtered.Value) ?? - Children.Reverse().Skip(InternalChildren.Count - lastSelectedIndex).FirstOrDefault(i => !i.Filtered.Value); + return Items.Skip(lastSelectedIndex).FirstOrDefault(i => !i.Filtered.Value) ?? + Items.Reverse().Skip(Items.Count - lastSelectedIndex).FirstOrDefault(i => !i.Filtered.Value); } protected virtual void PerformSelection() @@ -131,6 +131,6 @@ namespace osu.Game.Screens.Select.Carousel updateSelectedIndex(); } - private void updateSelectedIndex() => lastSelectedIndex = LastSelected == null ? 0 : Math.Max(0, InternalChildren.IndexOf(LastSelected)); + private void updateSelectedIndex() => lastSelectedIndex = LastSelected == null ? 0 : Math.Max(0, GetIndexOfItem(LastSelected)); } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index fc29f509ad..8c266c8dff 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Select.Carousel { var carouselBeatmapSet = (CarouselBeatmapSet)Item; - var visibleBeatmaps = carouselBeatmapSet.Children.Where(c => c.Visible).ToArray(); + var visibleBeatmaps = carouselBeatmapSet.Items.Where(c => c.Visible).ToArray(); // if we are already displaying all the correct beatmaps, only run animation updates. // note that the displayed beatmaps may change due to the applied filter. diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index c92a0ac4ac..133bf5f9c3 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Select.Carousel if (item is CarouselGroup group) { - foreach (var c in group.Children) + foreach (var c in group.Items) c.Filtered.ValueChanged -= onStateChange; } } @@ -117,7 +117,7 @@ namespace osu.Game.Screens.Select.Carousel if (Item is CarouselGroup group) { - foreach (var c in group.Children) + foreach (var c in group.Items) c.Filtered.ValueChanged += onStateChange; } } From 3cfe624af1c8a5b4ec1c953640034801df1ac100 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 16:16:39 +0900 Subject: [PATCH 737/803] Fix one more missed method with incorrect terminology --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Select/Carousel/CarouselGroupEagerSelect.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 8b8a85b243..04d77bfb0e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -121,7 +121,7 @@ namespace osu.Game.Screens.Select { CarouselRoot newRoot = new CarouselRoot(this); - newRoot.AddChildren(beatmapSets.Select(s => createCarouselSet(s.Detach())).Where(g => g != null)); + newRoot.AddItems(beatmapSets.Select(s => createCarouselSet(s.Detach())).Where(g => g != null)); root = newRoot; diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 26d64a2619..613b3db5d5 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -57,16 +57,16 @@ namespace osu.Game.Screens.Select.Carousel updateSelectedIndex(); } - private bool addingChildren; + private bool addingItems; - public void AddChildren(IEnumerable items) + public void AddItems(IEnumerable items) { - addingChildren = true; + addingItems = true; foreach (var i in items) AddItem(i); - addingChildren = false; + addingItems = false; attemptSelection(); } @@ -74,7 +74,7 @@ namespace osu.Game.Screens.Select.Carousel public override void AddItem(CarouselItem i) { base.AddItem(i); - if (!addingChildren) + if (!addingItems) attemptSelection(); } From fc0c9f76bd8ef2a9c5250d1977782ddb9916b678 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 16:24:46 +0900 Subject: [PATCH 738/803] Fix `UpdateBeatmapSetButton` intermittent test failure Carousel would only expire items when off-screen. This meant that for a case (like a test) where items are generally always on-screen, `UpdateBeatmapSet` calls would result in panels remaining hidden but not cleaned up. --- .../TestSceneUpdateBeatmapSetButton.cs | 2 ++ osu.Game/Screens/Select/BeatmapCarousel.cs | 27 ++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs index a95f145897..70786c93e7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs @@ -71,6 +71,7 @@ namespace osu.Game.Tests.Visual.SongSelect carousel.UpdateBeatmapSet(testBeatmapSetInfo); }); + AddUntilStep("only one set visible", () => carousel.ChildrenOfType().Count() == 1); AddUntilStep("update button visible", () => getUpdateButton() != null); AddStep("click button", () => getUpdateButton()?.TriggerClick()); @@ -120,6 +121,7 @@ namespace osu.Game.Tests.Visual.SongSelect carousel.UpdateBeatmapSet(testBeatmapSetInfo); }); + AddUntilStep("only one set visible", () => carousel.ChildrenOfType().Count() == 1); AddUntilStep("update button visible", () => getUpdateButton() != null); AddStep("click button", () => getUpdateButton()?.TriggerClick()); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 04d77bfb0e..75caa3c9a3 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -316,8 +316,13 @@ namespace osu.Game.Screens.Select previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID; var newSet = createCarouselSet(beatmapSet); + var removedSet = root.RemoveChild(beatmapSet.ID); - root.RemoveChild(beatmapSet.ID); + // If we don't remove this here, it may remain in a hidden state until scrolled off screen. + // Doesn't really affect anything during actual user interaction, but makes testing annoying. + var removedDrawable = Scroll.FirstOrDefault(c => c.Item == removedSet); + if (removedDrawable != null) + expirePanelImmediately(removedDrawable); if (newSet != null) { @@ -696,11 +701,7 @@ namespace osu.Game.Screens.Select // panel loaded as drawable but not required by visible range. // remove but only if too far off-screen if (panel.Y + panel.DrawHeight < visibleUpperBound - distance_offscreen_before_unload || panel.Y > visibleBottomBound + distance_offscreen_before_unload) - { - // may want a fade effect here (could be seen if a huge change happens, like a set with 20 difficulties becomes selected). - panel.ClearTransforms(); - panel.Expire(); - } + expirePanelImmediately(panel); } // Add those items within the previously found index range that should be displayed. @@ -730,6 +731,13 @@ namespace osu.Game.Screens.Select } } + private static void expirePanelImmediately(DrawableCarouselItem panel) + { + // may want a fade effect here (could be seen if a huge change happens, like a set with 20 difficulties becomes selected). + panel.ClearTransforms(); + panel.Expire(); + } + private readonly CarouselBoundsItem carouselBoundsItem = new CarouselBoundsItem(); private (int firstIndex, int lastIndex) getDisplayRange() @@ -972,10 +980,15 @@ namespace osu.Game.Screens.Select base.AddItem(i); } - public void RemoveChild(Guid beatmapSetID) + public CarouselBeatmapSet RemoveChild(Guid beatmapSetID) { if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSet)) + { RemoveItem(carouselBeatmapSet); + return carouselBeatmapSet; + } + + return null; } public override void RemoveItem(CarouselItem i) From aca19a005ef1db984b315d5aa529f207b0ddac7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 02:05:18 +0900 Subject: [PATCH 739/803] Add versioning to difficulty calculators --- .../Difficulty/CatchDifficultyCalculator.cs | 2 ++ .../Difficulty/ManiaDifficultyCalculator.cs | 2 ++ osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 ++ .../Difficulty/TaikoDifficultyCalculator.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 5 +++++ 5 files changed, 13 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index b64d860417..f37479f84a 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty private float halfCatcherWidth; + public override int Version => 20220701; + public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) { diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 178094476f..5d30d33190 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty private readonly bool isForCurrentRuleset; private readonly double originalOverallDifficulty; + public override int Version => 20220701; + public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) { diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 75d9469da3..9f4a405113 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty private const double difficulty_multiplier = 0.0675; private double hitWindowGreat; + public override int Version => 20220701; + public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 228856cbe9..9267d1ee3c 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private const double colour_skill_multiplier = 0.01; private const double stamina_skill_multiplier = 0.021; + public override int Version => 20220701; + public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) { diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 9c58a53b97..8dd1b51cae 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -34,6 +34,11 @@ namespace osu.Game.Rulesets.Difficulty private readonly IRulesetInfo ruleset; private readonly IWorkingBeatmap beatmap; + /// + /// A yymmdd version which is used to discern when reprocessing is required. + /// + public virtual int Version => 0; + protected DifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) { this.ruleset = ruleset; From 68f28ff660183a869fc2803b98beaec13dfdce1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 02:27:29 +0900 Subject: [PATCH 740/803] Add last applied version to `RulesetInfo` --- osu.Game/Database/RealmAccess.cs | 3 ++- osu.Game/Rulesets/RulesetInfo.cs | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index dff2bdddbd..7aac94576f 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -63,8 +63,9 @@ namespace osu.Game.Database /// 17 2022-07-16 Added CountryCode to RealmUser. /// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo. /// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo. + /// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo. /// - private const int schema_version = 19; + private const int schema_version = 20; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index b5e5fa1561..91954320a4 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -4,6 +4,7 @@ using System; using JetBrains.Annotations; using osu.Framework.Testing; +using osu.Game.Rulesets.Difficulty; using Realms; namespace osu.Game.Rulesets @@ -22,6 +23,11 @@ namespace osu.Game.Rulesets public string InstantiationInfo { get; set; } = string.Empty; + /// + /// Stores the last applied + /// + public int LastAppliedDifficultyVersion { get; set; } + public RulesetInfo(string shortName, string name, string instantiationInfo, int onlineID) { ShortName = shortName; @@ -86,7 +92,8 @@ namespace osu.Game.Rulesets Name = Name, ShortName = ShortName, InstantiationInfo = InstantiationInfo, - Available = Available + Available = Available, + LastAppliedDifficultyVersion = LastAppliedDifficultyVersion, }; public Ruleset CreateInstance() From 57a41c689756f8372ea06fb9eb040bf27940ca37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 03:18:57 +0900 Subject: [PATCH 741/803] Add basic background processor --- osu.Game/BackgroundBeatmapProcessor.cs | 112 +++++++++++++++++++++++++ osu.Game/OsuGame.cs | 2 + osu.Game/OsuGameBase.cs | 3 +- 3 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 osu.Game/BackgroundBeatmapProcessor.cs diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs new file mode 100644 index 0000000000..09db107744 --- /dev/null +++ b/osu.Game/BackgroundBeatmapProcessor.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; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Logging; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets; + +namespace osu.Game +{ + public class BackgroundBeatmapProcessor : Component + { + [Resolved] + private RulesetStore rulesetStore { get; set; } = null!; + + [Resolved] + private RealmAccess realmAccess { get; set; } = null!; + + [Resolved] + private BeatmapUpdater beatmapUpdater { get; set; } = null!; + + [Resolved] + private IBindable gameBeatmap { get; set; } = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Task.Run(() => + { + Logger.Log("Beginning background beatmap processing.."); + checkForOutdatedStarRatings(); + processBeatmapSetsWithMissingMetrics(); + Logger.Log("Finished background beatmap processing!"); + }); + } + + /// + /// Check whether the databased difficulty calculation version matches the latest ruleset provided version. + /// If it doesn't, clear out any existing difficulties so they can be incrementally recalculated. + /// + private void checkForOutdatedStarRatings() + { + foreach (var ruleset in rulesetStore.AvailableRulesets) + { + // beatmap being passed in is arbitrary here. just needs to be non-null. + int currentVersion = ruleset.CreateInstance().CreateDifficultyCalculator(gameBeatmap.Value).Version; + + if (ruleset.LastAppliedDifficultyVersion < currentVersion) + { + Logger.Log($"Resetting star ratings for {ruleset.Name} (difficulty calculation version updated from {ruleset.LastAppliedDifficultyVersion} to {currentVersion})"); + + int countReset = 0; + + realmAccess.Write(r => + { + foreach (var b in r.All()) + { + if (b.Ruleset.ShortName == ruleset.ShortName) + { + b.StarRating = 0; + countReset++; + } + } + + r.Find(ruleset.ShortName).LastAppliedDifficultyVersion = currentVersion; + }); + + Logger.Log($"Finished resetting {countReset} beatmap sets for {ruleset.Name}"); + } + } + } + + private void processBeatmapSetsWithMissingMetrics() + { + // TODO: rate limit and pause processing during gameplay. + + HashSet beatmapSetIds = new HashSet(); + + realmAccess.Run(r => + { + foreach (var b in r.All().Where(b => b.StarRating == 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) + { + Debug.Assert(b.BeatmapSet != null); + beatmapSetIds.Add(b.BeatmapSet.ID); + } + }); + + foreach (var id in beatmapSetIds) + { + realmAccess.Run(r => + { + var set = r.Find(id); + + if (set != null) + { + Logger.Log($"Background processing {set}"); + beatmapUpdater.Process(set); + } + }); + } + } + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 69d02b95ff..4f08511783 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -904,6 +904,8 @@ namespace osu.Game loadComponentSingleFile(CreateHighPerformanceSession(), Add); + loadComponentSingleFile(new BackgroundBeatmapProcessor(), Add); + chatOverlay.State.BindValueChanged(_ => updateChatPollRate()); // Multiplayer modes need to increase poll rate temporarily. API.Activity.BindValueChanged(_ => updateChatPollRate(), true); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 6a5c6835ba..3b81b5c8cd 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -280,8 +280,7 @@ namespace osu.Game AddInternal(difficultyCache); // TODO: OsuGame or OsuGameBase? - beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage); - + dependencies.CacheAs(beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage)); dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints)); dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); From 04f48d88627bf2cfe44f26871968a50cb204f8a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 03:55:05 +0900 Subject: [PATCH 742/803] Add better log output and sleeping during gameplay sections --- osu.Game/BackgroundBeatmapProcessor.cs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 09db107744..8cc725a3f8 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -13,6 +14,7 @@ using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets; +using osu.Game.Screens.Play; namespace osu.Game { @@ -30,6 +32,9 @@ namespace osu.Game [Resolved] private IBindable gameBeatmap { get; set; } = null!; + [Resolved] + private ILocalUserPlayInfo? localUserPlayInfo { get; set; } + protected override void LoadComplete() { base.LoadComplete(); @@ -81,10 +86,10 @@ namespace osu.Game private void processBeatmapSetsWithMissingMetrics() { - // TODO: rate limit and pause processing during gameplay. - HashSet beatmapSetIds = new HashSet(); + Logger.Log("Querying for beatmap sets to reprocess..."); + realmAccess.Run(r => { foreach (var b in r.All().Where(b => b.StarRating == 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) @@ -94,15 +99,25 @@ namespace osu.Game } }); + Logger.Log($"Found {beatmapSetIds.Count} beatmap sets which require reprocessing."); + + int i = 0; + foreach (var id in beatmapSetIds) { + while (localUserPlayInfo?.IsPlaying.Value == true) + { + Logger.Log("Background processing sleeping 30s due to active gameplay..."); + Thread.Sleep(30000); + } + realmAccess.Run(r => { var set = r.Find(id); if (set != null) { - Logger.Log($"Background processing {set}"); + Logger.Log($"Background processing {set} ({++i} / {beatmapSetIds.Count})"); beatmapUpdater.Process(set); } }); From d5e0dba9da649ebfbc19bac7e19e5b5871081231 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 17:39:07 +0900 Subject: [PATCH 743/803] Change default value of `StarRating` to `-1` --- osu.Game/BackgroundBeatmapProcessor.cs | 4 ++-- osu.Game/Beatmaps/BeatmapInfo.cs | 6 +++++- osu.Game/Database/RealmAccess.cs | 12 +++++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 8cc725a3f8..bd3fab9135 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -71,7 +71,7 @@ namespace osu.Game { if (b.Ruleset.ShortName == ruleset.ShortName) { - b.StarRating = 0; + b.StarRating = -1; countReset++; } } @@ -92,7 +92,7 @@ namespace osu.Game realmAccess.Run(r => { - foreach (var b in r.All().Where(b => b.StarRating == 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) + foreach (var b in r.All().Where(b => b.StarRating < 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) { Debug.Assert(b.BeatmapSet != null); beatmapSetIds.Add(b.BeatmapSet.ID); diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3ee306cc9a..466448d771 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -87,7 +87,11 @@ namespace osu.Game.Beatmaps public string Hash { get; set; } = string.Empty; - public double StarRating { get; set; } + /// + /// Defaults to -1 (meaning not-yet-calculated). + /// Will likely be superseded with a better storage considering ruleset/mods. + /// + public double StarRating { get; set; } = -1; [Indexed] public string MD5Hash { get; set; } = string.Empty; diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 7aac94576f..2754696130 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -63,7 +63,7 @@ namespace osu.Game.Database /// 17 2022-07-16 Added CountryCode to RealmUser. /// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo. /// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo. - /// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo. + /// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo, changed default value of BeatmapInfo.StarRating to -1. /// private const int schema_version = 20; @@ -781,6 +781,16 @@ namespace osu.Game.Database case 14: foreach (var beatmap in migration.NewRealm.All()) beatmap.UserSettings = new BeatmapUserSettings(); + + break; + + case 20: + foreach (var beatmap in migration.NewRealm.All()) + { + if (beatmap.StarRating == 0) + beatmap.StarRating = -1; + } + break; } } From 1374738a0da5fbc024862819c5b09172365c88fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 18:15:21 +0900 Subject: [PATCH 744/803] Add test coverage --- .../BackgroundBeatmapProcessorTests.cs | 132 ++++++++++++++++++ osu.Game/BackgroundBeatmapProcessor.cs | 6 +- 2 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs diff --git a/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs b/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs new file mode 100644 index 0000000000..4012e3f851 --- /dev/null +++ b/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs @@ -0,0 +1,132 @@ +// 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.Extensions; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Database +{ + [HeadlessTest] + public class BackgroundBeatmapProcessorTests : OsuTestScene, ILocalUserPlayInfo + { + public IBindable IsPlaying => isPlaying; + + private readonly Bindable isPlaying = new Bindable(); + + private BeatmapSetInfo importedSet = null!; + + [BackgroundDependencyLoader] + private void load(OsuGameBase osu) + { + importedSet = BeatmapImportHelper.LoadQuickOszIntoOsu(osu).GetResultSafely(); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Set not playing", () => isPlaying.Value = false); + } + + [Test] + public void TestDifficultyProcessing() + { + AddAssert("Difficulty is initially set", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); + }); + }); + + AddStep("Reset difficulty", () => + { + Realm.Write(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + foreach (var b in beatmapSetInfo.Beatmaps) + b.StarRating = -1; + }); + }); + + AddStep("Run background processor", () => + { + Add(new TestBackgroundBeatmapProcessor()); + }); + + AddUntilStep("wait for difficulties repopulated", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); + }); + }); + } + + [Test] + public void TestDifficultyProcessingWhilePlaying() + { + AddAssert("Difficulty is initially set", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); + }); + }); + + AddStep("Set playing", () => isPlaying.Value = true); + + AddStep("Reset difficulty", () => + { + Realm.Write(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + foreach (var b in beatmapSetInfo.Beatmaps) + b.StarRating = -1; + }); + }); + + AddStep("Run background processor", () => + { + Add(new TestBackgroundBeatmapProcessor()); + }); + + AddWaitStep("wait some", 500); + + AddAssert("Difficulty still not populated", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating == -1); + }); + }); + + AddStep("Set not playing", () => isPlaying.Value = false); + + AddUntilStep("wait for difficulties repopulated", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); + }); + }); + } + + public class TestBackgroundBeatmapProcessor : BackgroundBeatmapProcessor + { + protected override int TimeToSleepDuringGameplay => 10; + } + } +} diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index bd3fab9135..9b04f487a8 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -35,6 +35,8 @@ namespace osu.Game [Resolved] private ILocalUserPlayInfo? localUserPlayInfo { get; set; } + protected virtual int TimeToSleepDuringGameplay => 30000; + protected override void LoadComplete() { base.LoadComplete(); @@ -107,8 +109,8 @@ namespace osu.Game { while (localUserPlayInfo?.IsPlaying.Value == true) { - Logger.Log("Background processing sleeping 30s due to active gameplay..."); - Thread.Sleep(30000); + Logger.Log("Background processing sleeping due to active gameplay..."); + Thread.Sleep(TimeToSleepDuringGameplay); } realmAccess.Run(r => From 94cd641fb4ca493a8e30972134defcf220efce8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 18:27:31 +0900 Subject: [PATCH 745/803] Change migration to trigger reprocessing on every local beatmap Was originally relying on the fact that this would be triggered due to a null `LastOnlineUpdate`, but wouldn't cover the case of beatmaps with no `OnlineID`. --- osu.Game/Database/RealmAccess.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 2754696130..826341a5b9 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -785,11 +785,10 @@ namespace osu.Game.Database break; case 20: + // As we now have versioned difficulty calculations, let's reset + // all star ratings and have `BackgroundBeatmapProcessor` recalculate them. foreach (var beatmap in migration.NewRealm.All()) - { - if (beatmap.StarRating == 0) - beatmap.StarRating = -1; - } + beatmap.StarRating = -1; break; } From fb728fbed175f3beb717c31cfc76046292e20212 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 18:56:39 +0900 Subject: [PATCH 746/803] Fix FPS counter not being wide enough to show large fps numbers --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 9d5aa525d7..db86199667 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -29,6 +29,8 @@ namespace osu.Game.Graphics.UserInterface private Container background = null!; + private Container counters = null!; + private const float idle_background_alpha = 0.4f; private readonly BindableBool showFpsDisplay = new BindableBool(true); @@ -49,7 +51,7 @@ namespace osu.Game.Graphics.UserInterface mainContent = new Container { Alpha = 0, - Size = new Vector2(42, 26), + Height = 26, Children = new Drawable[] { background = new Container @@ -68,21 +70,30 @@ namespace osu.Game.Graphics.UserInterface }, } }, - counterUpdateFrameTime = new FrameTimeCounter + counters = new Container { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Margin = new MarginPadding(1), - Y = -2, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + counterUpdateFrameTime = new FrameTimeCounter + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Margin = new MarginPadding(1), + Y = -2, + }, + counterDrawFPS = new FramesPerSecondCounter + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Margin = new MarginPadding(2), + Y = 10, + Scale = new Vector2(0.8f), + } + } }, - counterDrawFPS = new FramesPerSecondCounter - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Margin = new MarginPadding(2), - Y = 10, - Scale = new Vector2(0.8f), - } } }, }; @@ -159,6 +170,8 @@ namespace osu.Game.Graphics.UserInterface { base.Update(); + mainContent.Width = Math.Max(mainContent.Width, counters.DrawWidth); + // Handle the case where the window has become inactive or the user changed the // frame limiter (we want to show the FPS as it's changing, even if it isn't an outlier). bool aimRatesChanged = updateAimFPS(); From 4c4939d18d3a751246ee12f5945248dcf2486960 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 19:11:16 +0900 Subject: [PATCH 747/803] Fix draw FPS being inaccurate due to using `ElapsedFrameTime` Had a feeling this would be the case. Basically, we're calculating on the update thread and checking the last value of draw thread's `ElapsedFrameTime`. In the case that value is spiky, a completely incorrect fps can be displayed. I've left the spike display do use `ElapsedFrameTime`, as `FramesPerSecond` is too averaged to see spikes. --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 9d5aa525d7..2edfebe203 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -165,13 +164,14 @@ namespace osu.Game.Graphics.UserInterface // TODO: this is wrong (elapsed clock time, not actual run time). double newUpdateFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; - // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. - double newDrawFps = 1000 / Math.Max(0.001, gameHost.DrawThread.Clock.ElapsedFrameTime); + double newDrawFrameTime = gameHost.DrawThread.Clock.ElapsedFrameTime; + double newDrawFps = gameHost.DrawThread.Clock.FramesPerSecond; const double spike_time_ms = 20; bool hasUpdateSpike = counterUpdateFrameTime.Current.Value < spike_time_ms && newUpdateFrameTime > spike_time_ms; - bool hasDrawSpike = counterDrawFPS.Current.Value > (1000 / spike_time_ms) && newDrawFps <= (1000 / spike_time_ms); + // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. + bool hasDrawSpike = counterDrawFPS.Current.Value > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; // If the frame time spikes up, make sure it shows immediately on the counter. if (hasUpdateSpike) @@ -180,7 +180,8 @@ namespace osu.Game.Graphics.UserInterface counterUpdateFrameTime.Current.Value = newUpdateFrameTime; if (hasDrawSpike) - counterDrawFPS.SetCountWithoutRolling(newDrawFps); + // show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show. + counterDrawFPS.SetCountWithoutRolling(1000 / newDrawFrameTime); else counterDrawFPS.Current.Value = newDrawFps; From 5db4d9437aee56366b248a8869fc1850d3ee0079 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 21:39:24 +0900 Subject: [PATCH 748/803] Add missing using statement --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 162d2d2a57..91e0429c85 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; From a4f071fe53e2d1d3e105ffdf0d20c4f65eec5db0 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Thu, 21 Jul 2022 08:26:48 -0500 Subject: [PATCH 749/803] Make zoom sensitivity relative to containers max zoom --- .../Screens/Edit/Compose/Components/Timeline/TimelineArea.cs | 4 +++- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 97dc04b9fa..c25a4834d3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -20,6 +20,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Drawable userContent; + private const float zoom_button_sensitivity = 0.02f; + public TimelineArea(Drawable content = null) { RelativeSizeAxes = Axes.X; @@ -154,6 +156,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Timeline.TicksVisible.BindTo(ticksCheckbox.Current); } - private void changeZoom(float change) => Timeline.Zoom += change; + private void changeZoom(float change) => Timeline.Zoom += change * Timeline.MaxZoom * zoom_button_sensitivity; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 80cdef38e9..7a4d45301f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -104,6 +104,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline set => updateZoom(value); } + private const float zoom_scroll_sensitivity = 0.02f; + private void updateZoom(float? value = null) { float newZoom = Math.Clamp(value ?? Zoom, MinZoom, MaxZoom); @@ -127,7 +129,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (e.AltPressed) { // zoom when holding alt. - setZoomTarget(zoomTarget + e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); + setZoomTarget(zoomTarget + e.ScrollDelta.Y * MaxZoom * zoom_scroll_sensitivity, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); return true; } From 3fad481a96799fecc55bb1819a4a28ed2f12972a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 22:27:22 +0900 Subject: [PATCH 750/803] Avoid using `RollingCounter` in fps counter It wasn't made to be updated every frame, and it shows. Inaccurate for reasons I'm not really interested in investigating, because I don't want to incur the `Transorm` overhead in the first place for an fps counter. Was only used originally out of convenience. --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 57 ++++++++++++------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 91e0429c85..6982f94abc 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -22,8 +22,8 @@ namespace osu.Game.Graphics.UserInterface { public class FPSCounter : VisibilityContainer, IHasCustomTooltip { - private RollingCounter counterUpdateFrameTime = null!; - private RollingCounter counterDrawFPS = null!; + private OsuSpriteText counterUpdateFrameTime = null!; + private OsuSpriteText counterDrawFPS = null!; private Container mainContent = null!; @@ -35,6 +35,9 @@ namespace osu.Game.Graphics.UserInterface private readonly BindableBool showFpsDisplay = new BindableBool(true); + private double displayedFpsCount; + private double displayedFrameTime; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -77,20 +80,23 @@ namespace osu.Game.Graphics.UserInterface AutoSizeAxes = Axes.Both, Children = new Drawable[] { - counterUpdateFrameTime = new FrameTimeCounter + counterUpdateFrameTime = new OsuSpriteText { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Margin = new MarginPadding(1), + Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), + Spacing = new Vector2(-1), Y = -2, }, - counterDrawFPS = new FramesPerSecondCounter + counterDrawFPS = new OsuSpriteText { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Margin = new MarginPadding(2), + Font = OsuFont.Default.With(fixedWidth: true, size: 13, weight: FontWeight.SemiBold), + Spacing = new Vector2(-2), Y = 10, - Scale = new Vector2(0.8f), } } }, @@ -183,37 +189,48 @@ namespace osu.Game.Graphics.UserInterface const double spike_time_ms = 20; - bool hasUpdateSpike = counterUpdateFrameTime.Current.Value < spike_time_ms && newUpdateFrameTime > spike_time_ms; + bool hasUpdateSpike = displayedFrameTime < spike_time_ms && newUpdateFrameTime > spike_time_ms; // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. - bool hasDrawSpike = counterDrawFPS.Current.Value > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; + bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; - // If the frame time spikes up, make sure it shows immediately on the counter. - if (hasUpdateSpike) - counterUpdateFrameTime.SetCountWithoutRolling(newUpdateFrameTime); - else - counterUpdateFrameTime.Current.Value = newUpdateFrameTime; + // note that we use an elapsed time here of 1 intentionally. + // this weights all updates equally. if we passed in the elapsed time, longer frames would be weighted incorrectly lower. + displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, newUpdateFrameTime, hasUpdateSpike ? 0 : 200, 1); if (hasDrawSpike) // show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show. - counterDrawFPS.SetCountWithoutRolling(1000 / newDrawFrameTime); + displayedFpsCount = 1000 / newDrawFrameTime; else - counterDrawFPS.Current.Value = newDrawFps; + displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 200, Time.Elapsed); - counterDrawFPS.Colour = getColour(counterDrawFPS.DisplayedCount / aimDrawFPS); - - double displayedUpdateFPS = 1000 / counterUpdateFrameTime.DisplayedCount; - counterUpdateFrameTime.Colour = getColour(displayedUpdateFPS / aimUpdateFPS); + updateFpsDisplay(); + updateFrameTimeDisplay(); bool hasSignificantChanges = aimRatesChanged || hasDrawSpike || hasUpdateSpike - || counterDrawFPS.DisplayedCount < aimDrawFPS * 0.8 - || displayedUpdateFPS < aimUpdateFPS * 0.8; + || displayedFpsCount < aimDrawFPS * 0.8 + || 1000 / displayedFrameTime < aimUpdateFPS * 0.8; if (hasSignificantChanges) displayTemporarily(); } + private void updateFpsDisplay() + { + counterDrawFPS.Colour = getColour(displayedFpsCount / aimDrawFPS); + counterDrawFPS.Text = $"{displayedFpsCount:#,0}fps"; + } + + private void updateFrameTimeDisplay() + { + counterUpdateFrameTime.Text = displayedFrameTime < 5 + ? $"{displayedFrameTime:N1}ms" + : $"{displayedFrameTime:N0}ms"; + + counterUpdateFrameTime.Colour = getColour((1000 / displayedFrameTime) / aimUpdateFPS); + } + private bool updateAimFPS() { if (gameHost.UpdateThread.Clock.Throttling) From 3d2603e0eb149451919950b4d3c350642070f829 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 22:51:35 +0900 Subject: [PATCH 751/803] Remove unused classes --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 46 ------------------- 1 file changed, 46 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 6982f94abc..d9d8a03b29 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Framework.Utils; @@ -270,50 +269,5 @@ namespace osu.Game.Graphics.UserInterface public ITooltip GetCustomTooltip() => new FPSCounterTooltip(); public object TooltipContent => this; - - public class FramesPerSecondCounter : RollingCounter - { - protected override double RollingDuration => 1000; - - protected override OsuSpriteText CreateSpriteText() - { - return new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), - Spacing = new Vector2(-2), - }; - } - - protected override LocalisableString FormatCount(double count) - { - return $"{count:#,0}fps"; - } - } - - public class FrameTimeCounter : RollingCounter - { - protected override double RollingDuration => 1000; - - protected override OsuSpriteText CreateSpriteText() - { - return new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), - Spacing = new Vector2(-1), - }; - } - - protected override LocalisableString FormatCount(double count) - { - if (count < 1) - return $"{count:N1}ms"; - - return $"{count:N0}ms"; - } - } } } From fc6445caeab481a09e5aa1793c64ba9b9b19fbf7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 22:55:45 +0900 Subject: [PATCH 752/803] Rate limit updates for good measure --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index d9d8a03b29..825bde9d2d 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -30,6 +30,10 @@ namespace osu.Game.Graphics.UserInterface private Container counters = null!; + private const double min_time_between_updates = 10; + + private const double spike_time_ms = 20; + private const float idle_background_alpha = 0.4f; private readonly BindableBool showFpsDisplay = new BindableBool(true); @@ -147,6 +151,8 @@ namespace osu.Game.Graphics.UserInterface private double aimDrawFPS; private double aimUpdateFPS; + private double lastUpdate; + private void displayTemporarily() { if (!isDisplayed) @@ -186,8 +192,6 @@ namespace osu.Game.Graphics.UserInterface double newDrawFrameTime = gameHost.DrawThread.Clock.ElapsedFrameTime; double newDrawFps = gameHost.DrawThread.Clock.FramesPerSecond; - const double spike_time_ms = 20; - bool hasUpdateSpike = displayedFrameTime < spike_time_ms && newUpdateFrameTime > spike_time_ms; // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; @@ -202,8 +206,13 @@ namespace osu.Game.Graphics.UserInterface else displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 200, Time.Elapsed); - updateFpsDisplay(); - updateFrameTimeDisplay(); + if (Time.Current - lastUpdate > min_time_between_updates) + { + updateFpsDisplay(); + updateFrameTimeDisplay(); + + lastUpdate = Time.Current; + } bool hasSignificantChanges = aimRatesChanged || hasDrawSpike From c140601c2de65aeb6ba5bcd58a68246ad08e3dfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 23:02:31 +0900 Subject: [PATCH 753/803] Cleanup pass on `FPSCounter` --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 825bde9d2d..0db54c3947 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Framework.Threading; +using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics.Sprites; @@ -41,6 +42,18 @@ namespace osu.Game.Graphics.UserInterface private double displayedFpsCount; private double displayedFrameTime; + private bool isDisplayed; + + private ScheduledDelegate? fadeOutDelegate; + + private double aimDrawFPS; + private double aimUpdateFPS; + + private double lastUpdate; + private ThrottledFrameClock drawClock = null!; + private ThrottledFrameClock updateClock = null!; + private ThrottledFrameClock inputClock = null!; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -50,7 +63,7 @@ namespace osu.Game.Graphics.UserInterface } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, GameHost gameHost) { InternalChildren = new Drawable[] { @@ -108,6 +121,10 @@ namespace osu.Game.Graphics.UserInterface }; config.BindWith(OsuSetting.ShowFpsDisplay, showFpsDisplay); + + drawClock = gameHost.DrawThread.Clock; + updateClock = gameHost.UpdateThread.Clock; + inputClock = gameHost.InputThread.Clock; } protected override void LoadComplete() @@ -144,15 +161,6 @@ namespace osu.Game.Graphics.UserInterface base.OnHoverLost(e); } - private bool isDisplayed; - - private ScheduledDelegate? fadeOutDelegate; - - private double aimDrawFPS; - private double aimUpdateFPS; - - private double lastUpdate; - private void displayTemporarily() { if (!isDisplayed) @@ -174,9 +182,6 @@ namespace osu.Game.Graphics.UserInterface } } - [Resolved] - private GameHost gameHost { get; set; } = null!; - protected override void Update() { base.Update(); @@ -187,24 +192,19 @@ namespace osu.Game.Graphics.UserInterface // frame limiter (we want to show the FPS as it's changing, even if it isn't an outlier). bool aimRatesChanged = updateAimFPS(); - // TODO: this is wrong (elapsed clock time, not actual run time). - double newUpdateFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; - double newDrawFrameTime = gameHost.DrawThread.Clock.ElapsedFrameTime; - double newDrawFps = gameHost.DrawThread.Clock.FramesPerSecond; - - bool hasUpdateSpike = displayedFrameTime < spike_time_ms && newUpdateFrameTime > spike_time_ms; + bool hasUpdateSpike = displayedFrameTime < spike_time_ms && updateClock.ElapsedFrameTime > spike_time_ms; // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. - bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; + bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && drawClock.ElapsedFrameTime > spike_time_ms; // note that we use an elapsed time here of 1 intentionally. // this weights all updates equally. if we passed in the elapsed time, longer frames would be weighted incorrectly lower. - displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, newUpdateFrameTime, hasUpdateSpike ? 0 : 200, 1); + displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, updateClock.ElapsedFrameTime, hasUpdateSpike ? 0 : 200, 1); if (hasDrawSpike) // show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show. - displayedFpsCount = 1000 / newDrawFrameTime; + displayedFpsCount = 1000 / drawClock.ElapsedFrameTime; else - displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 200, Time.Elapsed); + displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, drawClock.FramesPerSecond, 200, Time.Elapsed); if (Time.Current - lastUpdate > min_time_between_updates) { @@ -241,10 +241,10 @@ namespace osu.Game.Graphics.UserInterface private bool updateAimFPS() { - if (gameHost.UpdateThread.Clock.Throttling) + if (updateClock.Throttling) { - double newAimDrawFPS = gameHost.DrawThread.Clock.MaximumUpdateHz; - double newAimUpdateFPS = gameHost.UpdateThread.Clock.MaximumUpdateHz; + double newAimDrawFPS = drawClock.MaximumUpdateHz; + double newAimUpdateFPS = updateClock.MaximumUpdateHz; if (aimDrawFPS != newAimDrawFPS || aimUpdateFPS != newAimUpdateFPS) { @@ -255,7 +255,7 @@ namespace osu.Game.Graphics.UserInterface } else { - double newAimFPS = gameHost.InputThread.Clock.MaximumUpdateHz; + double newAimFPS = inputClock.MaximumUpdateHz; if (aimDrawFPS != newAimFPS || aimUpdateFPS != newAimFPS) { From 0eeafea50002d18f76a27ff390075ab859030893 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 23:37:32 +0900 Subject: [PATCH 754/803] Increase responsiveness to change slightly --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index d9d8a03b29..07eb9fa3ca 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -194,13 +194,13 @@ namespace osu.Game.Graphics.UserInterface // note that we use an elapsed time here of 1 intentionally. // this weights all updates equally. if we passed in the elapsed time, longer frames would be weighted incorrectly lower. - displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, newUpdateFrameTime, hasUpdateSpike ? 0 : 200, 1); + displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, newUpdateFrameTime, hasUpdateSpike ? 0 : 100, 1); if (hasDrawSpike) // show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show. displayedFpsCount = 1000 / newDrawFrameTime; else - displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 200, Time.Elapsed); + displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 100, Time.Elapsed); updateFpsDisplay(); updateFrameTimeDisplay(); From b3aa496ba783b3dc7c63303d88c940edf6dc9c99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 00:14:30 +0900 Subject: [PATCH 755/803] Add handling of realm disposed exceptions --- osu.Game/BackgroundBeatmapProcessor.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 9b04f487a8..6ecd8ca5c1 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -46,6 +46,14 @@ namespace osu.Game Logger.Log("Beginning background beatmap processing.."); checkForOutdatedStarRatings(); processBeatmapSetsWithMissingMetrics(); + }).ContinueWith(t => + { + if (t.Exception?.InnerException is ObjectDisposedException) + { + Logger.Log("Finished background aborted during shutdown"); + return; + } + Logger.Log("Finished background beatmap processing!"); }); } From d796b7d53c0bb135f667cd68dbd6633a442816c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jul 2022 21:56:23 +0200 Subject: [PATCH 756/803] Extract base mod select overlay panel presentation logic --- osu.Game/Overlays/Mods/ModPanel.cs | 237 ++--------------- .../Overlays/Mods/ModSelectOverlayPanel.cs | 250 ++++++++++++++++++ 2 files changed, 267 insertions(+), 220 deletions(-) create mode 100644 osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 02eb395bd9..5de6290248 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -1,144 +1,42 @@ // 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.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Framework.Utils; -using osu.Game.Audio; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osuTK; -using osuTK.Input; namespace osu.Game.Overlays.Mods { - public class ModPanel : OsuClickableContainer + public class ModPanel : ModSelectOverlayPanel { public Mod Mod => modState.Mod; - public BindableBool Active => modState.Active; + public override BindableBool Active => modState.Active; public BindableBool Filtered => modState.Filtered; + protected override float IdleSwitchWidth => 54; + protected override float ExpandedSwitchWidth => 70; + private readonly ModState modState; - protected readonly Box Background; - protected readonly Container SwitchContainer; - protected readonly Container MainContentContainer; - protected readonly Box TextBackground; - protected readonly FillFlowContainer TextFlow; - - [Resolved] - protected OverlayColourProvider ColourProvider { get; private set; } = null!; - - protected const double TRANSITION_DURATION = 150; - - public const float CORNER_RADIUS = 7; - - protected const float HEIGHT = 42; - protected const float IDLE_SWITCH_WIDTH = 54; - protected const float EXPANDED_SWITCH_WIDTH = 70; - - private Colour4 activeColour; - - private readonly Bindable samplePlaybackDisabled = new BindableBool(); - private Sample? sampleOff; - private Sample? sampleOn; - public ModPanel(ModState modState) { this.modState = modState; - RelativeSizeAxes = Axes.X; - Height = 42; + Title = Mod.Name; + Description = Mod.Description; - // all below properties are applied to `Content` rather than the `ModPanel` in its entirety - // to allow external components to set these properties on the panel without affecting - // its "internal" appearance. - Content.Masking = true; - Content.CornerRadius = CORNER_RADIUS; - Content.BorderThickness = 2; - Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); - - Children = new Drawable[] + SwitchContainer.Child = new ModSwitchSmall(Mod) { - Background = new Box - { - RelativeSizeAxes = Axes.Both - }, - SwitchContainer = new Container - { - RelativeSizeAxes = Axes.Y, - Child = new ModSwitchSmall(Mod) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Active = { BindTarget = Active }, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), - Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE) - } - }, - MainContentContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = CORNER_RADIUS, - Children = new Drawable[] - { - TextBackground = new Box - { - RelativeSizeAxes = Axes.Both - }, - TextFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Horizontal = 17.5f, - Vertical = 4 - }, - Direction = FillDirection.Vertical, - Children = new[] - { - new OsuSpriteText - { - Text = Mod.Name, - Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold), - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), - Margin = new MarginPadding - { - Left = -18 * ShearedOverlayContainer.SHEAR - } - }, - new OsuSpriteText - { - Text = Mod.Description, - Font = OsuFont.Default.With(size: 12), - RelativeSizeAxes = Axes.X, - Truncate = true, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0) - } - } - } - } - } - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Active = { BindTarget = Active }, + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE) }; - - Action = Active.Toggle; } public ModPanel(Mod mod) @@ -146,122 +44,21 @@ namespace osu.Game.Overlays.Mods { } - [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, OsuColour colours, ISamplePlaybackDisabler? samplePlaybackDisabler) + [BackgroundDependencyLoader] + private void load(OsuColour colours) { - sampleOn = audio.Samples.Get(@"UI/check-on"); - sampleOff = audio.Samples.Get(@"UI/check-off"); - - activeColour = colours.ForModType(Mod.Type); - - if (samplePlaybackDisabler != null) - ((IBindable)samplePlaybackDisabled).BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); + AccentColour = colours.ForModType(Mod.Type); } - protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); - protected override void LoadComplete() { base.LoadComplete(); - Active.BindValueChanged(_ => - { - playStateChangeSamples(); - UpdateState(); - }); + Filtered.BindValueChanged(_ => updateFilterState(), true); - - UpdateState(); - FinishTransforms(true); - } - - private void playStateChangeSamples() - { - if (samplePlaybackDisabled.Value) - return; - - if (Active.Value) - sampleOn?.Play(); - else - sampleOff?.Play(); - } - - protected override bool OnHover(HoverEvent e) - { - UpdateState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - UpdateState(); - base.OnHoverLost(e); - } - - private bool mouseDown; - - protected override bool OnMouseDown(MouseDownEvent e) - { - if (e.Button == MouseButton.Left) - mouseDown = true; - - UpdateState(); - return false; - } - - protected override void OnMouseUp(MouseUpEvent e) - { - mouseDown = false; - - UpdateState(); - base.OnMouseUp(e); - } - - protected virtual Colour4 BackgroundColour => Active.Value ? activeColour.Darken(0.3f) : ColourProvider.Background3; - protected virtual Colour4 ForegroundColour => Active.Value ? activeColour : ColourProvider.Background2; - protected virtual Colour4 TextColour => Active.Value ? ColourProvider.Background6 : Colour4.White; - - protected virtual void UpdateState() - { - float targetWidth = Active.Value ? EXPANDED_SWITCH_WIDTH : IDLE_SWITCH_WIDTH; - double transitionDuration = TRANSITION_DURATION; - - Colour4 backgroundColour = BackgroundColour; - Colour4 foregroundColour = ForegroundColour; - Colour4 textColour = TextColour; - - // Hover affects colour of button background - if (IsHovered) - { - backgroundColour = backgroundColour.Lighten(0.1f); - foregroundColour = foregroundColour.Lighten(0.1f); - } - - // Mouse down adds a halfway tween of the movement - if (mouseDown) - { - targetWidth = (float)Interpolation.Lerp(IDLE_SWITCH_WIDTH, EXPANDED_SWITCH_WIDTH, 0.5f); - transitionDuration *= 4; - } - - Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, foregroundColour), transitionDuration, Easing.OutQuint); - Background.FadeColour(backgroundColour, transitionDuration, Easing.OutQuint); - SwitchContainer.ResizeWidthTo(targetWidth, transitionDuration, Easing.OutQuint); - MainContentContainer.TransformTo(nameof(Padding), new MarginPadding - { - Left = targetWidth, - Right = CORNER_RADIUS - }, transitionDuration, Easing.OutQuint); - TextBackground.FadeColour(foregroundColour, transitionDuration, Easing.OutQuint); - TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint); } #region Filtering support - public void ApplyFilter(Func? filter) - { - Filtered.Value = filter != null && !filter.Invoke(Mod); - } - private void updateFilterState() { this.FadeTo(Filtered.Value ? 0 : 1); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs b/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs new file mode 100644 index 0000000000..d4969e0a81 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs @@ -0,0 +1,250 @@ +// 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.Audio; +using osu.Framework.Audio.Sample; +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.Input.Events; +using osu.Framework.Localisation; +using osu.Framework.Utils; +using osu.Game.Audio; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; + +namespace osu.Game.Overlays.Mods +{ + public abstract class ModSelectOverlayPanel : OsuClickableContainer, IHasAccentColour + { + public abstract BindableBool Active { get; } + + public Color4 AccentColour { get; set; } + + public LocalisableString Title + { + get => titleText.Text; + set => titleText.Text = value; + } + + public LocalisableString Description + { + get => descriptionText.Text; + set => descriptionText.Text = value; + } + + public const float CORNER_RADIUS = 7; + + protected const float HEIGHT = 42; + + protected virtual float IdleSwitchWidth => 14; + protected virtual float ExpandedSwitchWidth => 30; + protected virtual Colour4 BackgroundColour => Active.Value ? AccentColour.Darken(0.3f) : ColourProvider.Background3; + protected virtual Colour4 ForegroundColour => Active.Value ? AccentColour : ColourProvider.Background2; + protected virtual Colour4 TextColour => Active.Value ? ColourProvider.Background6 : Colour4.White; + + protected const double TRANSITION_DURATION = 150; + + protected readonly Box Background; + protected readonly Container SwitchContainer; + protected readonly Container MainContentContainer; + protected readonly Box TextBackground; + protected readonly FillFlowContainer TextFlow; + + [Resolved] + protected OverlayColourProvider ColourProvider { get; private set; } = null!; + + private readonly OsuSpriteText titleText; + private readonly OsuSpriteText descriptionText; + + private readonly Bindable samplePlaybackDisabled = new BindableBool(); + private Sample? sampleOff; + private Sample? sampleOn; + + protected ModSelectOverlayPanel() + { + RelativeSizeAxes = Axes.X; + Height = HEIGHT; + + // all below properties are applied to `Content` rather than the `ModPanel` in its entirety + // to allow external components to set these properties on the panel without affecting + // its "internal" appearance. + Content.Masking = true; + Content.CornerRadius = CORNER_RADIUS; + Content.BorderThickness = 2; + + Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); + + Children = new Drawable[] + { + Background = new Box + { + RelativeSizeAxes = Axes.Both + }, + SwitchContainer = new Container + { + RelativeSizeAxes = Axes.Y, + }, + MainContentContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = CORNER_RADIUS, + Children = new Drawable[] + { + TextBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + TextFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Horizontal = 17.5f, + Vertical = 4 + }, + Direction = FillDirection.Vertical, + Children = new[] + { + titleText = new OsuSpriteText + { + Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold), + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Margin = new MarginPadding + { + Left = -18 * ShearedOverlayContainer.SHEAR + } + }, + descriptionText = new OsuSpriteText + { + Font = OsuFont.Default.With(size: 12), + RelativeSizeAxes = Axes.X, + Truncate = true, + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0) + } + } + } + } + } + } + }; + + Action = () => Active.Toggle(); + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio, ISamplePlaybackDisabler? samplePlaybackDisabler) + { + sampleOn = audio.Samples.Get(@"UI/check-on"); + sampleOff = audio.Samples.Get(@"UI/check-off"); + + if (samplePlaybackDisabler != null) + ((IBindable)samplePlaybackDisabled).BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); + } + + protected sealed override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); + + protected override void LoadComplete() + { + base.LoadComplete(); + Active.BindValueChanged(_ => + { + playStateChangeSamples(); + UpdateState(); + }); + + UpdateState(); + FinishTransforms(true); + } + + private void playStateChangeSamples() + { + if (samplePlaybackDisabled.Value) + return; + + if (Active.Value) + sampleOn?.Play(); + else + sampleOff?.Play(); + } + + protected override bool OnHover(HoverEvent e) + { + UpdateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + UpdateState(); + base.OnHoverLost(e); + } + + private bool mouseDown; + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button == MouseButton.Left) + mouseDown = true; + + UpdateState(); + return false; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + mouseDown = false; + + UpdateState(); + base.OnMouseUp(e); + } + + protected virtual void UpdateState() + { + float targetWidth = Active.Value ? ExpandedSwitchWidth : IdleSwitchWidth; + double transitionDuration = TRANSITION_DURATION; + + Colour4 backgroundColour = BackgroundColour; + Colour4 foregroundColour = ForegroundColour; + Colour4 textColour = TextColour; + + // Hover affects colour of button background + if (IsHovered) + { + backgroundColour = backgroundColour.Lighten(0.1f); + foregroundColour = foregroundColour.Lighten(0.1f); + } + + // Mouse down adds a halfway tween of the movement + if (mouseDown) + { + targetWidth = (float)Interpolation.Lerp(IdleSwitchWidth, ExpandedSwitchWidth, 0.5f); + transitionDuration *= 4; + } + + Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, foregroundColour), transitionDuration, Easing.OutQuint); + Background.FadeColour(backgroundColour, transitionDuration, Easing.OutQuint); + SwitchContainer.ResizeWidthTo(targetWidth, transitionDuration, Easing.OutQuint); + MainContentContainer.TransformTo(nameof(Padding), new MarginPadding + { + Left = targetWidth, + Right = CORNER_RADIUS + }, transitionDuration, Easing.OutQuint); + TextBackground.FadeColour(foregroundColour, transitionDuration, Easing.OutQuint); + TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint); + } + } +} From 6cd18fad99b65202a87c2a7e4399b1b86990a963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jul 2022 23:29:02 +0200 Subject: [PATCH 757/803] Fix code inspections after base panel class extraction --- osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs | 6 +++--- osu.Game/Overlays/Mods/ModColumn.cs | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs index c8d55c213a..c0a4cf2a25 100644 --- a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Masking = true, - CornerRadius = ModPanel.CORNER_RADIUS, + CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0), Children = new Drawable[] { @@ -69,7 +69,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Y, - Width = multiplier_value_area_width + ModPanel.CORNER_RADIUS + Width = multiplier_value_area_width + ModSelectOverlayPanel.CORNER_RADIUS }, new GridContainer { @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Masking = true, - CornerRadius = ModPanel.CORNER_RADIUS, + CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, Children = new Drawable[] { contentBackground = new Box diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 3f788d10e3..63a51aaad0 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -105,20 +105,20 @@ namespace osu.Game.Overlays.Mods TopLevelContent = new Container { RelativeSizeAxes = Axes.Both, - CornerRadius = ModPanel.CORNER_RADIUS, + CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, Masking = true, Children = new Drawable[] { new Container { RelativeSizeAxes = Axes.X, - Height = header_height + ModPanel.CORNER_RADIUS, + Height = header_height + ModSelectOverlayPanel.CORNER_RADIUS, Children = new Drawable[] { headerBackground = new Box { RelativeSizeAxes = Axes.X, - Height = header_height + ModPanel.CORNER_RADIUS + Height = header_height + ModSelectOverlayPanel.CORNER_RADIUS }, headerText = new OsuTextFlowContainer(t => { @@ -135,7 +135,7 @@ namespace osu.Game.Overlays.Mods Padding = new MarginPadding { Horizontal = 17, - Bottom = ModPanel.CORNER_RADIUS + Bottom = ModSelectOverlayPanel.CORNER_RADIUS } } } @@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Mods { RelativeSizeAxes = Axes.Both, Masking = true, - CornerRadius = ModPanel.CORNER_RADIUS, + CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, BorderThickness = 3, Children = new Drawable[] { From de0a076eb6ce9835810a13977319413c4e41ac1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jul 2022 20:56:43 +0200 Subject: [PATCH 758/803] Add model class for mod presets --- osu.Game/Rulesets/Mods/ModPreset.cs | 35 +++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 osu.Game/Rulesets/Mods/ModPreset.cs diff --git a/osu.Game/Rulesets/Mods/ModPreset.cs b/osu.Game/Rulesets/Mods/ModPreset.cs new file mode 100644 index 0000000000..367acc8a91 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModPreset.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 System; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// A mod preset is a named collection of configured mods. + /// Presets are presented to the user in the mod select overlay for convenience. + /// + public class ModPreset + { + /// + /// The ruleset that the preset is valid for. + /// + public RulesetInfo RulesetInfo { get; set; } = null!; + + /// + /// The name of the mod preset. + /// + public string Name { get; set; } = string.Empty; + + /// + /// The description of the mod preset. + /// + public string Description { get; set; } = string.Empty; + + /// + /// The set of configured mods that are part of the preset. + /// + public ICollection Mods { get; set; } = Array.Empty(); + } +} From bdff7f1ef409bb48ca98a39d65c689c238281791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jul 2022 22:01:03 +0200 Subject: [PATCH 759/803] Implement basic appearance of mod preset panels --- .../UserInterface/TestSceneModPresetPanel.cs | 74 +++++++++++++++++++ osu.Game/Overlays/Mods/ModPresetPanel.cs | 27 +++++++ .../Overlays/Mods/ModSelectOverlayPanel.cs | 2 + 3 files changed, 103 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs create mode 100644 osu.Game/Overlays/Mods/ModPresetPanel.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs new file mode 100644 index 0000000000..62e63d47bc --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.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.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneModPresetPanel : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + [Test] + public void TestVariousModPresets() + { + AddStep("create content", () => Child = new FillFlowContainer + { + Width = 300, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(0, 5), + ChildrenEnumerable = createTestPresets().Select(preset => new ModPresetPanel(preset)) + }); + } + + private static IEnumerable createTestPresets() => new[] + { + new ModPreset + { + Name = "First preset", + Description = "Please ignore", + Mods = new Mod[] + { + new OsuModHardRock(), + new OsuModDoubleTime() + } + }, + new ModPreset + { + Name = "AR0", + Description = "For good readers", + Mods = new Mod[] + { + new OsuModDifficultyAdjust + { + ApproachRate = { Value = 0 } + } + } + }, + new ModPreset + { + Name = "This preset is going to have an extraordinarily long name", + Description = "This is done so that the capability to truncate overlong texts may be demonstrated", + Mods = new Mod[] + { + new OsuModFlashlight(), + new OsuModSpinIn() + } + } + }; + } +} diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs new file mode 100644 index 0000000000..7a55f99088 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModPresetPanel.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.Framework.Bindables; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Overlays.Mods +{ + public class ModPresetPanel : ModSelectOverlayPanel + { + public override BindableBool Active { get; } = new BindableBool(); + + public ModPresetPanel(ModPreset preset) + { + Title = preset.Name; + Description = preset.Description; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Orange1; + } + } +} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs b/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs index d4969e0a81..a794884d7d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs @@ -122,6 +122,8 @@ namespace osu.Game.Overlays.Mods titleText = new OsuSpriteText { Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold), + RelativeSizeAxes = Axes.X, + Truncate = true, Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), Margin = new MarginPadding { From a3090003de8af21d617c373285bb98350c83933e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jul 2022 22:34:54 +0200 Subject: [PATCH 760/803] Add tooltip showing contents of mod preset --- osu.Game/Overlays/Mods/ModPresetPanel.cs | 10 +- osu.Game/Overlays/Mods/ModPresetTooltip.cs | 115 +++++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/Mods/ModPresetTooltip.cs diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs index 7a55f99088..39f092b91a 100644 --- a/osu.Game/Overlays/Mods/ModPresetPanel.cs +++ b/osu.Game/Overlays/Mods/ModPresetPanel.cs @@ -3,17 +3,22 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.Cursor; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays.Mods { - public class ModPresetPanel : ModSelectOverlayPanel + public class ModPresetPanel : ModSelectOverlayPanel, IHasCustomTooltip { + public readonly ModPreset Preset; + public override BindableBool Active { get; } = new BindableBool(); public ModPresetPanel(ModPreset preset) { + Preset = preset; + Title = preset.Name; Description = preset.Description; } @@ -23,5 +28,8 @@ namespace osu.Game.Overlays.Mods { AccentColour = colours.Orange1; } + + public ModPreset TooltipContent => Preset; + public ITooltip GetCustomTooltip() => new ModPresetTooltip(ColourProvider); } } diff --git a/osu.Game/Overlays/Mods/ModPresetTooltip.cs b/osu.Game/Overlays/Mods/ModPresetTooltip.cs new file mode 100644 index 0000000000..68da649e81 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModPresetTooltip.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 System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public class ModPresetTooltip : VisibilityContainer, ITooltip + { + protected override Container Content { get; } + + private const double transition_duration = 200; + + public ModPresetTooltip(OverlayColourProvider colourProvider) + { + Width = 250; + AutoSizeAxes = Axes.Y; + + Masking = true; + CornerRadius = 7; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6 + }, + Content = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(7), + Spacing = new Vector2(7) + } + }; + } + + private ModPreset? lastPreset; + + public void SetContent(ModPreset preset) + { + if (preset == lastPreset) + return; + + lastPreset = preset; + Content.ChildrenEnumerable = preset.Mods.Select(mod => new ModPresetRow(mod)); + } + + protected override void PopIn() => this.FadeIn(transition_duration, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(transition_duration, Easing.OutQuint); + + public void Move(Vector2 pos) => Position = pos; + + private class ModPresetRow : FillFlowContainer + { + public ModPresetRow(Mod mod) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + Spacing = new Vector2(4); + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7), + Children = new Drawable[] + { + new ModSwitchTiny(mod) + { + Active = { Value = true }, + Scale = new Vector2(0.6f), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + new OsuSpriteText + { + Text = mod.Name, + Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Margin = new MarginPadding { Bottom = 2 } + } + } + } + }; + + if (!string.IsNullOrEmpty(mod.SettingDescription)) + { + AddInternal(new OsuTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = 14 }, + Text = mod.SettingDescription + }); + } + } + } + } +} From d69dc457ba2adf3d0a6db8f89237292cebb62efd Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Thu, 21 Jul 2022 17:28:43 -0500 Subject: [PATCH 761/803] Extract zoom delta method --- .../Edit/Compose/Components/Timeline/TimelineArea.cs | 4 +--- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 6 ++++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index c25a4834d3..b47c09109b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -20,8 +20,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Drawable userContent; - private const float zoom_button_sensitivity = 0.02f; - public TimelineArea(Drawable content = null) { RelativeSizeAxes = Axes.X; @@ -156,6 +154,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Timeline.TicksVisible.BindTo(ticksCheckbox.Current); } - private void changeZoom(float change) => Timeline.Zoom += change * Timeline.MaxZoom * zoom_button_sensitivity; + private void changeZoom(float change) => Timeline.Zoom += Timeline.CalculateZoomChange(change); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 7a4d45301f..9bd7332105 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline set => updateZoom(value); } - private const float zoom_scroll_sensitivity = 0.02f; + private const float zoom_change_sensitivity = 0.02f; private void updateZoom(float? value = null) { @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (e.AltPressed) { // zoom when holding alt. - setZoomTarget(zoomTarget + e.ScrollDelta.Y * MaxZoom * zoom_scroll_sensitivity, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); + setZoomTarget(zoomTarget + CalculateZoomChange(e.ScrollDelta.Y), zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); return true; } @@ -167,6 +167,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { } + public float CalculateZoomChange(float rawChange) => rawChange * MaxZoom * zoom_change_sensitivity; + private class TransformZoom : Transform { /// From 997fe00cdcab42a58ef1ba7533acc1278fe42e37 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Thu, 21 Jul 2022 17:29:13 -0500 Subject: [PATCH 762/803] Fix zoom delta math --- .../Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 9bd7332105..4d95600450 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -167,7 +167,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { } - public float CalculateZoomChange(float rawChange) => rawChange * MaxZoom * zoom_change_sensitivity; + public float CalculateZoomChange(float rawChange) => rawChange * (MaxZoom - minZoom) * zoom_change_sensitivity; private class TransformZoom : Transform { From ed94d7fce84c5cc4eefcabc0585647d270d85129 Mon Sep 17 00:00:00 2001 From: LukynkaCZE <48604271+LukynkaCZE@users.noreply.github.com> Date: Fri, 22 Jul 2022 02:46:17 +0200 Subject: [PATCH 763/803] Fix requested changes --- osu.Game/Localisation/ToastStrings.cs | 20 +++++--------------- osu.Game/Screens/Edit/Editor.cs | 4 ++-- osu.Game/Skinning/Editor/SkinEditor.cs | 4 ++-- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 4169a23798..9ceee807e6 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -35,24 +35,14 @@ namespace osu.Game.Localisation public static LocalisableString RestartTrack => new TranslatableString(getKey(@"restart_track"), @"Restart track"); /// - /// "Beatmap Editor" - /// r - public static LocalisableString BeatmapEditor => new TranslatableString(getKey(@"beatmap_editor"), @"Beatmap Editor"); + /// "Beatmap saved" + /// + public static LocalisableString BeatmapSaved => new TranslatableString(getKey(@"beatmap_saved"), @"Beatmap saved"); /// - /// "Beatmap Saved" + /// "Skin saved" /// - public static LocalisableString EditorSaveBeatmap => new TranslatableString(getKey(@"beatmap_editor_save"), @"Beatmap Saved"); - - /// - /// "Skin Editor" - /// - public static LocalisableString SkinEditor => new TranslatableString(getKey(@"skin_editor"), @"Skin Editor"); - - /// - /// "Skin Saved" - /// - public static LocalisableString EditorSaveSkin => new TranslatableString(getKey(@"skin_editor_save"), @"Skin Saved"); + public static LocalisableString SkinSaved => new TranslatableString(getKey(@"skin_saved"), @"Skin saved"); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index bf9785063b..1933076338 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -411,7 +411,7 @@ namespace osu.Game.Screens.Edit // no longer new after first user-triggered save. isNewBeatmap = false; updateLastSavedHash(); - onScreenDisplay?.Display(new BeatmapEditorToast(ToastStrings.EditorSaveBeatmap, editorBeatmap.BeatmapInfo.GetDisplayTitle())); + onScreenDisplay?.Display(new BeatmapEditorToast(ToastStrings.BeatmapSaved, editorBeatmap.BeatmapInfo.GetDisplayTitle())); return true; } @@ -945,7 +945,7 @@ namespace osu.Game.Screens.Edit private class BeatmapEditorToast : Toast { public BeatmapEditorToast(LocalisableString value, string beatmapDisplayName) - : base(ToastStrings.BeatmapEditor, value, beatmapDisplayName) { } + : base(InputSettingsStrings.EditorSection, value, beatmapDisplayName) { } } } } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 326574f2da..b02054072b 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -322,7 +322,7 @@ namespace osu.Game.Skinning.Editor currentSkin.Value.UpdateDrawableTarget(t); skins.Save(skins.CurrentSkin.Value); - onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.EditorSaveSkin, currentSkin.Value.SkinInfo.ToString())); + onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.SkinSaved, currentSkin.Value.SkinInfo.ToString())); } protected override bool OnHover(HoverEvent e) => true; @@ -406,7 +406,7 @@ namespace osu.Game.Skinning.Editor private class SkinEditorToast : Toast { public SkinEditorToast(LocalisableString value, string skinDisplayName) - : base(ToastStrings.SkinEditor, value, skinDisplayName) { } + : base(SkinSettingsStrings.SkinLayoutEditor, value, skinDisplayName) { } } } } From 92f59ce9a0da37e0c5e2a7d10d9d84ce95675161 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 13:10:18 +0900 Subject: [PATCH 764/803] Add the ability to save in the skin editor using system save hotkey --- osu.Game/Skinning/Editor/SkinEditor.cs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 649b63dda4..27c8d0711a 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -13,6 +13,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Testing; using osu.Game.Database; @@ -27,7 +29,7 @@ using osu.Game.Screens.Edit.Components.Menus; namespace osu.Game.Skinning.Editor { [Cached(typeof(SkinEditor))] - public class SkinEditor : VisibilityContainer, ICanAcceptFiles + public class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBindingHandler { public const double TRANSITION_DURATION = 500; @@ -199,6 +201,25 @@ namespace osu.Game.Skinning.Editor SelectedComponents.BindCollectionChanged((_, _) => Scheduler.AddOnce(populateSettings), true); } + public bool OnPressed(KeyBindingPressEvent e) + { + switch (e.Action) + { + case PlatformAction.Save: + if (e.Repeat) + return false; + + Save(); + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + public void UpdateTargetScreen(Drawable targetScreen) { this.targetScreen = targetScreen; From f713253d1b3bf4c959a58cbeebf51edf4742b18e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 14:00:29 +0900 Subject: [PATCH 765/803] Fix formatting inconsistencies in empty `ctor`s --- osu.Game/Screens/Edit/Editor.cs | 4 +++- osu.Game/Skinning/Editor/SkinEditor.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 1933076338..3e3940c5ba 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -945,7 +945,9 @@ namespace osu.Game.Screens.Edit private class BeatmapEditorToast : Toast { public BeatmapEditorToast(LocalisableString value, string beatmapDisplayName) - : base(InputSettingsStrings.EditorSection, value, beatmapDisplayName) { } + : base(InputSettingsStrings.EditorSection, value, beatmapDisplayName) + { + } } } } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index b02054072b..741cad3e57 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -406,7 +406,9 @@ namespace osu.Game.Skinning.Editor private class SkinEditorToast : Toast { public SkinEditorToast(LocalisableString value, string skinDisplayName) - : base(SkinSettingsStrings.SkinLayoutEditor, value, skinDisplayName) { } + : base(SkinSettingsStrings.SkinLayoutEditor, value, skinDisplayName) + { + } } } } From 4cec9a085a4e4e9678785f21d63aed7fee86599a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 14:44:48 +0900 Subject: [PATCH 766/803] Combine both calls to use same pathway --- .../Compose/Components/Timeline/TimelineArea.cs | 6 ++---- .../Timeline/ZoomableScrollContainer.cs | 17 ++++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index b47c09109b..c2415ce978 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -118,7 +118,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Y, Height = 0.5f, Icon = FontAwesome.Solid.SearchPlus, - Action = () => changeZoom(1) + Action = () => Timeline.AdjustZoomRelatively(1) }, new TimelineButton { @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Y, Height = 0.5f, Icon = FontAwesome.Solid.SearchMinus, - Action = () => changeZoom(-1) + Action = () => Timeline.AdjustZoomRelatively(-1) }, } } @@ -153,7 +153,5 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Timeline.ControlPointsVisible.BindTo(controlPointsCheckbox.Current); Timeline.TicksVisible.BindTo(ticksCheckbox.Current); } - - private void changeZoom(float change) => Timeline.Zoom += Timeline.CalculateZoomChange(change); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 4d95600450..96f6ef6d02 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -104,8 +104,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline set => updateZoom(value); } - private const float zoom_change_sensitivity = 0.02f; - private void updateZoom(float? value = null) { float newZoom = Math.Clamp(value ?? Zoom, MinZoom, MaxZoom); @@ -129,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (e.AltPressed) { // zoom when holding alt. - setZoomTarget(zoomTarget + CalculateZoomChange(e.ScrollDelta.Y), zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); + AdjustZoomRelatively(e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); return true; } @@ -147,12 +145,19 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline zoomedContentWidthCache.Validate(); } + public void AdjustZoomRelatively(float change, float? focusPoint = null) + { + const float zoom_change_sensitivity = 0.02f; + + setZoomTarget(zoomTarget + change * (MaxZoom - minZoom) * zoom_change_sensitivity, focusPoint); + } + private float zoomTarget = 1; - private void setZoomTarget(float newZoom, float focusPoint) + private void setZoomTarget(float newZoom, float? focusPoint = null) { zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom); - transformZoomTo(zoomTarget, focusPoint, ZoomDuration, ZoomEasing); + transformZoomTo(zoomTarget, focusPoint ?? DrawWidth / 2, ZoomDuration, ZoomEasing); OnZoomChanged(); } @@ -167,8 +172,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { } - public float CalculateZoomChange(float rawChange) => rawChange * (MaxZoom - minZoom) * zoom_change_sensitivity; - private class TransformZoom : Transform { /// From 3b913bb9ad9fd63ec185ad96a5b51f888b72110e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 22 Jul 2022 09:15:48 +0300 Subject: [PATCH 767/803] Fix sorting mode not filling up to usable area in filter control --- osu.Game/Screens/Select/FilterControl.cs | 69 ++++++++++++++---------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index e43261f374..d39862b65f 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -115,42 +115,53 @@ namespace osu.Game.Screens.Select Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, }, - new FillFlowContainer + new GridContainer { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Direction = FillDirection.Horizontal, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Spacing = new Vector2(OsuTabControl.HORIZONTAL_SPACING, 0), - Children = new Drawable[] + ColumnDimensions = new[] { - new OsuTabControlCheckbox + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, OsuTabControl.HORIZONTAL_SPACING), + new Dimension(), + new Dimension(GridSizeMode.Absolute, OsuTabControl.HORIZONTAL_SPACING), + new Dimension(GridSizeMode.AutoSize), + }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + Content = new[] + { + new[] { - Text = "Show converted", - Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }, - sortTabs = new OsuTabControl - { - RelativeSizeAxes = Axes.X, - Width = 0.5f, - Height = 24, - AutoSort = true, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - AccentColour = colours.GreenLight, - Current = { BindTarget = sortMode } - }, - new OsuSpriteText - { - Text = SortStrings.Default, - Font = OsuFont.GetFont(size: 14), - Margin = new MarginPadding(5), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }, + new OsuSpriteText + { + Text = SortStrings.Default, + Font = OsuFont.GetFont(size: 14), + Margin = new MarginPadding(5), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + Empty(), + sortTabs = new OsuTabControl + { + RelativeSizeAxes = Axes.X, + Height = 24, + AutoSort = true, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + AccentColour = colours.GreenLight, + Current = { BindTarget = sortMode } + }, + Empty(), + new OsuTabControlCheckbox + { + Text = "Show converted", + Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + } } }, } From 6eb42d08ce2ebbe216424a7179fbe65629d0eb09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 15:21:25 +0900 Subject: [PATCH 768/803] Fix timeline zoom receiving feedback from bindable changes This causes the focal point zooming to not work (as the focal point is lost). There's no need to handle ongoing changes to `BeatmapInfo.TimelineZoom` because it is not a property which is changed at runtime. --- .../Edit/Compose/Components/Timeline/Timeline.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 37fc4b03b2..bbe011a2e0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -41,6 +41,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private EditorClock editorClock { get; set; } + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } + /// /// The timeline's scroll position in the last frame. /// @@ -68,8 +71,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// private float defaultTimelineZoom; - private readonly Bindable timelineZoomScale = new BindableDouble(1.0); - public Timeline(Drawable userContent) { this.userContent = userContent; @@ -93,7 +94,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable waveformOpacity; [BackgroundDependencyLoader] - private void load(IBindable beatmap, EditorBeatmap editorBeatmap, OsuColour colours, OsuConfigManager config) + private void load(IBindable beatmap, OsuColour colours, OsuConfigManager config) { CentreMarker centreMarker; @@ -154,12 +155,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } }, true); - timelineZoomScale.Value = editorBeatmap.BeatmapInfo.TimelineZoom; - timelineZoomScale.BindValueChanged(scale => - { - Zoom = (float)(defaultTimelineZoom * scale.NewValue); - editorBeatmap.BeatmapInfo.TimelineZoom = scale.NewValue; - }, true); + Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); } protected override void LoadComplete() @@ -221,7 +217,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void OnZoomChanged() { base.OnZoomChanged(); - timelineZoomScale.Value = Zoom / defaultTimelineZoom; + editorBeatmap.BeatmapInfo.TimelineZoom = Zoom / defaultTimelineZoom; } protected override void UpdateAfterChildren() From e20458421a9ad07a409bafc3b22d4388ad0ee5a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 16:05:20 +0900 Subject: [PATCH 769/803] Update flaky timeline zoom test to output something useful --- osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs index 2cada1989e..6c5cca1874 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs @@ -5,7 +5,6 @@ using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Utils; namespace osu.Game.Tests.Visual.Editing { @@ -32,12 +31,12 @@ namespace osu.Game.Tests.Visual.Editing AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange); AddStep("scale zoom", () => TimelineArea.Timeline.Zoom = 200); - AddAssert("range halved", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange / 2, 1)); + AddStep("range halved", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange / 2).Within(1))); AddStep("descale zoom", () => TimelineArea.Timeline.Zoom = 50); - AddAssert("range doubled", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange * 2, 1)); + AddStep("range doubled", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange * 2).Within(1))); AddStep("restore zoom", () => TimelineArea.Timeline.Zoom = 100); - AddAssert("range restored", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange, 1)); + AddStep("range restored", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange).Within(1))); } [Test] From b884ed2a3d8b8fd50412134a3f162a84c1c29417 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 16:18:14 +0900 Subject: [PATCH 770/803] Make test actually test drum behaviours --- .../TestSceneDrumTouchInputArea.cs | 63 +++++++++---------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs index 45555a55c1..979464db5d 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs @@ -1,55 +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 System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Taiko.Objects; +using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - public class TestSceneDrumTouchInputArea : DrawableTaikoRulesetTestScene + public class TestSceneDrumTouchInputArea : OsuTestScene { - protected const double NUM_HIT_OBJECTS = 10; - protected const double HIT_OBJECT_TIME_SPACING_MS = 1000; + [Cached] + private TaikoInputManager taikoInputManager = new TaikoInputManager(new TaikoRuleset().RulesetInfo); - [BackgroundDependencyLoader] - private void load() + private DrumTouchInputArea drumTouchInputArea = null!; + + [SetUpSteps] + public void SetUpSteps() { - var drumTouchInputArea = new DrumTouchInputArea(); - DrawableRuleset.KeyBindingInputManager.Add(drumTouchInputArea); - drumTouchInputArea.ShowTouchControls(); - } - - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) - { - List hitObjects = new List(); - - for (int i = 0; i < NUM_HIT_OBJECTS; i++) + AddStep("create drum", () => { - hitObjects.Add(new Hit + Children = new Drawable[] { - StartTime = Time.Current + i * HIT_OBJECT_TIME_SPACING_MS, - IsStrong = isOdd(i), - Type = isOdd(i / 2) ? HitType.Centre : HitType.Rim - }); - } - - var beatmap = new Beatmap - { - BeatmapInfo = { Ruleset = ruleset }, - HitObjects = hitObjects - }; - - return beatmap; + new InputDrum + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Height = 0.5f, + }, + drumTouchInputArea = new DrumTouchInputArea + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Height = 0.5f, + }, + }; + }); } - private bool isOdd(int number) + [Test] + public void TestDrum() { - return number % 2 == 0; + AddStep("show drum", () => drumTouchInputArea.Show()); } } } From 7015cf0b1b303607857c42a34ae9e36c0b434122 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 16:08:57 +0900 Subject: [PATCH 771/803] Move touch input drum to own file for now --- .../UI/DrumTouchInputArea.cs | 12 +- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 13 +- osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs | 187 ++++++++++++++++++ 3 files changed, 197 insertions(+), 15 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index a66c52df1f..679e66d33b 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -25,10 +26,10 @@ namespace osu.Game.Rulesets.Taiko.UI // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) private const float offscreen_percent = 0.35f; - private readonly InputDrum touchInputDrum; + private readonly TouchInputDrum touchInputDrum; private readonly Circle drumBackground; - private KeyBindingContainer keyBindingContainer; + private KeyBindingContainer keyBindingContainer = null!; // Which Taiko action was pressed by the last OnMouseDown event, so that the corresponding action can be released OnMouseUp even if the cursor position moved private TaikoAction mouseAction; @@ -62,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI FillMode = FillMode.Fit, Alpha = 0.9f, }, - touchInputDrum = new InputDrum + touchInputDrum = new TouchInputDrum { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -84,7 +85,10 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) { - keyBindingContainer = taikoInputManager?.KeyBindingContainer; + Debug.Assert(taikoInputManager?.KeyBindingContainer != null); + + keyBindingContainer = taikoInputManager.KeyBindingContainer; + drumBackground.Colour = colours.Gray0; } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 97990395a2..054f98e18f 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osuTK; @@ -24,7 +23,6 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { - public float CentreSize = 0.7f; private const float middle_split = 0.025f; public InputDrum() @@ -64,7 +62,7 @@ namespace osu.Game.Rulesets.Taiko.UI Scale = new Vector2(0.9f), Children = new[] { - new TaikoHalfDrum(false, CentreSize) + new TaikoHalfDrum(false) { Name = "Left Half", Anchor = Anchor.Centre, @@ -75,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.UI RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new TaikoHalfDrum(true, CentreSize) + new TaikoHalfDrum(true) { Name = "Right Half", Anchor = Anchor.Centre, @@ -110,9 +108,6 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - [Resolved] - private DrumSampleTriggerSource sampleTriggerSource { get; set; } - public TaikoHalfDrum(bool flipped) { Masking = true; @@ -173,15 +168,11 @@ namespace osu.Game.Rulesets.Taiko.UI { target = centreHit; back = centre; - - sampleTriggerSource.Play(HitType.Centre); } else if (e.Action == RimAction) { target = rimHit; back = rim; - - sampleTriggerSource.Play(HitType.Rim); } if (target != null) diff --git a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs new file mode 100644 index 0000000000..85eec31a8a --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs @@ -0,0 +1,187 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.UI +{ + /// + /// A component of the playfield that captures input and displays input as a drum. + /// + internal class TouchInputDrum : Container + { + public float CentreSize = 0.7f; + private const float middle_split = 0.025f; + + public TouchInputDrum() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Scale = new Vector2(0.9f), + Children = new Drawable[] + { + new TaikoHalfDrum(false, CentreSize) + { + Name = "Left Half", + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = -middle_split / 2, + RimAction = TaikoAction.LeftRim, + CentreAction = TaikoAction.LeftCentre + }, + new TaikoHalfDrum(true, CentreSize) + { + Name = "Right Half", + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = middle_split / 2, + RimAction = TaikoAction.RightRim, + CentreAction = TaikoAction.RightCentre + } + } + }) + }; + } + + /// + /// A half-drum. Contains one centre and one rim hit. + /// + private class TaikoHalfDrum : Container, IKeyBindingHandler + { + /// + /// The key to be used for the rim of the half-drum. + /// + public TaikoAction RimAction; + + /// + /// The key to be used for the centre of the half-drum. + /// + public TaikoAction CentreAction; + + private readonly Sprite rim; + private readonly Sprite rimHit; + private readonly Sprite centre; + private readonly Sprite centreHit; + + public TaikoHalfDrum(bool flipped, float centreSize) + { + Masking = true; + + Children = new Drawable[] + { + rim = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both + }, + rimHit = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Blending = BlendingParameters.Additive, + }, + centre = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(centreSize) + }, + centreHit = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(centreSize), + Alpha = 0, + Blending = BlendingParameters.Additive + } + }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures, OsuColour colours) + { + rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer"); + rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit"); + centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner"); + centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit"); + + rimHit.Colour = colours.Blue; + centreHit.Colour = colours.Pink; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + Drawable target = null; + Drawable back = null; + + if (e.Action == CentreAction) + { + target = centreHit; + back = centre; + } + else if (e.Action == RimAction) + { + target = rimHit; + back = rim; + } + + if (target != null) + { + const float scale_amount = 0.05f; + const float alpha_amount = 0.5f; + + const float down_time = 40; + const float up_time = 1000; + + back.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint) + .Then() + .ScaleTo(1, up_time, Easing.OutQuint); + + target.Animate( + t => t.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint), + t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, Easing.OutQuint) + ).Then( + t => t.ScaleTo(1, up_time, Easing.OutQuint), + t => t.FadeOut(up_time, Easing.OutQuint) + ); + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } + } +} From b604eb62620e811567239dcd291a6a3703dc072d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 16:18:22 +0900 Subject: [PATCH 772/803] Simplify implementation --- .../TestSceneDrumTouchInputArea.cs | 29 ++-- .../UI/DrawableTaikoRuleset.cs | 7 +- .../UI/DrumTouchInputArea.cs | 135 ++++++++---------- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs | 5 +- 5 files changed, 75 insertions(+), 103 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs index 979464db5d..7210419c0e 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Rulesets.Taiko.UI; @@ -13,9 +12,6 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestFixture] public class TestSceneDrumTouchInputArea : OsuTestScene { - [Cached] - private TaikoInputManager taikoInputManager = new TaikoInputManager(new TaikoRuleset().RulesetInfo); - private DrumTouchInputArea drumTouchInputArea = null!; [SetUpSteps] @@ -23,19 +19,22 @@ namespace osu.Game.Rulesets.Taiko.Tests { AddStep("create drum", () => { - Children = new Drawable[] + Child = new TaikoInputManager(new TaikoRuleset().RulesetInfo) { - new InputDrum + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Height = 0.5f, - }, - drumTouchInputArea = new DrumTouchInputArea - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Height = 0.5f, + new InputDrum + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Height = 0.2f, + }, + drumTouchInputArea = new DrumTouchInputArea + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }, }, }; }); diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index a117435ada..2fa511b8ab 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -39,8 +39,6 @@ namespace osu.Game.Rulesets.Taiko.UI private SkinnableDrawable scroller; - private DrumTouchInputArea drumTouchInputArea; - public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { @@ -59,8 +57,7 @@ namespace osu.Game.Rulesets.Taiko.UI Depth = float.MaxValue }); - if ((drumTouchInputArea = CreateDrumTouchInputArea()) != null) - KeyBindingInputManager.Add(drumTouchInputArea); + KeyBindingInputManager.Add(new DrumTouchInputArea()); } protected override void UpdateAfterChildren() @@ -79,8 +76,6 @@ namespace osu.Game.Rulesets.Taiko.UI return ControlPoints[result]; } - public DrumTouchInputArea CreateDrumTouchInputArea() => new DrumTouchInputArea(); - public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer { LockPlayfieldAspect = { BindTarget = LockPlayfieldAspect } diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 679e66d33b..55eb533a47 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.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.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; @@ -13,152 +12,132 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; using osuTK; +using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Rulesets.Taiko.UI { /// - /// An overlay that captures and displays Taiko mouse and touch input. - /// The boundaries of this overlay defines the interactable area for touch input. - /// A secondary InputDrum is attached by this overlay, which defines the circular boundary which distinguishes "centre" from "rim" hits, and also displays input. + /// An overlay that captures and displays osu!taiko mouse and touch input. /// public class DrumTouchInputArea : Container { - // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreen_percent = 0.35f; - - private readonly TouchInputDrum touchInputDrum; - private readonly Circle drumBackground; + private readonly Circle outerCircle; private KeyBindingContainer keyBindingContainer = null!; - // Which Taiko action was pressed by the last OnMouseDown event, so that the corresponding action can be released OnMouseUp even if the cursor position moved - private TaikoAction mouseAction; + private readonly Dictionary trackedTouches = new Dictionary(); + private readonly Dictionary trackedMouseButtons = new Dictionary(); - // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved - private readonly Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + private readonly Container mainContent; - private readonly Container visibleComponents; + private readonly Circle centreCircle; public DrumTouchInputArea() { - RelativeSizeAxes = Axes.Both; - RelativePositionAxes = Axes.Both; + RelativeSizeAxes = Axes.X; + Height = 300; + + Masking = true; + Children = new Drawable[] { - visibleComponents = new Container + mainContent = new Container { - Alpha = 0.0f, RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, + Height = 2, Children = new Drawable[] { - drumBackground = new Circle + outerCircle = new Circle { - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, FillMode = FillMode.Fit, - Alpha = 0.9f, - }, - touchInputDrum = new TouchInputDrum - { + RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, + centreCircle = new Circle + { + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.5f), + }, + new Box + { + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.Black, + Width = 3, + }, } }, }; } - protected override void LoadComplete() - { - Padding = new MarginPadding - { - Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield - Bottom = -touchInputDrum.DrawHeight * offscreen_percent, // The drum should go past the bottom of the screen so that it can be wider - }; - } - [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) { - Debug.Assert(taikoInputManager?.KeyBindingContainer != null); + Debug.Assert(taikoInputManager.KeyBindingContainer != null); keyBindingContainer = taikoInputManager.KeyBindingContainer; - drumBackground.Colour = colours.Gray0; + outerCircle.Colour = colours.Gray0; } protected override bool OnMouseDown(MouseDownEvent e) { - ShowTouchControls(); - mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); - keyBindingContainer?.TriggerPressed(mouseAction); + mainContent.Show(); + + TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); + + trackedMouseButtons.Add(e.Button, taikoAction); + keyBindingContainer.TriggerPressed(taikoAction); return true; } protected override void OnMouseUp(MouseUpEvent e) { - keyBindingContainer?.TriggerReleased(mouseAction); + keyBindingContainer.TriggerReleased(trackedMouseButtons[e.Button]); + trackedMouseButtons.Remove(e.Button); base.OnMouseUp(e); } protected override bool OnTouchDown(TouchDownEvent e) { - ShowTouchControls(); + mainContent.Show(); + TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); - touchActions.Add(e.Touch.Source, taikoAction); - keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); + + trackedTouches.Add(e.Touch.Source, taikoAction); + keyBindingContainer.TriggerPressed(taikoAction); return true; } protected override void OnTouchUp(TouchUpEvent e) { - keyBindingContainer?.TriggerReleased(touchActions[e.Touch.Source]); - touchActions.Remove(e.Touch.Source); + keyBindingContainer.TriggerReleased(trackedTouches[e.Touch.Source]); + trackedTouches.Remove(e.Touch.Source); base.OnTouchUp(e); } protected override bool OnKeyDown(KeyDownEvent e) { - HideTouchControls(); + mainContent.Hide(); return false; } - public void ShowTouchControls() - { - visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); - } - - public void HideTouchControls() - { - visibleComponents.Animate(components => components.FadeOut(2000, Easing.OutQuint)); - } - private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { - bool centreHit = inputIsCenterHit(inputPosition); - bool leftSide = inputIsOnLeftSide(inputPosition); + bool centreHit = centreCircle.ScreenSpaceDrawQuad.Contains(inputPosition); + bool leftSide = ToLocalSpace(inputPosition).X < DrawWidth / 2; - return centreHit ? (leftSide ? TaikoAction.LeftCentre : TaikoAction.RightCentre) : (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); - } + if (leftSide) + return centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim; - private bool inputIsOnLeftSide(Vector2 inputPosition) - { - Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; - return inputPositionToDrumCentreDelta.X < 0f; - } - - private bool inputIsCenterHit(Vector2 inputPosition) - { - Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; - - float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; - float centreRadius = (inputDrumRadius * touchInputDrum.CentreSize); - return inputPositionToDrumCentreDelta.Length <= centreRadius; + return centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim; } } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index a17fc0391a..ccca5587b7 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load(OsuColour colours) { - inputDrum = new InputDrum() + inputDrum = new InputDrum { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs index 85eec31a8a..21b83e5495 100644 --- a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Taiko.UI @@ -34,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.UI { Children = new Drawable[] { - new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container + new Container { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, @@ -64,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI CentreAction = TaikoAction.RightCentre } } - }) + } }; } From e1df50c8ffa2047f6eea539952d884b6237bf839 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Jul 2022 17:03:04 +0900 Subject: [PATCH 773/803] Fix timeline zoom focus point when using buttons --- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 96f6ef6d02..83fd1dea2b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -157,7 +157,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void setZoomTarget(float newZoom, float? focusPoint = null) { zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom); - transformZoomTo(zoomTarget, focusPoint ?? DrawWidth / 2, ZoomDuration, ZoomEasing); + focusPoint ??= zoomedContent.ToLocalSpace(ToScreenSpace(new Vector2(DrawWidth / 2, 0))).X; + + transformZoomTo(zoomTarget, focusPoint.Value, ZoomDuration, ZoomEasing); OnZoomChanged(); } From bd6ff40b43b91c8e79966cb0e751f9fc86515de8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 17:02:58 +0900 Subject: [PATCH 774/803] Combine touch and mouse handling into single path --- .../UI/DrawableTaikoRuleset.cs | 10 ++-- .../UI/DrumTouchInputArea.cs | 48 ++++++++++--------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 2fa511b8ab..58e703b8be 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -8,18 +8,18 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.Taiko.Replays; using osu.Framework.Input; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Input.Handlers; using osu.Game.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Rulesets.Timing; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Scoring; using osu.Game.Skinning; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 55eb533a47..253ced9a39 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -7,13 +7,11 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; using osuTK; using osuTK.Graphics; -using osuTK.Input; namespace osu.Game.Rulesets.Taiko.UI { @@ -26,8 +24,7 @@ namespace osu.Game.Rulesets.Taiko.UI private KeyBindingContainer keyBindingContainer = null!; - private readonly Dictionary trackedTouches = new Dictionary(); - private readonly Dictionary trackedMouseButtons = new Dictionary(); + private readonly Dictionary trackedActions = new Dictionary(); private readonly Container mainContent; @@ -87,46 +84,51 @@ namespace osu.Game.Rulesets.Taiko.UI outerCircle.Colour = colours.Gray0; } + protected override bool OnKeyDown(KeyDownEvent e) + { + // Hide whenever the keyboard is used. + mainContent.Hide(); + return false; + } + protected override bool OnMouseDown(MouseDownEvent e) { - mainContent.Show(); - - TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); - - trackedMouseButtons.Add(e.Button, taikoAction); - keyBindingContainer.TriggerPressed(taikoAction); + handleDown(e.Button, e.ScreenSpaceMousePosition); return true; } protected override void OnMouseUp(MouseUpEvent e) { - keyBindingContainer.TriggerReleased(trackedMouseButtons[e.Button]); - trackedMouseButtons.Remove(e.Button); + handleUp(e.Button); base.OnMouseUp(e); } protected override bool OnTouchDown(TouchDownEvent e) { - mainContent.Show(); - - TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); - - trackedTouches.Add(e.Touch.Source, taikoAction); - keyBindingContainer.TriggerPressed(taikoAction); + handleDown(e.Touch.Source, e.ScreenSpaceTouchDownPosition); return true; } protected override void OnTouchUp(TouchUpEvent e) { - keyBindingContainer.TriggerReleased(trackedTouches[e.Touch.Source]); - trackedTouches.Remove(e.Touch.Source); + handleUp(e.Touch.Source); base.OnTouchUp(e); } - protected override bool OnKeyDown(KeyDownEvent e) + private void handleDown(object source, Vector2 position) { - mainContent.Hide(); - return false; + mainContent.Show(); + + TaikoAction taikoAction = getTaikoActionFromInput(position); + + trackedActions.Add(source, taikoAction); + keyBindingContainer.TriggerPressed(taikoAction); + } + + private void handleUp(object source) + { + keyBindingContainer.TriggerReleased(trackedActions[source]); + trackedActions.Remove(source); } private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) From e5ab6652fd5876324a72bfcf7d0de55199901591 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 22 Jul 2022 11:12:24 +0300 Subject: [PATCH 775/803] Fix one more case of referencing old mod select overlay in tests --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index d35887c443..29a5ef82fe 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -633,7 +633,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("invoke on back button", () => multiplayerComponents.OnBackButton()); - AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); + AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().UserModsSelectOverlay.State.Value == Visibility.Hidden); AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden); From aeeedc40b446ae79030ce8acaf862b6b55e798d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 17:17:38 +0900 Subject: [PATCH 776/803] Add first pass design --- .../UI/DrumTouchInputArea.cs | 121 ++++++++++-------- 1 file changed, 70 insertions(+), 51 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 253ced9a39..d8ae4d9210 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -20,68 +20,74 @@ namespace osu.Game.Rulesets.Taiko.UI /// public class DrumTouchInputArea : Container { - private readonly Circle outerCircle; - private KeyBindingContainer keyBindingContainer = null!; private readonly Dictionary trackedActions = new Dictionary(); - private readonly Container mainContent; + private Container mainContent = null!; - private readonly Circle centreCircle; - - public DrumTouchInputArea() - { - RelativeSizeAxes = Axes.X; - Height = 300; - - Masking = true; - - Children = new Drawable[] - { - mainContent = new Container - { - RelativeSizeAxes = Axes.Both, - Height = 2, - Children = new Drawable[] - { - outerCircle = new Circle - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - centreCircle = new Circle - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.5f), - }, - new Box - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Y, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = Color4.Black, - Width = 3, - }, - } - }, - }; - } + private Circle centreCircle = null!; + private Circle outerCircle = null!; [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) { Debug.Assert(taikoInputManager.KeyBindingContainer != null); - keyBindingContainer = taikoInputManager.KeyBindingContainer; - outerCircle.Colour = colours.Gray0; + // Container should handle input everywhere. + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + Height = 300, + Y = 20, + Masking = true, + FillMode = FillMode.Fit, + Children = new Drawable[] + { + mainContent = new Container + { + RelativeSizeAxes = Axes.Both, + Height = 2, + Children = new Drawable[] + { + outerCircle = new Circle + { + FillMode = FillMode.Fit, + Colour = colours.BlueDarker, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + centreCircle = new Circle + { + FillMode = FillMode.Fit, + Colour = colours.YellowDark, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.8f), + }, + new Box + { + Colour = colours.BlueDarker, + RelativeSizeAxes = Axes.Y, + Height = 0.9f, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Width = 7, + }, + } + }, + } + }, + }; } protected override bool OnKeyDown(KeyDownEvent e) @@ -121,6 +127,19 @@ namespace osu.Game.Rulesets.Taiko.UI TaikoAction taikoAction = getTaikoActionFromInput(position); + switch (taikoAction) + { + case TaikoAction.LeftCentre: + case TaikoAction.RightCentre: + centreCircle.FlashColour(Color4.White, 2000, Easing.OutQuint); + break; + + case TaikoAction.LeftRim: + case TaikoAction.RightRim: + outerCircle.FlashColour(Color4.White, 2000, Easing.OutQuint); + break; + } + trackedActions.Add(source, taikoAction); keyBindingContainer.TriggerPressed(taikoAction); } @@ -133,7 +152,7 @@ namespace osu.Game.Rulesets.Taiko.UI private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { - bool centreHit = centreCircle.ScreenSpaceDrawQuad.Contains(inputPosition); + bool centreHit = centreCircle.Contains(inputPosition); bool leftSide = ToLocalSpace(inputPosition).X < DrawWidth / 2; if (leftSide) From 6b69ff19c87c834b670cf933b430e4e7aa248732 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Jul 2022 17:24:46 +0900 Subject: [PATCH 777/803] Remove unused using --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 29a5ef82fe..bf9b99e3e2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -24,7 +24,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mods; From 2d2d98ab6ee3c2b6a1ee6c904782f5844cac7afd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 17:48:07 +0900 Subject: [PATCH 778/803] Add final design pass --- .../UI/DrumTouchInputArea.cs | 151 +++++++++++++----- 1 file changed, 107 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index d8ae4d9210..5ba2ea282e 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -18,16 +18,20 @@ namespace osu.Game.Rulesets.Taiko.UI /// /// An overlay that captures and displays osu!taiko mouse and touch input. /// - public class DrumTouchInputArea : Container + public class DrumTouchInputArea : VisibilityContainer { + // visibility state affects our child. we always want to handle input. + public override bool PropagatePositionalInputSubTree => true; + public override bool PropagateNonPositionalInputSubTree => true; + private KeyBindingContainer keyBindingContainer = null!; private readonly Dictionary trackedActions = new Dictionary(); private Container mainContent = null!; - private Circle centreCircle = null!; - private Circle outerCircle = null!; + private QuarterCircle leftCentre = null!; + private QuarterCircle rightCentre = null!; [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) @@ -38,6 +42,8 @@ namespace osu.Game.Rulesets.Taiko.UI // Container should handle input everywhere. RelativeSizeAxes = Axes.Both; + const float centre_region = 0.80f; + Children = new Drawable[] { new Container @@ -45,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, - Height = 300, + Height = 350, Y = 20, Masking = true, FillMode = FillMode.Fit, @@ -54,35 +60,36 @@ namespace osu.Game.Rulesets.Taiko.UI mainContent = new Container { RelativeSizeAxes = Axes.Both, - Height = 2, Children = new Drawable[] { - outerCircle = new Circle + new QuarterCircle(TaikoAction.LeftRim, colours.YellowDark) { - FillMode = FillMode.Fit, - Colour = colours.BlueDarker, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - centreCircle = new Circle - { - FillMode = FillMode.Fit, - Colour = colours.YellowDark, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.8f), - }, - new Box - { - Colour = colours.BlueDarker, - RelativeSizeAxes = Axes.Y, - Height = 0.9f, Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Width = 7, + Origin = Anchor.BottomRight, + X = -2, }, + new QuarterCircle(TaikoAction.RightRim, colours.YellowDark) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomRight, + X = 2, + Rotation = 90, + }, + leftCentre = new QuarterCircle(TaikoAction.LeftCentre, colours.BlueDark) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomRight, + X = -2, + Scale = new Vector2(centre_region), + }, + rightCentre = new QuarterCircle(TaikoAction.RightCentre, colours.BlueDark) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomRight, + X = 2, + Scale = new Vector2(centre_region), + Rotation = 90, + } } }, } @@ -93,7 +100,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnKeyDown(KeyDownEvent e) { // Hide whenever the keyboard is used. - mainContent.Hide(); + Hide(); return false; } @@ -123,23 +130,10 @@ namespace osu.Game.Rulesets.Taiko.UI private void handleDown(object source, Vector2 position) { - mainContent.Show(); + Show(); TaikoAction taikoAction = getTaikoActionFromInput(position); - switch (taikoAction) - { - case TaikoAction.LeftCentre: - case TaikoAction.RightCentre: - centreCircle.FlashColour(Color4.White, 2000, Easing.OutQuint); - break; - - case TaikoAction.LeftRim: - case TaikoAction.RightRim: - outerCircle.FlashColour(Color4.White, 2000, Easing.OutQuint); - break; - } - trackedActions.Add(source, taikoAction); keyBindingContainer.TriggerPressed(taikoAction); } @@ -152,7 +146,7 @@ namespace osu.Game.Rulesets.Taiko.UI private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { - bool centreHit = centreCircle.Contains(inputPosition); + bool centreHit = leftCentre.Contains(inputPosition) || rightCentre.Contains(inputPosition); bool leftSide = ToLocalSpace(inputPosition).X < DrawWidth / 2; if (leftSide) @@ -160,5 +154,74 @@ namespace osu.Game.Rulesets.Taiko.UI return centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim; } + + protected override void PopIn() + { + mainContent.FadeIn(500, Easing.OutQuint); + } + + protected override void PopOut() + { + mainContent.FadeOut(300); + } + + private class QuarterCircle : CompositeDrawable, IKeyBindingHandler + { + private readonly Circle overlay; + + private readonly TaikoAction handledAction; + + private readonly Circle circle; + + public override bool Contains(Vector2 screenSpacePos) => circle.Contains(screenSpacePos); + + public QuarterCircle(TaikoAction handledAction, Color4 colour) + { + this.handledAction = handledAction; + RelativeSizeAxes = Axes.Both; + + FillMode = FillMode.Fit; + + InternalChildren = new Drawable[] + { + new Container + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + circle = new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = colour, + Alpha = 0.8f, + Scale = new Vector2(2), + }, + overlay = new Circle + { + Alpha = 0, + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, + Colour = colour, + Scale = new Vector2(2), + } + } + }, + }; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == handledAction) + overlay.FadeTo(0.4f, 80, Easing.OutQuint); + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + if (e.Action == handledAction) + overlay.FadeOut(1000, Easing.OutQuint); + } + } } } From 4279ac866c293291f6a0121684f414fa33d00108 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 17:58:52 +0900 Subject: [PATCH 779/803] Tidy up unnecessary changes and remove unused classes --- .../Skinning/Legacy/LegacyInputDrum.cs | 2 - osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs | 186 ------------------ 2 files changed, 188 deletions(-) delete mode 100644 osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index a03d987efd..101f70b97a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -35,8 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { Child = content = new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Size = new Vector2(180, 200), Children = new Drawable[] { diff --git a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs deleted file mode 100644 index 21b83e5495..0000000000 --- a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osuTK; - -namespace osu.Game.Rulesets.Taiko.UI -{ - /// - /// A component of the playfield that captures input and displays input as a drum. - /// - internal class TouchInputDrum : Container - { - public float CentreSize = 0.7f; - private const float middle_split = 0.025f; - - public TouchInputDrum() - { - RelativeSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Scale = new Vector2(0.9f), - Children = new Drawable[] - { - new TaikoHalfDrum(false, CentreSize) - { - Name = "Left Half", - Anchor = Anchor.Centre, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = -middle_split / 2, - RimAction = TaikoAction.LeftRim, - CentreAction = TaikoAction.LeftCentre - }, - new TaikoHalfDrum(true, CentreSize) - { - Name = "Right Half", - Anchor = Anchor.Centre, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = middle_split / 2, - RimAction = TaikoAction.RightRim, - CentreAction = TaikoAction.RightCentre - } - } - } - }; - } - - /// - /// A half-drum. Contains one centre and one rim hit. - /// - private class TaikoHalfDrum : Container, IKeyBindingHandler - { - /// - /// The key to be used for the rim of the half-drum. - /// - public TaikoAction RimAction; - - /// - /// The key to be used for the centre of the half-drum. - /// - public TaikoAction CentreAction; - - private readonly Sprite rim; - private readonly Sprite rimHit; - private readonly Sprite centre; - private readonly Sprite centreHit; - - public TaikoHalfDrum(bool flipped, float centreSize) - { - Masking = true; - - Children = new Drawable[] - { - rim = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both - }, - rimHit = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Blending = BlendingParameters.Additive, - }, - centre = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(centreSize) - }, - centreHit = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(centreSize), - Alpha = 0, - Blending = BlendingParameters.Additive - } - }; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures, OsuColour colours) - { - rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer"); - rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit"); - centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner"); - centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit"); - - rimHit.Colour = colours.Blue; - centreHit.Colour = colours.Pink; - } - - public bool OnPressed(KeyBindingPressEvent e) - { - Drawable target = null; - Drawable back = null; - - if (e.Action == CentreAction) - { - target = centreHit; - back = centre; - } - else if (e.Action == RimAction) - { - target = rimHit; - back = rim; - } - - if (target != null) - { - const float scale_amount = 0.05f; - const float alpha_amount = 0.5f; - - const float down_time = 40; - const float up_time = 1000; - - back.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint) - .Then() - .ScaleTo(1, up_time, Easing.OutQuint); - - target.Animate( - t => t.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint), - t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, Easing.OutQuint) - ).Then( - t => t.ScaleTo(1, up_time, Easing.OutQuint), - t => t.FadeOut(up_time, Easing.OutQuint) - ); - } - - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - } - } -} From ec98693ccaa7c34468ac8784d450352442415274 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 18:07:10 +0900 Subject: [PATCH 780/803] Add back standard mouse bindings support and only handle mouse when inside the visible zone --- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 ++ osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 9a5f5791ab..223e268d7f 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -50,6 +50,8 @@ namespace osu.Game.Rulesets.Taiko public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { + new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), + new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim), new KeyBinding(InputKey.D, TaikoAction.LeftRim), new KeyBinding(InputKey.F, TaikoAction.LeftCentre), new KeyBinding(InputKey.J, TaikoAction.RightCentre), diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 5ba2ea282e..8813cbbbc7 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Taiko.UI private QuarterCircle leftCentre = null!; private QuarterCircle rightCentre = null!; + private QuarterCircle leftRim = null!; + private QuarterCircle rightRim = null!; [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) @@ -62,13 +64,13 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new QuarterCircle(TaikoAction.LeftRim, colours.YellowDark) + leftRim = new QuarterCircle(TaikoAction.LeftRim, colours.YellowDark) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, X = -2, }, - new QuarterCircle(TaikoAction.RightRim, colours.YellowDark) + rightRim = new QuarterCircle(TaikoAction.RightRim, colours.YellowDark) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, @@ -106,12 +108,18 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnMouseDown(MouseDownEvent e) { + if (validMouse(e)) + return false; + handleDown(e.Button, e.ScreenSpaceMousePosition); return true; } protected override void OnMouseUp(MouseUpEvent e) { + if (validMouse(e)) + return; + handleUp(e.Button); base.OnMouseUp(e); } @@ -144,6 +152,10 @@ namespace osu.Game.Rulesets.Taiko.UI trackedActions.Remove(source); } + private bool validMouse(MouseButtonEvent e) => + !leftRim.Contains(e.ScreenSpaceMouseDownPosition) + && !rightRim.Contains(e.ScreenSpaceMouseDownPosition); + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { bool centreHit = leftCentre.Contains(inputPosition) || rightCentre.Contains(inputPosition); From 9e5e03af5d8e3c899a06bb7561702473a5d2c3fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 18:16:01 +0900 Subject: [PATCH 781/803] Adjust colours to match default skin for now --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 8813cbbbc7..fb22921acf 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -64,27 +65,27 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - leftRim = new QuarterCircle(TaikoAction.LeftRim, colours.YellowDark) + leftRim = new QuarterCircle(TaikoAction.LeftRim, colours.Blue) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, X = -2, }, - rightRim = new QuarterCircle(TaikoAction.RightRim, colours.YellowDark) + rightRim = new QuarterCircle(TaikoAction.RightRim, colours.Blue) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, X = 2, Rotation = 90, }, - leftCentre = new QuarterCircle(TaikoAction.LeftCentre, colours.BlueDark) + leftCentre = new QuarterCircle(TaikoAction.LeftCentre, colours.Pink) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, X = -2, Scale = new Vector2(centre_region), }, - rightCentre = new QuarterCircle(TaikoAction.RightCentre, colours.BlueDark) + rightCentre = new QuarterCircle(TaikoAction.RightCentre, colours.Pink) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, @@ -205,7 +206,7 @@ namespace osu.Game.Rulesets.Taiko.UI circle = new Circle { RelativeSizeAxes = Axes.Both, - Colour = colour, + Colour = colour.Multiply(1.4f).Darken(2.8f), Alpha = 0.8f, Scale = new Vector2(2), }, @@ -225,7 +226,7 @@ namespace osu.Game.Rulesets.Taiko.UI public bool OnPressed(KeyBindingPressEvent e) { if (e.Action == handledAction) - overlay.FadeTo(0.4f, 80, Easing.OutQuint); + overlay.FadeTo(1f, 80, Easing.OutQuint); return false; } From ee5e27638ee829df1f4c99e3061368d0387a01b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 18:19:33 +0900 Subject: [PATCH 782/803] Fix method name not matching actual implementation --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index fb22921acf..bf91ba7d49 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnMouseDown(MouseDownEvent e) { - if (validMouse(e)) + if (!validMouse(e)) return false; handleDown(e.Button, e.ScreenSpaceMousePosition); @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override void OnMouseUp(MouseUpEvent e) { - if (validMouse(e)) + if (!validMouse(e)) return; handleUp(e.Button); @@ -154,8 +154,7 @@ namespace osu.Game.Rulesets.Taiko.UI } private bool validMouse(MouseButtonEvent e) => - !leftRim.Contains(e.ScreenSpaceMouseDownPosition) - && !rightRim.Contains(e.ScreenSpaceMouseDownPosition); + leftRim.Contains(e.ScreenSpaceMouseDownPosition) || rightRim.Contains(e.ScreenSpaceMouseDownPosition); private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { From 28586c704dbf6e5a2b1bc43e8f93c13e3b8f72e5 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Fri, 22 Jul 2022 05:43:05 -0500 Subject: [PATCH 783/803] Add showModdedValue parameter to StatisticRow --- .../Screens/Select/Details/AdvancedStats.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 693f182065..80e9989c47 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Select.Details } } - public AdvancedStats() + public AdvancedStats(bool updateWithModSelection = true) { Child = new FillFlowContainer { @@ -65,11 +65,11 @@ namespace osu.Game.Screens.Select.Details AutoSizeAxes = Axes.Y, Children = new[] { - FirstValue = new StatisticRow(), // circle size/key amount - HpDrain = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsDrain }, - Accuracy = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAccuracy }, - ApproachRate = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAr }, - starDifficulty = new StatisticRow(10, true) { Title = BeatmapsetsStrings.ShowStatsStars }, + FirstValue = new StatisticRow(updateWithModSelection), // circle size/key amount + HpDrain = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsDrain }, + Accuracy = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsAccuracy }, + ApproachRate = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsAr }, + starDifficulty = new StatisticRow(updateWithModSelection, 10, true) { Title = BeatmapsetsStrings.ShowStatsStars }, }, }; } @@ -183,6 +183,7 @@ namespace osu.Game.Screens.Select.Details private readonly OsuSpriteText name, valueText; private readonly Bar bar; public readonly Bar ModBar; + private readonly bool showModdedValue; [Resolved] private OsuColour colours { get; set; } @@ -203,6 +204,9 @@ namespace osu.Game.Screens.Select.Details if (value == this.value) return; + if (!showModdedValue) + value.adjustedValue = null; + this.value = value; bar.Length = value.baseValue / maxValue; @@ -225,13 +229,14 @@ namespace osu.Game.Screens.Select.Details set => bar.AccentColour = value; } - public StatisticRow(float maxValue = 10, bool forceDecimalPlaces = false) + public StatisticRow(bool showModdedValue, float maxValue = 10, bool forceDecimalPlaces = false) { this.maxValue = maxValue; this.forceDecimalPlaces = forceDecimalPlaces; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Vertical = 2.5f }; + this.showModdedValue = showModdedValue; Children = new Drawable[] { From d9d35bb847a8f424c1379763019dbb0dc05c52a2 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Fri, 22 Jul 2022 05:43:56 -0500 Subject: [PATCH 784/803] Set BeatmapSetHeaderContent details to not show modded values --- osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs | 2 +- osu.Game/Overlays/BeatmapSet/Details.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index c9e97d5f2f..68f5f2be6e 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -222,7 +222,7 @@ namespace osu.Game.Overlays.BeatmapSet TextSize = 14, TextPadding = new MarginPadding { Horizontal = 35, Vertical = 10 } }, - Details = new Details(), + Details = new Details(false), }, }, } diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs index 6db54db811..092646f0ca 100644 --- a/osu.Game/Overlays/BeatmapSet/Details.cs +++ b/osu.Game/Overlays/BeatmapSet/Details.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.BeatmapSet ratingBox.Alpha = BeatmapSet?.Status > 0 ? 1 : 0; } - public Details() + public Details(bool updateWithModSelection = true) { Width = BeatmapSetOverlay.RIGHT_WIDTH; AutoSizeAxes = Axes.Y; @@ -82,7 +82,7 @@ namespace osu.Game.Overlays.BeatmapSet }, new DetailBox { - Child = advanced = new AdvancedStats + Child = advanced = new AdvancedStats(updateWithModSelection) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, From 7baa1a7e85a366478772a972f8e16e30aa9037ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 20:19:13 +0900 Subject: [PATCH 785/803] Attempt to fix crashing from weird input interactions --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index bf91ba7d49..a7d9bd18c5 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -143,6 +143,10 @@ namespace osu.Game.Rulesets.Taiko.UI TaikoAction taikoAction = getTaikoActionFromInput(position); + // Not too sure how this can happen, but let's avoid throwing. + if (trackedActions.ContainsKey(source)) + return; + trackedActions.Add(source, taikoAction); keyBindingContainer.TriggerPressed(taikoAction); } From 6ce8e74e6b75b8283921190344e25584b981c0df Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 22 Jul 2022 19:06:31 +0900 Subject: [PATCH 786/803] Add panel appearance sounds --- .../Expanded/ExpandedPanelTopContent.cs | 22 +++++++++++++-- osu.Game/Screens/Ranking/ResultsScreen.cs | 10 ++++++- osu.Game/Screens/Ranking/ScorePanel.cs | 27 ++++++++++++++++--- .../Ranking/Statistics/StatisticsPanel.cs | 27 ++++++++++++++++--- 4 files changed, 77 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelTopContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelTopContent.cs index d2b2a842b8..2708090855 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelTopContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelTopContent.cs @@ -4,6 +4,8 @@ #nullable disable using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -21,13 +23,19 @@ namespace osu.Game.Screens.Ranking.Expanded { private readonly APIUser user; + private Sample appearanceSample; + + private readonly bool playAppearanceSound; + /// /// Creates a new . /// /// The to display. - public ExpandedPanelTopContent(APIUser user) + /// Whether the appearance sample should play + public ExpandedPanelTopContent(APIUser user, bool playAppearanceSound = false) { this.user = user; + this.playAppearanceSound = playAppearanceSound; Anchor = Anchor.TopCentre; Origin = Anchor.Centre; @@ -35,8 +43,10 @@ namespace osu.Game.Screens.Ranking.Expanded } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { + appearanceSample = audio.Samples.Get(@"Results/score-panel-top-appear"); + InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -62,5 +72,13 @@ namespace osu.Game.Screens.Ranking.Expanded } }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (playAppearanceSound) + appearanceSample?.Play(); + } } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 2f8868c06d..c530febcae 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -7,6 +7,8 @@ using System; using System.Collections.Generic; using System.Linq; 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; @@ -60,6 +62,8 @@ namespace osu.Game.Screens.Ranking private readonly bool allowRetry; private readonly bool allowWatchingReplay; + private Sample popInSample; + protected ResultsScreen(ScoreInfo score, bool allowRetry, bool allowWatchingReplay = true) { Score = score; @@ -70,10 +74,12 @@ namespace osu.Game.Screens.Ranking } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { FillFlowContainer buttons; + popInSample = audio.Samples.Get(@"UI/overlay-pop-in"); + InternalChild = new GridContainer { RelativeSizeAxes = Axes.Both, @@ -244,6 +250,8 @@ namespace osu.Game.Screens.Ranking }); bottomPanel.FadeTo(1, 250); + + popInSample?.Play(); } public override bool OnExiting(ScreenExitEvent e) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index babdd4b149..322124313f 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -6,13 +6,16 @@ using System; using osu.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Contracted; using osu.Game.Screens.Ranking.Expanded; @@ -107,6 +110,8 @@ namespace osu.Game.Screens.Ranking private Container middleLayerContentContainer; private Drawable middleLayerContent; + private DrawableSample samplePanelFocus; + public ScorePanel(ScoreInfo score, bool isNewLocalScore = false) { Score = score; @@ -116,7 +121,7 @@ namespace osu.Game.Screens.Ranking } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { // ScorePanel doesn't include the top extruding area in its own size. // Adding a manual offset here allows the expanded version to take on an "acceptable" vertical centre when at 100% UI scale. @@ -174,7 +179,8 @@ namespace osu.Game.Screens.Ranking }, middleLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both } } - } + }, + samplePanelFocus = new DrawableSample(audio.Samples.Get(@"Results/score-panel-focus")) } }; } @@ -202,12 +208,26 @@ namespace osu.Game.Screens.Ranking state = value; if (IsLoaded) + { updateState(); + if (value == PanelState.Expanded) + playAppearSample(); + } + StateChanged?.Invoke(value); } } + private void playAppearSample() + { + var channel = samplePanelFocus?.GetChannel(); + if (channel == null) return; + + channel.Frequency.Value = 0.99 + RNG.NextDouble(0.2); + channel.Play(); + } + private void updateState() { topLayerContent?.FadeOut(content_fade_duration).Expire(); @@ -221,7 +241,8 @@ namespace osu.Game.Screens.Ranking topLayerBackground.FadeColour(expanded_top_layer_colour, RESIZE_DURATION, Easing.OutQuint); middleLayerBackground.FadeColour(expanded_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint); - topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User) { Alpha = 0 }); + bool firstLoad = topLayerContent == null; + topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User, firstLoad) { Alpha = 0 }); middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair) { Alpha = 0 }); // only the first expanded display should happen with flair. diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index ab68dec92d..435162e057 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -8,6 +8,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -35,6 +37,10 @@ namespace osu.Game.Screens.Ranking.Statistics private readonly Container content; private readonly LoadingSpinner spinner; + private bool wasOpened; + private Sample popInSample; + private Sample popOutSample; + public StatisticsPanel() { InternalChild = new Container @@ -56,9 +62,12 @@ namespace osu.Game.Screens.Ranking.Statistics } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { Score.BindValueChanged(populateStatistics, true); + + popInSample = audio.Samples.Get(@"Results/statistics-panel-pop-in"); + popOutSample = audio.Samples.Get(@"Results/statistics-panel-pop-out"); } private CancellationTokenSource loadCancellation; @@ -216,9 +225,21 @@ namespace osu.Game.Screens.Ranking.Statistics return true; } - protected override void PopIn() => this.FadeIn(150, Easing.OutQuint); + protected override void PopIn() + { + this.FadeIn(150, Easing.OutQuint); - protected override void PopOut() => this.FadeOut(150, Easing.OutQuint); + popInSample?.Play(); + wasOpened = true; + } + + protected override void PopOut() + { + this.FadeOut(150, Easing.OutQuint); + + if (wasOpened) + popOutSample?.Play(); + } protected override void Dispose(bool isDisposing) { From 89da21b6dee6856d690397f87f9c3fa4998e327f Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 22 Jul 2022 19:48:29 +0900 Subject: [PATCH 787/803] Add total score counter sfx --- .../Expanded/ExpandedPanelMiddleContent.cs | 2 +- .../Ranking/Expanded/TotalScoreCounter.cs | 62 ++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 5b8777d2a4..0f202e5e08 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -133,7 +133,7 @@ namespace osu.Game.Screens.Ranking.Expanded FillMode = FillMode.Fit, } }, - scoreCounter = new TotalScoreCounter + scoreCounter = new TotalScoreCounter(!withFlair) { Margin = new MarginPadding { Top = 0, Bottom = 5 }, Current = { Value = 0 }, diff --git a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs index 0b06dedc44..1e3443c2df 100644 --- a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs +++ b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs @@ -3,7 +3,11 @@ #nullable disable +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -22,11 +26,32 @@ namespace osu.Game.Screens.Ranking.Expanded protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING; - public TotalScoreCounter() + private readonly bool playSound; + private bool isTicking; + private readonly Bindable tickPlaybackRate = new Bindable(); + private double lastSampleTime; + private DrawableSample sampleTick; + + public TotalScoreCounter(bool playSound = false) { // Todo: AutoSize X removed here due to https://github.com/ppy/osu-framework/issues/3369 AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; + this.playSound = playSound; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + AddInternal(sampleTick = new DrawableSample(audio.Samples.Get(@"Results/score-tick-lesser"))); + lastSampleTime = Time.Current; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(startTickingPlayback); } protected override LocalisableString FormatCount(long count) => count.ToString("N0"); @@ -39,5 +64,40 @@ namespace osu.Game.Screens.Ranking.Expanded s.Font = OsuFont.Torus.With(size: 60, weight: FontWeight.Light, fixedWidth: true); s.Spacing = new Vector2(-5, 0); }); + + protected override void Update() + { + base.Update(); + + if (playSound && isTicking) playTickSample(); + } + + private void startTickingPlayback(ValueChangedEvent _) + { + const double tick_debounce_rate_start = 10f; + const double tick_debounce_rate_end = 100f; + const double tick_volume_start = 0.5f; + const double tick_volume_end = 1.0f; + double tickDuration = RollingDuration - AccuracyCircle.ACCURACY_TRANSFORM_DELAY - AccuracyCircle.RANK_CIRCLE_TRANSFORM_DELAY; + + this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_start); + this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_end, tickDuration, Easing.OutSine); + sampleTick.VolumeTo(tick_volume_start).Then().VolumeTo(tick_volume_end, tickDuration, Easing.OutSine); + + Scheduler.AddDelayed(stopTickingPlayback, tickDuration); + + isTicking = true; + } + + private void stopTickingPlayback() => isTicking = false; + + private void playTickSample() + { + if (Time.Current > lastSampleTime + tickPlaybackRate.Value) + { + sampleTick?.Play(); + lastSampleTime = Time.Current; + } + } } } From 62d4d4b05559334686737b59fb1667273e505a66 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 22 Jul 2022 19:52:42 +0900 Subject: [PATCH 788/803] Add dynamic panning fun to score panel sfx --- osu.Game/Screens/Ranking/ScorePanel.cs | 8 ++++---- osu.Game/Screens/Ranking/ScorePanelList.cs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 322124313f..aa4b732bb6 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -96,9 +96,9 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; - private bool displayWithFlair; + public DrawableAudioMixer Mixer; - private Container content; + private bool displayWithFlair; private Container topLayerContainer; private Drawable topLayerBackground; @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Ranking // Adding a manual offset here allows the expanded version to take on an "acceptable" vertical centre when at 100% UI scale. const float vertical_fudge = 20; - InternalChild = content = new Container + InternalChild = Mixer = new DrawableAudioMixer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -265,7 +265,7 @@ namespace osu.Game.Screens.Ranking break; } - content.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint); + Mixer.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint); bool topLayerExpanded = topLayerContainer.Y < 0; diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 4f9e61a4a1..d312953a38 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -344,6 +344,23 @@ namespace osu.Game.Screens.Ranking private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() .OrderByDescending(GetLayoutPosition) .ThenBy(s => s.Panel.Score.OnlineID); + + protected override void Update() + { + foreach (ScorePanelTrackingContainer trackingContainer in FlowingChildren.OfType()) + { + var panel = trackingContainer.Panel; + + if (panel.State != PanelState.Expanded) continue; + + var scrollContainer = Parent.Parent; + float balance = scrollContainer.ToLocalSpace(panel.ToScreenSpace(panel.BoundingBox.Centre)).X / scrollContainer.RelativeToAbsoluteFactor.X; + + panel.Mixer.Balance.Value = Math.Clamp(-1 + balance * 2, -1, 1); + } + + base.Update(); + } } private class Scroll : OsuScrollContainer From 9c0d79389639becc7491a9b6c3af832cd90c8605 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 00:04:11 +0900 Subject: [PATCH 789/803] 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 c83b7872ac..7c4582adf5 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 4fa4b804ab..ec0b392481 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 dc012ab2fa..73d219cb66 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From e08e4fc4260b8e5b075eef360ee4054527f436ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 00:04:14 +0900 Subject: [PATCH 790/803] 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 7c4582adf5..97fc97153c 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 ec0b392481..d95753179f 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 73d219cb66..5455c94998 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 9f045209b93f4cead222a89cbc7ed041dbb3abc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 02:09:08 +0900 Subject: [PATCH 791/803] Simplify score panel balance adjustment --- osu.Game/Screens/Ranking/ScorePanel.cs | 15 ++++++++++++--- osu.Game/Screens/Ranking/ScorePanelList.cs | 17 ----------------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index aa4b732bb6..9dcaafd9f9 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -96,7 +96,10 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; - public DrawableAudioMixer Mixer; + [Resolved] + private OsuGame game { get; set; } + + private DrawableAudioMixer mixer; private bool displayWithFlair; @@ -127,7 +130,7 @@ namespace osu.Game.Screens.Ranking // Adding a manual offset here allows the expanded version to take on an "acceptable" vertical centre when at 100% UI scale. const float vertical_fudge = 20; - InternalChild = Mixer = new DrawableAudioMixer + InternalChild = mixer = new DrawableAudioMixer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -219,6 +222,12 @@ namespace osu.Game.Screens.Ranking } } + protected override void Update() + { + base.Update(); + mixer.Balance.Value = (ScreenSpaceDrawQuad.Centre.X / game.ScreenSpaceDrawQuad.Width) * 2 - 1; + } + private void playAppearSample() { var channel = samplePanelFocus?.GetChannel(); @@ -265,7 +274,7 @@ namespace osu.Game.Screens.Ranking break; } - Mixer.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint); + mixer.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint); bool topLayerExpanded = topLayerContainer.Y < 0; diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index d312953a38..4f9e61a4a1 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -344,23 +344,6 @@ namespace osu.Game.Screens.Ranking private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() .OrderByDescending(GetLayoutPosition) .ThenBy(s => s.Panel.Score.OnlineID); - - protected override void Update() - { - foreach (ScorePanelTrackingContainer trackingContainer in FlowingChildren.OfType()) - { - var panel = trackingContainer.Panel; - - if (panel.State != PanelState.Expanded) continue; - - var scrollContainer = Parent.Parent; - float balance = scrollContainer.ToLocalSpace(panel.ToScreenSpace(panel.BoundingBox.Centre)).X / scrollContainer.RelativeToAbsoluteFactor.X; - - panel.Mixer.Balance.Value = Math.Clamp(-1 + balance * 2, -1, 1); - } - - base.Update(); - } } private class Scroll : OsuScrollContainer From f3ceabc53f074d610a3fad562c0f2620ee86a0ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Jul 2022 19:14:39 +0200 Subject: [PATCH 792/803] Rename `ModSelect{Overlay -> }Panel` --- osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs | 6 +++--- osu.Game/Overlays/Mods/ModColumn.cs | 10 +++++----- osu.Game/Overlays/Mods/ModPanel.cs | 2 +- osu.Game/Overlays/Mods/ModPresetPanel.cs | 2 +- .../{ModSelectOverlayPanel.cs => ModSelectPanel.cs} | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) rename osu.Game/Overlays/Mods/{ModSelectOverlayPanel.cs => ModSelectPanel.cs} (98%) diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs index c0a4cf2a25..255d01466f 100644 --- a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Masking = true, - CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, + CornerRadius = ModSelectPanel.CORNER_RADIUS, Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0), Children = new Drawable[] { @@ -69,7 +69,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Y, - Width = multiplier_value_area_width + ModSelectOverlayPanel.CORNER_RADIUS + Width = multiplier_value_area_width + ModSelectPanel.CORNER_RADIUS }, new GridContainer { @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Masking = true, - CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, + CornerRadius = ModSelectPanel.CORNER_RADIUS, Children = new Drawable[] { contentBackground = new Box diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 63a51aaad0..beb4856477 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -105,20 +105,20 @@ namespace osu.Game.Overlays.Mods TopLevelContent = new Container { RelativeSizeAxes = Axes.Both, - CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, + CornerRadius = ModSelectPanel.CORNER_RADIUS, Masking = true, Children = new Drawable[] { new Container { RelativeSizeAxes = Axes.X, - Height = header_height + ModSelectOverlayPanel.CORNER_RADIUS, + Height = header_height + ModSelectPanel.CORNER_RADIUS, Children = new Drawable[] { headerBackground = new Box { RelativeSizeAxes = Axes.X, - Height = header_height + ModSelectOverlayPanel.CORNER_RADIUS + Height = header_height + ModSelectPanel.CORNER_RADIUS }, headerText = new OsuTextFlowContainer(t => { @@ -135,7 +135,7 @@ namespace osu.Game.Overlays.Mods Padding = new MarginPadding { Horizontal = 17, - Bottom = ModSelectOverlayPanel.CORNER_RADIUS + Bottom = ModSelectPanel.CORNER_RADIUS } } } @@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Mods { RelativeSizeAxes = Axes.Both, Masking = true, - CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, + CornerRadius = ModSelectPanel.CORNER_RADIUS, BorderThickness = 3, Children = new Drawable[] { diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 5de6290248..6ef6ab0595 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -11,7 +11,7 @@ using osuTK; namespace osu.Game.Overlays.Mods { - public class ModPanel : ModSelectOverlayPanel + public class ModPanel : ModSelectPanel { public Mod Mod => modState.Mod; public override BindableBool Active => modState.Active; diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs index 39f092b91a..47e2f25538 100644 --- a/osu.Game/Overlays/Mods/ModPresetPanel.cs +++ b/osu.Game/Overlays/Mods/ModPresetPanel.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays.Mods { - public class ModPresetPanel : ModSelectOverlayPanel, IHasCustomTooltip + public class ModPresetPanel : ModSelectPanel, IHasCustomTooltip { public readonly ModPreset Preset; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs b/osu.Game/Overlays/Mods/ModSelectPanel.cs similarity index 98% rename from osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs rename to osu.Game/Overlays/Mods/ModSelectPanel.cs index a794884d7d..abf327a388 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs +++ b/osu.Game/Overlays/Mods/ModSelectPanel.cs @@ -24,7 +24,7 @@ using osuTK.Input; namespace osu.Game.Overlays.Mods { - public abstract class ModSelectOverlayPanel : OsuClickableContainer, IHasAccentColour + public abstract class ModSelectPanel : OsuClickableContainer, IHasAccentColour { public abstract BindableBool Active { get; } @@ -70,7 +70,7 @@ namespace osu.Game.Overlays.Mods private Sample? sampleOff; private Sample? sampleOn; - protected ModSelectOverlayPanel() + protected ModSelectPanel() { RelativeSizeAxes = Axes.X; Height = HEIGHT; From bb46ba66e03df7d0f102f3ff43ad1cb7a34b4be2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 02:25:37 +0900 Subject: [PATCH 793/803] Simplify `TotalScoreCounter` tick playback logic --- .../Ranking/Expanded/TotalScoreCounter.cs | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs index 1e3443c2df..33298063fc 100644 --- a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs +++ b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs @@ -26,32 +26,35 @@ namespace osu.Game.Screens.Ranking.Expanded protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING; - private readonly bool playSound; - private bool isTicking; + private readonly bool playSamples; + private readonly Bindable tickPlaybackRate = new Bindable(); + private double lastSampleTime; + private DrawableSample sampleTick; - public TotalScoreCounter(bool playSound = false) + public TotalScoreCounter(bool playSamples = false) { // Todo: AutoSize X removed here due to https://github.com/ppy/osu-framework/issues/3369 AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; - this.playSound = playSound; + + this.playSamples = playSamples; } [BackgroundDependencyLoader] private void load(AudioManager audio) { AddInternal(sampleTick = new DrawableSample(audio.Samples.Get(@"Results/score-tick-lesser"))); - lastSampleTime = Time.Current; } protected override void LoadComplete() { base.LoadComplete(); - Current.BindValueChanged(startTickingPlayback); + if (playSamples) + Current.BindValueChanged(_ => startTicking()); } protected override LocalisableString FormatCount(long count) => count.ToString("N0"); @@ -65,39 +68,36 @@ namespace osu.Game.Screens.Ranking.Expanded s.Spacing = new Vector2(-5, 0); }); - protected override void Update() + public override long DisplayedCount { - base.Update(); + get => base.DisplayedCount; + set + { + if (base.DisplayedCount == value) + return; - if (playSound && isTicking) playTickSample(); + base.DisplayedCount = value; + + if (playSamples && Time.Current > lastSampleTime + tickPlaybackRate.Value) + { + sampleTick?.Play(); + lastSampleTime = Time.Current; + } + } } - private void startTickingPlayback(ValueChangedEvent _) + private void startTicking() { const double tick_debounce_rate_start = 10f; const double tick_debounce_rate_end = 100f; const double tick_volume_start = 0.5f; const double tick_volume_end = 1.0f; - double tickDuration = RollingDuration - AccuracyCircle.ACCURACY_TRANSFORM_DELAY - AccuracyCircle.RANK_CIRCLE_TRANSFORM_DELAY; + + double tickDuration = RollingDuration; this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_start); this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_end, tickDuration, Easing.OutSine); sampleTick.VolumeTo(tick_volume_start).Then().VolumeTo(tick_volume_end, tickDuration, Easing.OutSine); - - Scheduler.AddDelayed(stopTickingPlayback, tickDuration); - - isTicking = true; - } - - private void stopTickingPlayback() => isTicking = false; - - private void playTickSample() - { - if (Time.Current > lastSampleTime + tickPlaybackRate.Value) - { - sampleTick?.Play(); - lastSampleTime = Time.Current; - } } } } From 475679ca66da4f34051d14c52b0f01b9e5704920 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 02:27:47 +0900 Subject: [PATCH 794/803] Fix DI failure --- osu.Game/Screens/Ranking/ScorePanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 9dcaafd9f9..0bcfa0da1f 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -97,7 +97,7 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; [Resolved] - private OsuGame game { get; set; } + private OsuGameBase game { get; set; } private DrawableAudioMixer mixer; From db632c0d6e5f40674d7e79136520b2ffd39145fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 02:38:00 +0900 Subject: [PATCH 795/803] Inline rolling duration --- osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs index 33298063fc..c7286a1838 100644 --- a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs +++ b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs @@ -93,11 +93,9 @@ namespace osu.Game.Screens.Ranking.Expanded const double tick_volume_start = 0.5f; const double tick_volume_end = 1.0f; - double tickDuration = RollingDuration; - this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_start); - this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_end, tickDuration, Easing.OutSine); - sampleTick.VolumeTo(tick_volume_start).Then().VolumeTo(tick_volume_end, tickDuration, Easing.OutSine); + this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_end, RollingDuration, Easing.OutSine); + sampleTick.VolumeTo(tick_volume_start).Then().VolumeTo(tick_volume_end, RollingDuration, Easing.OutSine); } } } From d451bc8fda5784c5574f6fe8e68f66f16d16ee49 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Fri, 22 Jul 2022 22:24:50 -0500 Subject: [PATCH 796/803] Revert commits This reverts commit 28586c704dbf6e5a2b1bc43e8f93c13e3b8f72e5. This reverts commit d9d35bb847a8f424c1379763019dbb0dc05c52a2. --- .../BeatmapSet/BeatmapSetHeaderContent.cs | 2 +- osu.Game/Overlays/BeatmapSet/Details.cs | 4 ++-- .../Screens/Select/Details/AdvancedStats.cs | 19 +++++++------------ 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 68f5f2be6e..c9e97d5f2f 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -222,7 +222,7 @@ namespace osu.Game.Overlays.BeatmapSet TextSize = 14, TextPadding = new MarginPadding { Horizontal = 35, Vertical = 10 } }, - Details = new Details(false), + Details = new Details(), }, }, } diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs index 092646f0ca..6db54db811 100644 --- a/osu.Game/Overlays/BeatmapSet/Details.cs +++ b/osu.Game/Overlays/BeatmapSet/Details.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.BeatmapSet ratingBox.Alpha = BeatmapSet?.Status > 0 ? 1 : 0; } - public Details(bool updateWithModSelection = true) + public Details() { Width = BeatmapSetOverlay.RIGHT_WIDTH; AutoSizeAxes = Axes.Y; @@ -82,7 +82,7 @@ namespace osu.Game.Overlays.BeatmapSet }, new DetailBox { - Child = advanced = new AdvancedStats(updateWithModSelection) + Child = advanced = new AdvancedStats { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 80e9989c47..693f182065 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Select.Details } } - public AdvancedStats(bool updateWithModSelection = true) + public AdvancedStats() { Child = new FillFlowContainer { @@ -65,11 +65,11 @@ namespace osu.Game.Screens.Select.Details AutoSizeAxes = Axes.Y, Children = new[] { - FirstValue = new StatisticRow(updateWithModSelection), // circle size/key amount - HpDrain = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsDrain }, - Accuracy = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsAccuracy }, - ApproachRate = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsAr }, - starDifficulty = new StatisticRow(updateWithModSelection, 10, true) { Title = BeatmapsetsStrings.ShowStatsStars }, + FirstValue = new StatisticRow(), // circle size/key amount + HpDrain = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsDrain }, + Accuracy = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAccuracy }, + ApproachRate = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAr }, + starDifficulty = new StatisticRow(10, true) { Title = BeatmapsetsStrings.ShowStatsStars }, }, }; } @@ -183,7 +183,6 @@ namespace osu.Game.Screens.Select.Details private readonly OsuSpriteText name, valueText; private readonly Bar bar; public readonly Bar ModBar; - private readonly bool showModdedValue; [Resolved] private OsuColour colours { get; set; } @@ -204,9 +203,6 @@ namespace osu.Game.Screens.Select.Details if (value == this.value) return; - if (!showModdedValue) - value.adjustedValue = null; - this.value = value; bar.Length = value.baseValue / maxValue; @@ -229,14 +225,13 @@ namespace osu.Game.Screens.Select.Details set => bar.AccentColour = value; } - public StatisticRow(bool showModdedValue, float maxValue = 10, bool forceDecimalPlaces = false) + public StatisticRow(float maxValue = 10, bool forceDecimalPlaces = false) { this.maxValue = maxValue; this.forceDecimalPlaces = forceDecimalPlaces; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Vertical = 2.5f }; - this.showModdedValue = showModdedValue; Children = new Drawable[] { From 06462c13dd7023441ad9e2288673417c1e349c31 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Fri, 22 Jul 2022 23:15:24 -0500 Subject: [PATCH 797/803] Overwrite IBindable> cache in BeatmapSetOverlay Implement fix as suggested --- osu.Game/Overlays/BeatmapSetOverlay.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 823d0023cf..a50a604b92 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -3,7 +3,10 @@ #nullable disable +using System; +using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -12,6 +15,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.Comments; +using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; @@ -25,6 +29,10 @@ namespace osu.Game.Overlays private readonly Bindable beatmapSet = new Bindable(); + [Cached] + [Cached(typeof(IBindable>))] + protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); + public BeatmapSetOverlay() : base(OverlayColourScheme.Blue) { From 661c79baf691c59bc0af3501dd3fe28d62a04028 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jul 2022 10:15:52 +0300 Subject: [PATCH 798/803] Add explanatory comment --- osu.Game/Overlays/BeatmapSetOverlay.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index a50a604b92..207dc91ca5 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -16,6 +16,7 @@ using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.Comments; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Select.Details; using osuTK; using osuTK.Graphics; @@ -29,6 +30,10 @@ namespace osu.Game.Overlays private readonly Bindable beatmapSet = new Bindable(); + /// + /// Isolates the beatmap set overlay from the game-wide selected mods bindable + /// to avoid affecting the beatmap details section (i.e. ). + /// [Cached] [Cached(typeof(IBindable>))] protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); From c937c0548eb1782041d52445896a098b8d0d300e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jul 2022 10:01:06 +0300 Subject: [PATCH 799/803] Add test coverage --- .../Online/TestSceneBeatmapSetOverlay.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 416e8aebcc..bb4823fb1d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -16,6 +16,10 @@ using osu.Framework.Testing; using osu.Game.Beatmaps.Drawables; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Scores; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Select.Details; using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Tests.Visual.Online @@ -34,6 +38,9 @@ namespace osu.Game.Tests.Visual.Online [Resolved] private IRulesetStore rulesets { get; set; } + [SetUp] + public void SetUp() => Schedule(() => SelectedMods.Value = Array.Empty()); + [Test] public void TestLoading() { @@ -205,6 +212,21 @@ namespace osu.Game.Tests.Visual.Online }); } + [Test] + public void TestSelectedModsDontAffectStatistics() + { + AddStep("show map", () => overlay.ShowBeatmapSet(getBeatmapSet())); + AddAssert("AR displayed as 0", () => overlay.ChildrenOfType().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value == (0, null)); + AddStep("set AR10 diff adjust", () => SelectedMods.Value = new[] + { + new OsuModDifficultyAdjust + { + ApproachRate = { Value = 10 } + } + }); + AddAssert("AR still displayed as 0", () => overlay.ChildrenOfType().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value == (0, null)); + } + [Test] public void TestHide() { From 840ad8fad2e8abe166d20a86a5ca4c5d7df7269c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jul 2022 11:10:59 +0300 Subject: [PATCH 800/803] Fix background beatmap processor resetting star ratings in tests --- osu.Game/Tests/Visual/OsuGameTestScene.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index b9f6183869..69a945db34 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -171,6 +171,11 @@ namespace osu.Game.Tests.Visual API.Login("Rhythm Champion", "osu!"); Dependencies.Get().SetValue(Static.MutedAudioNotificationShownOnce, true); + + // set applied version to latest so that the BackgroundBeatmapProcessor doesn't consider + // beatmap star ratings as outdated and reset them throughout the test. + foreach (var ruleset in RulesetStore.AvailableRulesets) + ruleset.LastAppliedDifficultyVersion = ruleset.CreateInstance().CreateDifficultyCalculator(Beatmap.Default).Version; } protected override void Update() From f1791e79e3e6cb607867304a1b560937f5876ba2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 19:21:12 +0900 Subject: [PATCH 801/803] Add error logging for background processing failures --- osu.Game/BackgroundBeatmapProcessor.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 6ecd8ca5c1..14fdb2e1ef 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -127,8 +127,15 @@ namespace osu.Game if (set != null) { - Logger.Log($"Background processing {set} ({++i} / {beatmapSetIds.Count})"); - beatmapUpdater.Process(set); + try + { + Logger.Log($"Background processing {set} ({++i} / {beatmapSetIds.Count})"); + beatmapUpdater.Process(set); + } + catch (Exception e) + { + Logger.Log($"Background processing failed on {set}: {e}"); + } } }); } From 0c16ef3e2ed0215ac03c51a1fd7743273f695fbb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 24 Jul 2022 08:33:35 +0300 Subject: [PATCH 802/803] Add failing test case --- .../TestSceneLabelledSliderBar.cs | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs index 8ccfe2ee9c..e5f3aea2f7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs @@ -8,8 +8,10 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; @@ -17,11 +19,26 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneLabelledSliderBar : OsuTestScene { - [TestCase(false)] - [TestCase(true)] - public void TestSliderBar(bool hasDescription) => createSliderBar(hasDescription); + [Test] + public void TestBasic() => createSliderBar(); - private void createSliderBar(bool hasDescription = false) + [Test] + public void TestDescription() + { + createSliderBar(); + AddStep("set description", () => this.ChildrenOfType>().ForEach(l => l.Description = "this text describes the component")); + } + + [Test] + public void TestSize() + { + createSliderBar(); + AddStep("set zero width", () => this.ChildrenOfType>().ForEach(l => l.ResizeWidthTo(0, 200, Easing.OutQuint))); + AddStep("set negative width", () => this.ChildrenOfType>().ForEach(l => l.ResizeWidthTo(-1, 200, Easing.OutQuint))); + AddStep("revert back", () => this.ChildrenOfType>().ForEach(l => l.ResizeWidthTo(1, 200, Easing.OutQuint))); + } + + private void createSliderBar() { AddStep("create component", () => { @@ -38,6 +55,8 @@ namespace osu.Game.Tests.Visual.UserInterface { new LabelledSliderBar { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Current = new BindableDouble(5) { MinValue = 0, @@ -45,7 +64,6 @@ namespace osu.Game.Tests.Visual.UserInterface Precision = 1, }, Label = "a sample component", - Description = hasDescription ? "this text describes the component" : string.Empty, }, }, }; @@ -54,10 +72,14 @@ namespace osu.Game.Tests.Visual.UserInterface { flow.Add(new OverlayColourContainer(colour) { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Child = new LabelledSliderBar { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Current = new BindableDouble(5) { MinValue = 0, @@ -65,7 +87,6 @@ namespace osu.Game.Tests.Visual.UserInterface Precision = 1, }, Label = "a sample component", - Description = hasDescription ? "this text describes the component" : string.Empty, } }); } From 4332e6cae90980396482edbe6309259bc6096c42 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 24 Jul 2022 08:34:05 +0300 Subject: [PATCH 803/803] Fix `OsuSliderBar` throwing on negative draw width --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index c48627bd21..2a8b41fd20 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -228,10 +228,8 @@ namespace osu.Game.Graphics.UserInterface protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - LeftBox.Scale = new Vector2(Math.Clamp( - RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); - RightBox.Scale = new Vector2(Math.Clamp( - DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, DrawWidth), 1); + LeftBox.Scale = new Vector2(Math.Clamp(RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, Math.Max(0, DrawWidth)), 1); + RightBox.Scale = new Vector2(Math.Clamp(DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, Math.Max(0, DrawWidth)), 1); } protected override void UpdateValue(float value)