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/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-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs
index 7dcdac7019..0522840e9e 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs
@@ -4,8 +4,6 @@
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
@@ -50,9 +48,6 @@ namespace osu.Game.Rulesets.Pippidon
new KeyBinding(InputKey.X, PippidonAction.Button2),
};
- public override Drawable CreateIcon() => new Sprite
- {
- Texture = new TextureStore(new TextureLoaderStore(CreateResourceStore()), false).Get("Textures/coin"),
- };
+ public override Drawable CreateIcon() => new PippidonRulesetIcon(this);
}
}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonRulesetIcon.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonRulesetIcon.cs
new file mode 100644
index 0000000000..ff10233b50
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonRulesetIcon.cs
@@ -0,0 +1,26 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.Rendering;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+
+namespace osu.Game.Rulesets.Pippidon
+{
+ public class PippidonRulesetIcon : Sprite
+ {
+ private readonly Ruleset ruleset;
+
+ public PippidonRulesetIcon(Ruleset ruleset)
+ {
+ this.ruleset = ruleset;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IRenderer renderer)
+ {
+ Texture = new TextureStore(renderer, new TextureLoaderStore(ruleset.CreateResourceStore()), false).Get("Textures/coin");
+ }
+ }
+}
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/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs
index d4566477db..89246373ee 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs
@@ -4,8 +4,6 @@
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
@@ -47,10 +45,6 @@ namespace osu.Game.Rulesets.Pippidon
new KeyBinding(InputKey.S, PippidonAction.MoveDown),
};
- public override Drawable CreateIcon() => new Sprite
- {
- Margin = new MarginPadding { Top = 3 },
- Texture = new TextureStore(new TextureLoaderStore(CreateResourceStore()), false).Get("Textures/coin"),
- };
+ public override Drawable CreateIcon() => new PippidonRulesetIcon(this);
}
}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRulesetIcon.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRulesetIcon.cs
new file mode 100644
index 0000000000..6b87d93951
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRulesetIcon.cs
@@ -0,0 +1,29 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Rendering;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+
+namespace osu.Game.Rulesets.Pippidon
+{
+ public class PippidonRulesetIcon : Sprite
+ {
+ private readonly Ruleset ruleset;
+
+ public PippidonRulesetIcon(Ruleset ruleset)
+ {
+ this.ruleset = ruleset;
+
+ Margin = new MarginPadding { Top = 3 };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IRenderer renderer)
+ {
+ Texture = new TextureStore(renderer, new TextureLoaderStore(ruleset.CreateResourceStore()), false).Get("Textures/coin");
+ }
+ }
+}
diff --git a/osu.Android.props b/osu.Android.props
index ceea60a1c1..247140ceef 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,8 +51,8 @@
-
-
+
+
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..6b88f21bcd 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);
}
@@ -103,9 +106,9 @@ namespace osu.Android
private class AndroidBatteryInfo : BatteryInfo
{
- public override double ChargeLevel => Battery.ChargeLevel;
+ public override double? ChargeLevel => Battery.ChargeLevel;
- public override bool IsCharging => Battery.PowerSource != BatteryPowerSource.Battery;
+ public override bool OnBattery => Battery.PowerSource == BatteryPowerSource.Battery;
}
}
}
diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj
index 1da467cd24..a83bdbb9ff 100644
--- a/osu.Android/osu.Android.csproj
+++ b/osu.Android/osu.Android.csproj
@@ -27,6 +27,7 @@
true
+
diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs
index d0b6953c30..9cf68d88d9 100644
--- a/osu.Desktop/DiscordRichPresence.cs
+++ b/osu.Desktop/DiscordRichPresence.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.Text;
using DiscordRPC;
@@ -26,15 +24,15 @@ namespace osu.Desktop
{
private const string client_id = "367827983903490050";
- private DiscordRpcClient client;
+ private DiscordRpcClient client = null!;
[Resolved]
- private IBindable ruleset { get; set; }
+ private IBindable ruleset { get; set; } = null!;
- private IBindable user;
+ private IBindable user = null!;
[Resolved]
- private IAPIProvider api { get; set; }
+ private IAPIProvider api { get; set; } = null!;
private readonly IBindable status = new Bindable();
private readonly IBindable activity = new Bindable();
@@ -130,7 +128,7 @@ namespace osu.Desktop
presence.Assets.LargeImageText = string.Empty;
else
{
- if (user.Value.RulesetsStatistics != null && user.Value.RulesetsStatistics.TryGetValue(ruleset.Value.ShortName, out UserStatistics statistics))
+ if (user.Value.RulesetsStatistics != null && user.Value.RulesetsStatistics.TryGetValue(ruleset.Value.ShortName, out UserStatistics? statistics))
presence.Assets.LargeImageText = $"{user.Value.Username}" + (statistics.GlobalRank > 0 ? $" (rank #{statistics.GlobalRank:N0})" : string.Empty);
else
presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty);
@@ -164,7 +162,7 @@ namespace osu.Desktop
});
}
- private IBeatmapInfo getBeatmap(UserActivity activity)
+ private IBeatmapInfo? getBeatmap(UserActivity activity)
{
switch (activity)
{
@@ -183,10 +181,10 @@ namespace osu.Desktop
switch (activity)
{
case UserActivity.InGame game:
- return game.BeatmapInfo.ToString();
+ return game.BeatmapInfo.ToString() ?? string.Empty;
case UserActivity.Editing edit:
- return edit.BeatmapInfo.ToString();
+ return edit.BeatmapInfo.ToString() ?? string.Empty;
case UserActivity.InLobby lobby:
return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
diff --git a/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationRequest.cs b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationRequest.cs
index 7b0bd69363..0ad68919a2 100644
--- a/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationRequest.cs
+++ b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationRequest.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.Desktop.LegacyIpc
{
///
@@ -13,7 +11,7 @@ namespace osu.Desktop.LegacyIpc
///
public class LegacyIpcDifficultyCalculationRequest
{
- public string BeatmapFile { get; set; }
+ public string BeatmapFile { get; set; } = string.Empty;
public int RulesetId { get; set; }
public int Mods { get; set; }
}
diff --git a/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationResponse.cs b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationResponse.cs
index 6d36cbc4b6..7b9fae5797 100644
--- a/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationResponse.cs
+++ b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationResponse.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.Desktop.LegacyIpc
{
///
diff --git a/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs b/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs
index 4df477191d..8d0add32d1 100644
--- a/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs
+++ b/osu.Desktop/LegacyIpc/LegacyIpcMessage.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.Platform;
using Newtonsoft.Json.Linq;
@@ -39,17 +37,20 @@ namespace osu.Desktop.LegacyIpc
public new object Value
{
get => base.Value;
- set => base.Value = new Data
- {
- MessageType = value.GetType().Name,
- MessageData = value
- };
+ set => base.Value = new Data(value.GetType().Name, value);
}
public class Data
{
- public string MessageType { get; set; }
- public object MessageData { get; set; }
+ public string MessageType { get; }
+
+ public object MessageData { get; }
+
+ public Data(string messageType, object messageData)
+ {
+ MessageType = messageType;
+ MessageData = messageData;
+ }
}
}
}
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 314a03a73e..d9ad95f96a 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -22,11 +22,15 @@ 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;
+using osu.Game.Utils;
+using SDL2;
namespace osu.Desktop
{
@@ -156,11 +160,16 @@ namespace osu.Desktop
case JoystickHandler jh:
return new JoystickSettings(jh);
+ case TouchHandler th:
+ return new InputSection.HandlerSection(th);
+
default:
return base.CreateSettingsSubsectionFor(handler);
}
}
+ protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
+
private readonly List importableFiles = new List();
private ScheduledDelegate? importSchedule;
@@ -201,5 +210,23 @@ namespace osu.Desktop
base.Dispose(isDisposing);
osuSchemeLinkIPCChannel?.Dispose();
}
+
+ private class SDL2BatteryInfo : BatteryInfo
+ {
+ public override double? ChargeLevel
+ {
+ get
+ {
+ SDL.SDL_GetPowerInfo(out _, out int percentage);
+
+ if (percentage == -1)
+ return null;
+
+ return percentage / 100.0;
+ }
+ }
+
+ public override bool OnBattery => SDL.SDL_GetPowerInfo(out _, out _) == SDL.SDL_PowerState.SDL_POWERSTATE_ON_BATTERY;
+ }
}
}
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 712f300671..5a1373e040 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/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 System;
using System.IO;
using System.Runtime.Versioning;
@@ -14,22 +12,47 @@ using osu.Framework.Platform;
using osu.Game;
using osu.Game.IPC;
using osu.Game.Tournament;
+using SDL2;
using Squirrel;
namespace osu.Desktop
{
public static class Program
{
+#if DEBUG
+ private const string base_game_name = @"osu-development";
+#else
private const string base_game_name = @"osu";
+#endif
- private static LegacyTcpIpcProvider legacyIpc;
+ private static LegacyTcpIpcProvider? legacyIpc;
[STAThread]
public static void Main(string[] args)
{
// run Squirrel first, as the app may exit after these run
if (OperatingSystem.IsWindows())
+ {
+ var windowsVersion = Environment.OSVersion.Version;
+
+ // 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))
+ {
+ // 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.\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;
+ }
+
setupSquirrel();
+ }
// Back up the cwd before DesktopGameHost changes it
string cwd = Environment.CurrentDirectory;
diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs
index f0d95ba194..9959b24b35 100644
--- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs
+++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.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.Security.Principal;
using osu.Framework;
@@ -21,7 +19,7 @@ namespace osu.Desktop.Security
public class ElevatedPrivilegesChecker : Component
{
[Resolved]
- private INotificationOverlay notifications { get; set; }
+ private INotificationOverlay notifications { get; set; } = null!;
private bool elevated;
diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs
index 4e5f8d37b1..d53db6c516 100644
--- a/osu.Desktop/Updater/SquirrelUpdateManager.cs
+++ b/osu.Desktop/Updater/SquirrelUpdateManager.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.Runtime.Versioning;
using System.Threading.Tasks;
@@ -26,8 +24,8 @@ namespace osu.Desktop.Updater
[SupportedOSPlatform("windows")]
public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager
{
- private UpdateManager updateManager;
- private INotificationOverlay notificationOverlay;
+ private UpdateManager? updateManager;
+ private INotificationOverlay notificationOverlay = null!;
public Task PrepareUpdateAsync() => UpdateManager.RestartAppWhenExited();
@@ -50,12 +48,12 @@ namespace osu.Desktop.Updater
protected override async Task PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false);
- private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
+ private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification? notification = null)
{
// should we schedule a retry on completion of this check?
bool scheduleRecheck = true;
- const string github_token = null; // TODO: populate.
+ const string? github_token = null; // TODO: populate.
try
{
@@ -145,7 +143,7 @@ namespace osu.Desktop.Updater
private class UpdateCompleteNotification : ProgressCompletionNotification
{
[Resolved]
- private OsuGame game { get; set; }
+ private OsuGame game { get; set; } = null!;
public UpdateCompleteNotification(SquirrelUpdateManager updateManager)
{
@@ -154,7 +152,7 @@ namespace osu.Desktop.Updater
Activated = () =>
{
updateManager.PrepareUpdateAsync()
- .ContinueWith(_ => updateManager.Schedule(() => game?.AttemptExit()));
+ .ContinueWith(_ => updateManager.Schedule(() => game.AttemptExit()));
return true;
};
}
diff --git a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs
index 0cb4ba9c04..284d25306d 100644
--- a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs
+++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.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;
@@ -14,12 +12,12 @@ namespace osu.Desktop.Windows
{
public class GameplayWinKeyBlocker : Component
{
- private Bindable disableWinKey;
- private IBindable localUserPlaying;
- private IBindable isActive;
+ private Bindable disableWinKey = null!;
+ private IBindable localUserPlaying = null!;
+ private IBindable isActive = null!;
[Resolved]
- private GameHost host { get; set; }
+ private GameHost host { get; set; } = null!;
[BackgroundDependencyLoader]
private void load(ILocalUserPlayInfo localUserInfo, OsuConfigManager config)
diff --git a/osu.Desktop/Windows/WindowsKey.cs b/osu.Desktop/Windows/WindowsKey.cs
index c69cce6200..1051e61f2f 100644
--- a/osu.Desktop/Windows/WindowsKey.cs
+++ b/osu.Desktop/Windows/WindowsKey.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.Runtime.InteropServices;
@@ -21,7 +19,7 @@ namespace osu.Desktop.Windows
private const int wm_syskeyup = 261;
//Resharper disable once NotAccessedField.Local
- private static LowLevelKeyboardProcDelegate keyboardHookDelegate; // keeping a reference alive for the GC
+ private static LowLevelKeyboardProcDelegate? keyboardHookDelegate; // keeping a reference alive for the GC
private static IntPtr keyHook;
[StructLayout(LayoutKind.Explicit)]
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 0c7076e004..060c42b3d4 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -21,7 +21,7 @@
-
+
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/BenchmarkHitObject.cs b/osu.Game.Benchmarks/BenchmarkHitObject.cs
new file mode 100644
index 0000000000..65c78e39b3
--- /dev/null
+++ b/osu.Game.Benchmarks/BenchmarkHitObject.cs
@@ -0,0 +1,166 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using 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;
+ }
+ 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;
+ }
+
+ [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;
+ }
+ else
+ {
+ _ = hits[i].Type;
+ _ = hits[i].IsStrong;
+ _ = hits[i].Samples;
+ _ = hits[i].StartTime;
+ }
+ }
+
+ 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;
+ }
+ 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;
+ }
+
+ [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;
+ }
+ else
+ {
+ _ = notes[i].Column;
+ _ = notes[i].Samples;
+ _ = notes[i].StartTime;
+ }
+ }
+
+ return notes;
+ }
+ }
+}
diff --git a/osu.Game.Benchmarks/BenchmarkMod.cs b/osu.Game.Benchmarks/BenchmarkMod.cs
index a1d92d9a67..994300df36 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;
@@ -11,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 5ffda6504e..1df77320d2 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;
@@ -17,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; }
@@ -135,9 +133,9 @@ namespace osu.Game.Benchmarks
[GlobalCleanup]
public void Cleanup()
{
- realm?.Dispose();
- storage?.Dispose();
- updateThread?.Exit();
+ realm.Dispose();
+ storage.Dispose();
+ updateThread.Exit();
}
}
}
diff --git a/osu.Game.Benchmarks/BenchmarkRuleset.cs b/osu.Game.Benchmarks/BenchmarkRuleset.cs
index de8cb13773..7d318e043b 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;
@@ -13,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()
{
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;
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs
new file mode 100644
index 0000000000..ff557638a9
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.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.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.UI;
+
+namespace osu.Game.Rulesets.Mania.Tests.Skinning
+{
+ public class TestSceneBarLine : ManiaSkinnableTestScene
+ {
+ [Test]
+ public void TestMinor()
+ {
+ AddStep("Create barlines", () => recreate());
+ }
+
+ private void recreate(Func>? createBarLines = null)
+ {
+ var stageDefinitions = new List
+ {
+ new StageDefinition { Columns = 4 },
+ };
+
+ SetContents(_ => new ManiaPlayfield(stageDefinitions).With(s =>
+ {
+ if (createBarLines != null)
+ {
+ var barLines = createBarLines();
+
+ foreach (var b in barLines)
+ s.Add(b);
+
+ return;
+ }
+
+ for (int i = 0; i < 64; i++)
+ {
+ s.Add(new BarLine
+ {
+ StartTime = Time.Current + i * 500,
+ Major = i % 4 == 0,
+ });
+ }
+ }));
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs b/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs
index 7c4ab2f5f4..b6c8103e3c 100644
--- a/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs
@@ -6,7 +6,8 @@ using System.Diagnostics;
using System.Linq;
using Moq;
using NUnit.Framework;
-using osu.Framework.Graphics.OpenGL.Textures;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
@@ -19,6 +20,9 @@ namespace osu.Game.Rulesets.Osu.Tests
[HeadlessTest]
public class LegacyMainCirclePieceTest : OsuTestScene
{
+ [Resolved]
+ private IRenderer renderer { get; set; } = null!;
+
private static readonly object?[][] texture_priority_cases =
{
// default priority lookup
@@ -76,7 +80,12 @@ 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 _) =>
+ {
+ var tex = renderer.CreateTexture(1, 1);
+ tex.AssetName = componentName;
+ return tex;
+ });
Child = new DependencyProvidingContainer
{
@@ -84,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Child = piece = new TestLegacyMainCirclePiece(priorityLookup),
};
- var sprites = this.ChildrenOfType().Where(s => s.Texture.AssetName != null).DistinctBy(s => s.Texture.AssetName).ToArray();
+ var sprites = this.ChildrenOfType().Where(s => !string.IsNullOrEmpty(s.Texture.AssetName)).DistinctBy(s => s.Texture.AssetName).ToArray();
Debug.Assert(sprites.Length <= 2);
});
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs b/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs
index 4f005a0c70..d3cb3bcf59 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.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.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests.Mods
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs
index 3d59e4fb51..5e46498aca 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.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.Tests/Mods/TestSceneOsuModAutoplay.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs
index 378b71ccf7..3563995234 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs
@@ -1,10 +1,9 @@
// 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.Extensions.ObjectExtensions;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
@@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
private void runSpmTest(Mod mod)
{
- SpinnerSpmCalculator spmCalculator = null;
+ SpinnerSpmCalculator? spmCalculator = null;
CreateModTest(new ModTestData
{
@@ -61,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
return spmCalculator != null;
});
- AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCalculator.Result.Value, 477, 5));
+ AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCalculator.AsNonNull().Result.Value, 477, 5));
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
index 80dc83d7dc..9d06ff5801 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.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;
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs
index e1bed5153b..8df8afe147 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.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.Utils;
using osu.Game.Rulesets.Osu.Mods;
@@ -24,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
{
Mod = mod,
PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 &&
- Precision.AlmostEquals(Player.GameplayClockContainer.GameplayClock.Rate, mod.SpeedChange.Value)
+ Precision.AlmostEquals(Player.GameplayClockContainer.Rate, mod.SpeedChange.Value)
});
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
index 5ed25baca3..e692f8ecbc 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.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;
@@ -162,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
private class TestOsuModHidden : OsuModHidden
{
- public new HitObject FirstObject => base.FirstObject;
+ public new HitObject? FirstObject => base.FirstObject;
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs
index 1f1db04c24..9b49e60363 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.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.Osu.Mods;
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs
index 99c9036ac0..68669d1a53 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.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.Testing;
@@ -33,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
[Test]
public void TestModCopy()
{
- OsuModMuted muted = null;
+ OsuModMuted muted = null!;
AddStep("create inversed mod", () => muted = new OsuModMuted
{
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs
index 47e7ad320c..44404ca245 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.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;
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs
index b7669624ff..985baa8cf5 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.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.Objects;
using osu.Game.Rulesets.Objects.Types;
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.Tests/Mods/TestSceneOsuModSingleTap.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs
new file mode 100644
index 0000000000..1aed84be10
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs
@@ -0,0 +1,175 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.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)),
+ }
+ });
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs
index 4f6d6376bf..e121e6103d 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.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;
@@ -30,8 +28,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
[Test]
public void TestSpinnerAutoCompleted()
{
- DrawableSpinner spinner = null;
- JudgementResult lastResult = null;
+ DrawableSpinner? spinner = null;
+ JudgementResult? lastResult = null;
CreateModTest(new ModTestData
{
@@ -63,11 +61,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
[TestCase(null)]
[TestCase(typeof(OsuModDoubleTime))]
[TestCase(typeof(OsuModHalfTime))]
- public void TestSpinRateUnaffectedByMods(Type additionalModType)
+ public void TestSpinRateUnaffectedByMods(Type? additionalModType)
{
var mods = new List { new OsuModSpunOut() };
if (additionalModType != null)
- mods.Add((Mod)Activator.CreateInstance(additionalModType));
+ mods.Add((Mod)Activator.CreateInstance(additionalModType)!);
CreateModTest(new ModTestData
{
@@ -96,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
[Test]
public void TestSpinnerGetsNoBonusScore()
{
- DrawableSpinner spinner = null;
+ DrawableSpinner? spinner = null;
List results = new List();
CreateModTest(new ModTestData
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
index bb593c2fb3..7e995f2dde 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.7115569159190587d, 206, "diffcalc-test")]
+ [TestCase(1.4391311903612753d, 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.9757300665532966d, 206, "diffcalc-test")]
+ [TestCase(1.7437232654020756d, 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.7115569159190587d, 239, "diffcalc-test")]
+ [TestCase(1.4391311903612753d, 54, "zero-length-sliders")]
public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
index c8d2d07be5..d3e70a0a01 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
@@ -11,7 +11,7 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.OpenGL.Textures;
+using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing.Input;
using osu.Game.Audio;
@@ -25,6 +25,9 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneCursorTrail : OsuTestScene
{
+ [Resolved]
+ private IRenderer renderer { get; set; }
+
[Test]
public void TestSmoothCursorTrail()
{
@@ -44,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
createTest(() =>
{
- var skinContainer = new LegacySkinContainer(false);
+ var skinContainer = new LegacySkinContainer(renderer, false);
var legacyCursorTrail = new LegacyCursorTrail(skinContainer);
skinContainer.Child = legacyCursorTrail;
@@ -58,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
createTest(() =>
{
- var skinContainer = new LegacySkinContainer(true);
+ var skinContainer = new LegacySkinContainer(renderer, true);
var legacyCursorTrail = new LegacyCursorTrail(skinContainer);
skinContainer.Child = legacyCursorTrail;
@@ -82,10 +85,12 @@ namespace osu.Game.Rulesets.Osu.Tests
[Cached(typeof(ISkinSource))]
private class LegacySkinContainer : Container, ISkinSource
{
+ private readonly IRenderer renderer;
private readonly bool disjoint;
- public LegacySkinContainer(bool disjoint)
+ public LegacySkinContainer(IRenderer renderer, bool disjoint)
{
+ this.renderer = renderer;
this.disjoint = disjoint;
RelativeSizeAxes = Axes.Both;
@@ -98,14 +103,14 @@ namespace osu.Game.Rulesets.Osu.Tests
switch (componentName)
{
case "cursortrail":
- var tex = new Texture(Texture.WhitePixel.TextureGL);
+ var tex = new Texture(renderer.WhitePixel);
if (disjoint)
tex.ScaleAdjust = 1 / 25f;
return tex;
case "cursormiddle":
- return disjoint ? null : Texture.WhitePixel;
+ return disjoint ? null : renderer.WhitePixel;
}
return null;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
index 2edacee9ac..360815afbf 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
@@ -10,7 +10,6 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
index dd110662b2..036f90b962 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
@@ -12,7 +12,6 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Timing;
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.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
index d1796f2231..b7f91c22f4 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.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;
@@ -12,7 +10,6 @@ using osu.Framework.Audio;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Framework.Timing;
-using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Replays;
using osu.Game.Rulesets.Objects;
@@ -36,16 +33,16 @@ namespace osu.Game.Rulesets.Osu.Tests
private const double spinner_duration = 6000;
[Resolved]
- private AudioManager audioManager { get; set; }
+ private AudioManager audioManager { get; set; } = null!;
protected override bool Autoplay => true;
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new ScoreExposedPlayer();
- protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null)
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
- private DrawableSpinner drawableSpinner;
+ private DrawableSpinner drawableSpinner = null!;
private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType().Single();
[SetUpSteps]
@@ -67,12 +64,12 @@ namespace osu.Game.Rulesets.Osu.Tests
{
trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f);
});
- AddAssert("is disc rotation not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, 100));
- AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, 0, 100));
+ AddAssert("is disc rotation not almost 0", () => drawableSpinner.RotationTracker.Rotation, () => Is.Not.EqualTo(0).Within(100));
+ AddAssert("is disc rotation absolute not almost 0", () => drawableSpinner.Result.RateAdjustedRotation, () => Is.Not.EqualTo(0).Within(100));
addSeekStep(0);
- AddAssert("is disc rotation almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, trackerRotationTolerance));
- AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, 0, 100));
+ AddAssert("is disc rotation almost 0", () => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(0).Within(trackerRotationTolerance));
+ AddAssert("is disc rotation absolute almost 0", () => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(0).Within(100));
}
[Test]
@@ -100,20 +97,20 @@ namespace osu.Game.Rulesets.Osu.Tests
// we want to make sure that the rotation at time 2500 is in the same direction as at time 5000, but about half-way in.
// due to the exponential damping applied we're allowing a larger margin of error of about 10%
// (5% relative to the final rotation value, but we're half-way through the spin).
- () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalTrackerRotation / 2, trackerRotationTolerance));
+ () => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation / 2).Within(trackerRotationTolerance));
AddAssert("symbol rotation rewound",
- () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation / 2, spinnerSymbolRotationTolerance));
+ () => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation / 2).Within(spinnerSymbolRotationTolerance));
AddAssert("is cumulative rotation rewound",
// cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error.
- () => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, finalCumulativeTrackerRotation / 2, 100));
+ () => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation / 2).Within(100));
addSeekStep(spinner_start_time + 5000);
AddAssert("is disc rotation almost same",
- () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalTrackerRotation, trackerRotationTolerance));
+ () => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation).Within(trackerRotationTolerance));
AddAssert("is symbol rotation almost same",
- () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation, spinnerSymbolRotationTolerance));
+ () => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation).Within(spinnerSymbolRotationTolerance));
AddAssert("is cumulative rotation almost same",
- () => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, finalCumulativeTrackerRotation, 100));
+ () => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation).Within(100));
}
[Test]
@@ -177,10 +174,10 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpinsPerMinute.Value);
addSeekStep(2000);
- AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpinsPerMinute.Value, estimatedSpm, 1.0));
+ AddAssert("spm still valid", () => drawableSpinner.SpinsPerMinute.Value, () => Is.EqualTo(estimatedSpm).Within(1.0));
addSeekStep(1000);
- AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpinsPerMinute.Value, estimatedSpm, 1.0));
+ AddAssert("spm still valid", () => drawableSpinner.SpinsPerMinute.Value, () => Is.EqualTo(estimatedSpm).Within(1.0));
}
[TestCase(0.5)]
@@ -202,14 +199,14 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("adjust track rate", () => ((MasterGameplayClockContainer)Player.GameplayClockContainer).UserPlaybackRate.Value = rate);
addSeekStep(1000);
- AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05));
- AddAssert("spm almost same", () => Precision.AlmostEquals(expectedSpm, drawableSpinner.SpinsPerMinute.Value, 2.0));
+ AddAssert("progress almost same", () => expectedProgress, () => Is.EqualTo(drawableSpinner.Progress).Within(0.05));
+ AddAssert("spm almost same", () => expectedSpm, () => Is.EqualTo(drawableSpinner.SpinsPerMinute.Value).Within(2.0));
}
private void addSeekStep(double time)
{
AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time));
- AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
+ AddUntilStep("wait for seek to finish", () => time, () => Is.EqualTo(Player.DrawableRuleset.FrameStableClock.CurrentTime).Within(100));
}
private void transformReplay(Func replayTransformation) => AddStep("set replay", () =>
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..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
@@ -1,9 +1,8 @@
-
-
+
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs
index 0694746cbf..6d1b4d1a15 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs
@@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
public static class AimEvaluator
{
private const double wide_angle_multiplier = 1.5;
- private const double acute_angle_multiplier = 2.0;
- private const double slider_multiplier = 1.5;
+ private const double acute_angle_multiplier = 1.95;
+ private const double slider_multiplier = 1.35;
private const double velocity_change_multiplier = 0.75;
///
@@ -108,19 +108,13 @@ 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);
}
- if (osuLastObj.TravelTime != 0)
+ if (osuLastObj.BaseObject is Slider)
{
// Reward sliders based on velocity.
sliderBonus = osuLastObj.TravelDistance / osuLastObj.TravelTime;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
index 3b0826394c..fcf4179a3b 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
@@ -15,11 +15,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
private const double max_opacity_bonus = 0.4;
private const double hidden_bonus = 0.2;
+ private const double min_velocity = 0.5;
+ private const double slider_multiplier = 1.3;
+
///
/// Evaluates the difficulty of memorising and hitting an object, based on:
///
- /// - distance between the previous and current object,
+ /// - distance between a number of previous objects and the current object,
/// - the visual opacity of the current object,
+ /// - length and speed of the current object (for sliders),
/// - and whether the hidden mod is enabled.
///
///
@@ -73,6 +77,26 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
if (hidden)
result *= 1.0 + hidden_bonus;
+ double sliderBonus = 0.0;
+
+ if (osuCurrent.BaseObject is Slider osuSlider)
+ {
+ // Invert the scaling factor to determine the true travel distance independent of circle size.
+ double pixelTravelDistance = osuSlider.LazyTravelDistance / scalingFactor;
+
+ // Reward sliders based on velocity.
+ sliderBonus = Math.Pow(Math.Max(0.0, pixelTravelDistance / osuCurrent.TravelTime - min_velocity), 0.5);
+
+ // Longer sliders require more memorisation.
+ sliderBonus *= pixelTravelDistance;
+
+ // Nerf sliders with repeats, as less memorisation is required.
+ if (osuSlider.RepeatCount > 0)
+ sliderBonus /= (osuSlider.RepeatCount + 1);
+ }
+
+ result += sliderBonus * slider_multiplier;
+
return result;
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 75d9469da3..0ebfb9a283 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)
{
@@ -44,7 +46,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
if (mods.Any(h => h is OsuModRelax))
+ {
+ aimRating *= 0.9;
speedRating = 0.0;
+ flashlightRating *= 0.7;
+ }
double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000;
double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000;
@@ -60,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
Math.Pow(baseFlashlightPerformance, 1.1), 1.0 / 1.1
);
- double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
+ double starRating = basePerformance > 0.00001 ? Math.Cbrt(OsuPerformanceCalculator.PERFORMANCE_BASE_MULTIPLIER) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
double drainRate = beatmap.Difficulty.DrainRate;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index c3b7834009..3c82c2dc33 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
public class OsuPerformanceCalculator : PerformanceCalculator
{
+ public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
+
private double accuracy;
private int scoreMaxCombo;
private int countGreat;
@@ -41,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
effectiveMissCount = calculateEffectiveMissCount(osuAttributes);
- double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
+ double multiplier = PERFORMANCE_BASE_MULTIPLIER;
if (score.Mods.Any(m => m is OsuModNoFail))
multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount);
@@ -51,10 +53,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (score.Mods.Any(h => h is OsuModRelax))
{
- // As we're adding Oks and Mehs to an approximated number of combo breaks the result can be higher than total hits in specific scenarios (which breaks some calculations) so we need to clamp it.
- effectiveMissCount = Math.Min(effectiveMissCount + countOk + countMeh, totalHits);
+ // https://www.desmos.com/calculator/bc9eybdthb
+ // we use OD13.3 as maximum since it's the value at which great hitwidow becomes 0
+ // this is well beyond currently maximum achievable OD which is 12.17 (DTx2 + DA with OD11)
+ double okMultiplier = Math.Max(0.0, osuAttributes.OverallDifficulty > 0.0 ? 1 - Math.Pow(osuAttributes.OverallDifficulty / 13.33, 1.8) : 1.0);
+ double mehMultiplier = Math.Max(0.0, osuAttributes.OverallDifficulty > 0.0 ? 1 - Math.Pow(osuAttributes.OverallDifficulty / 13.33, 5) : 1.0);
- multiplier *= 0.6;
+ // As we're adding Oks and Mehs to an approximated number of combo breaks the result can be higher than total hits in specific scenarios (which breaks some calculations) so we need to clamp it.
+ effectiveMissCount = Math.Min(effectiveMissCount + countOk * okMultiplier + countMeh * mehMultiplier, totalHits);
}
double aimValue = computeAimValue(score, osuAttributes);
@@ -103,7 +109,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (attributes.ApproachRate > 10.33)
approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33);
else if (attributes.ApproachRate < 8.0)
- approachRateFactor = 0.1 * (8.0 - attributes.ApproachRate);
+ approachRateFactor = 0.05 * (8.0 - attributes.ApproachRate);
+
+ if (score.Mods.Any(h => h is OsuModRelax))
+ approachRateFactor = 0.0;
aimValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR.
@@ -134,6 +143,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attributes)
{
+ if (score.Mods.Any(h => h is OsuModRelax))
+ return 0.0;
+
double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
@@ -174,7 +186,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
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);
+ speedValue *= Math.Pow(0.99, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
return speedValue;
}
@@ -266,6 +278,5 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0);
private int totalHits => countGreat + countOk + countMeh + countMiss;
- private int totalSuccessfulHits => countGreat + countOk + countMeh;
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index c8646ec456..c7c5650184 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -15,10 +15,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
{
public class OsuDifficultyHitObject : DifficultyHitObject
{
- private const int normalised_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
+ ///
+ /// A distance by which all distances should be scaled in order to assume a uniform circle size.
+ ///
+ public const int NORMALISED_RADIUS = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
+
private const int min_delta_time = 25;
- private const float maximum_slider_radius = normalised_radius * 2.4f;
- private const float assumed_slider_radius = normalised_radius * 1.8f;
+ private const float maximum_slider_radius = NORMALISED_RADIUS * 2.4f;
+ private const float assumed_slider_radius = NORMALISED_RADIUS * 1.8f;
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
@@ -64,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
public double TravelDistance { get; private set; }
///
- /// The time taken to travel through , with a minimum value of 25ms for a non-zero distance.
+ /// The time taken to travel through , with a minimum value of 25ms for objects.
///
public double TravelTime { get; private set; }
@@ -123,7 +127,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
if (BaseObject is Slider currentSlider)
{
computeSliderCursorPosition(currentSlider);
- TravelDistance = currentSlider.LazyTravelDistance;
+ // Bonus for repeat sliders until a better per nested object strain system can be achieved.
+ TravelDistance = currentSlider.LazyTravelDistance * (float)Math.Pow(1 + currentSlider.RepeatCount / 2.5, 1.0 / 2.5);
TravelTime = Math.Max(currentSlider.LazyTravelTime / clockRate, min_delta_time);
}
@@ -132,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
return;
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
- float scalingFactor = normalised_radius / (float)BaseObject.Radius;
+ float scalingFactor = NORMALISED_RADIUS / (float)BaseObject.Radius;
if (BaseObject.Radius < 30)
{
@@ -206,7 +211,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
slider.LazyEndPosition = slider.StackedPosition + slider.Path.PositionAt(endTimeMin); // temporary lazy end position until a real result can be derived.
var currCursorPosition = slider.StackedPosition;
- double scalingFactor = normalised_radius / slider.Radius; // lazySliderDistance is coded to be sensitive to scaling, this makes the maths easier with the thresholds being used.
+ double scalingFactor = NORMALISED_RADIUS / slider.Radius; // lazySliderDistance is coded to be sensitive to scaling, this makes the maths easier with the thresholds being used.
for (int i = 1; i < slider.NestedHitObjects.Count; i++)
{
@@ -234,7 +239,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
else if (currMovementObj is SliderRepeat)
{
// For a slider repeat, assume a tighter movement threshold to better assess repeat sliders.
- requiredMovement = normalised_radius;
+ requiredMovement = NORMALISED_RADIUS;
}
if (currMovementLength > requiredMovement)
@@ -248,8 +253,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
if (i == slider.NestedHitObjects.Count - 1)
slider.LazyEndPosition = currCursorPosition;
}
-
- slider.LazyTravelDistance *= (float)Math.Pow(1 + slider.RepeatCount / 2.5, 1.0 / 2.5); // Bonus for repeat sliders until a better per nested object strain system can be achieved.
}
private Vector2 getEndCursorPosition(OsuHitObject hitObject)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
index 9b1fbf9a2e..38e0e5b677 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private double currentStrain;
- private double skillMultiplier => 23.25;
+ private double skillMultiplier => 23.55;
private double strainDecayBase => 0.15;
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
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);
diff --git a/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs
index affc0bae6a..4a3b187e83 100644
--- a/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs
+++ b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.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.Osu.Mods
{
///
diff --git a/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs
index a108f5fd14..1458abfe05 100644
--- a/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs
+++ b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.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.Osu.Mods
{
///
diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs
new file mode 100644
index 0000000000..a7aca8257b
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs
@@ -0,0 +1,114 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+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), typeof(OsuModCinema) };
+ public override ModType Type => ModType.Conversion;
+
+ private const double flash_duration = 1000;
+
+ private DrawableRuleset ruleset = null!;
+
+ protected OsuAction? LastAcceptedAction { get; private set; }
+
+ ///
+ /// 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 = null!;
+
+ private IFrameStableClock gameplayClock = null!;
+
+ 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 abstract bool CheckValidNewAction(OsuAction action);
+
+ private bool checkCorrectAction(OsuAction action)
+ {
+ if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
+ {
+ LastAcceptedAction = 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;
+ }
+
+ if (CheckValidNewAction(action))
+ {
+ LastAcceptedAction = action;
+ return true;
+ }
+
+ ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint);
+ 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..d88cb17e84 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs
@@ -1,119 +1,20 @@
// 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.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;
+ public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray();
- 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)
- {
- 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;
- 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)
- {
- // User alternated correctly.
- lastActionPressed = action;
- return true;
- }
-
- 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)
- {
- }
- }
+ protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction != action;
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs
index e25845f5ab..e6889403a3 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.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.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
index 4c9418726c..9229c0393d 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.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;
@@ -23,18 +21,18 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => OsuIcon.ModAutopilot;
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 double ScoreMultiplier => 0.1;
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel) };
public bool PerformFail() => false;
public bool RestartOnFail => false;
- private OsuInputManager inputManager;
+ private OsuInputManager inputManager = null!;
- private IFrameStableClock gameplayClock;
+ private IFrameStableClock gameplayClock = null!;
- private List replayFrames;
+ private List replayFrames = null!;
private int currentFrame;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs
index d562c37541..7c1f6be9ed 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.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;
@@ -14,7 +12,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), 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/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs
index 71bdd98457..9e71f657ce 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.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.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
index 199c735787..56665db770 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.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.Graphics;
@@ -30,10 +28,10 @@ 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;
+ private DrawableOsuBlinds blinds = null!;
public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
@@ -55,9 +53,12 @@ namespace osu.Game.Rulesets.Osu.Mods
///
/// Black background boxes behind blind panel textures.
///
- private Box blackBoxLeft, blackBoxRight;
+ private Box blackBoxLeft = null!, blackBoxRight = null!;
- private Drawable panelLeft, panelRight, bgPanelLeft, bgPanelRight;
+ private Drawable panelLeft = null!;
+ private Drawable panelRight = null!;
+ private Drawable bgPanelLeft = null!;
+ private Drawable bgPanelRight = null!;
private readonly Beatmap beatmap;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs
index 656cf95e77..769694baf4 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.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;
@@ -15,7 +13,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), typeof(OsuModRepel) }).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/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs
index 00009f4c3d..e021992f86 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.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.Osu/Mods/OsuModDaycore.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs
index c4cc0b4f48..371dfe6a1a 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.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.Mods;
namespace osu.Game.Rulesets.Osu.Mods
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
index e95e61312e..ee6a7815e2 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.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.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
index be159523b7..3a6b232f9f 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.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.Game.Beatmaps;
using osu.Game.Configuration;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs
index 2d19305509..700a3f44bc 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.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.Game.Rulesets.Mods;
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/OsuModEasy.cs b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs
index 90b22e8d9c..06b5b6cfb8 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.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.Mods;
namespace osu.Game.Rulesets.Osu.Mods
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
index c082805a0e..e5a458488e 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.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;
@@ -21,7 +19,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;
@@ -53,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override float DefaultFlashlightSize => 180;
- private OsuFlashlight flashlight;
+ private OsuFlashlight flashlight = null!;
protected override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(this);
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
index 34840de983..182d6eeb4b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.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.Osu/Mods/OsuModHalfTime.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs
index 54c5c56ca6..4769e7660b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.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.Mods;
namespace osu.Game.Rulesets.Osu.Mods
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
index fdddfed4d5..5430929143 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Linq;
using osu.Game.Rulesets.Mods;
@@ -14,7 +12,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..97f201b2cc 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.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;
@@ -25,7 +23,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/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs
index cee40866b1..7f7d6f70d2 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.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.Sprites;
@@ -25,10 +23,10 @@ namespace osu.Game.Rulesets.Osu.Mods
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 double ScoreMultiplier => 0.5;
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel) };
- private IFrameStableClock gameplayClock;
+ private IFrameStableClock gameplayClock = null!;
[SettingSource("Attraction strength", "How strong the pull is.", 0)]
public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f)
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs
index 1d822a2d4c..3faca0b01f 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModMirror.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.Osu/Mods/OsuModMuted.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMuted.cs
index 1d4650a379..5e3ee37b61 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModMuted.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModMuted.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.Mods;
using osu.Game.Rulesets.Osu.Objects;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs
index e9be56fcc5..b7838ebaa7 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.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.Mods;
using osu.Game.Rulesets.Osu.Objects;
@@ -10,6 +8,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.Osu/Mods/OsuModNoFail.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs
index c20fcf0b1b..9f707a5aa6 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Linq;
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
index fe415cb967..3eb8982f5d 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.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;
@@ -21,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Description => "Where's the cursor?";
- private PeriodTracker spinnerPeriods;
+ private PeriodTracker spinnerPeriods = null!;
[SettingSource(
"Hidden at combo",
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
index 44942e9e37..59984f9a7b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.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.Osu/Mods/OsuModPerfect.cs b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs
index c5795177d0..33581405a6 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs
@@ -1,13 +1,14 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#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[] { typeof(OsuModAutopilot) }).ToArray();
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index 2cf8c278ca..908bb34ed6 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.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;
@@ -21,7 +19,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.
@@ -31,9 +29,9 @@ namespace osu.Game.Rulesets.Osu.Mods
private bool isDownState;
private bool wasLeft;
- private OsuInputManager osuInputManager;
+ private OsuInputManager osuInputManager = null!;
- private ReplayState state;
+ private ReplayState state = null!;
private double lastStateChangeTime;
private bool hasReplay;
@@ -134,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Mods
wasLeft = !wasLeft;
}
- state?.Apply(osuInputManager.CurrentState, osuInputManager);
+ state.Apply(osuInputManager.CurrentState, osuInputManager);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs
new file mode 100644
index 0000000000..211987ee32
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs
@@ -0,0 +1,98 @@
+// 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.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.Osu.Utils;
+using osu.Game.Rulesets.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ 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 => "Hit objects run away!";
+ 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 BindableFloat RepulsionStrength { 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)
+ {
+ var destination = Vector2.Clamp(2 * drawable.Position - cursorPos, Vector2.Zero, OsuPlayfield.BASE_SIZE);
+
+ if (drawable.HitObject is Slider thisSlider)
+ {
+ var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(thisSlider);
+
+ destination = Vector2.Clamp(
+ destination,
+ new Vector2(possibleMovementBounds.Left, possibleMovementBounds.Top),
+ new Vector2(possibleMovementBounds.Right, possibleMovementBounds.Bottom)
+ );
+ }
+
+ switch (drawable)
+ {
+ case DrawableHitCircle circle:
+ easeTo(circle, destination, cursorPos);
+ break;
+
+ case DrawableSlider slider:
+
+ if (!slider.HeadCircle.Result.HasResult)
+ easeTo(slider, destination, cursorPos);
+ else
+ easeTo(slider, destination - slider.Ball.DrawPosition, cursorPos);
+
+ break;
+ }
+ }
+ }
+
+ 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);
+ 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/OsuModSingleTap.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs
new file mode 100644
index 0000000000..b170d30448
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ public class OsuModSingleTap : InputBlockingMod
+ {
+ public override string Name => @"Single Tap";
+ 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();
+
+ protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction == null || LastAcceptedAction == action;
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
index 16e7780af0..95e7d13ee7 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.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.Sprites;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
index 61028a1ee8..d9ab749ad3 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.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.Sprites;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs
index 565ff415be..0b34ab28a3 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.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 System.Threading;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs
index 4eb7659152..429fe30fc5 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Linq;
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
index f03bcffdc8..623157a427 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.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;
@@ -96,11 +94,7 @@ namespace osu.Game.Rulesets.Osu.Mods
#region Private Fields
- private ControlPointInfo controlPointInfo;
-
- private List originalHitObjects;
-
- private Random rng;
+ private ControlPointInfo controlPointInfo = null!;
#endregion
@@ -171,16 +165,17 @@ namespace osu.Game.Rulesets.Osu.Mods
public override void ApplyToBeatmap(IBeatmap beatmap)
{
Seed.Value ??= RNG.Next();
- rng = new Random(Seed.Value.Value);
+
+ var rng = new Random(Seed.Value.Value);
var osuBeatmap = (OsuBeatmap)beatmap;
if (osuBeatmap.HitObjects.Count == 0) return;
controlPointInfo = osuBeatmap.ControlPointInfo;
- originalHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList();
- var hitObjects = generateBeats(osuBeatmap)
+ var originalHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList();
+ var hitObjects = generateBeats(osuBeatmap, originalHitObjects)
.Select(beat =>
{
var newCircle = new HitCircle();
@@ -189,18 +184,18 @@ namespace osu.Game.Rulesets.Osu.Mods
return (OsuHitObject)newCircle;
}).ToList();
- addHitSamples(hitObjects);
+ addHitSamples(hitObjects, originalHitObjects);
- fixComboInfo(hitObjects);
+ fixComboInfo(hitObjects, originalHitObjects);
- randomizeCirclePos(hitObjects);
+ randomizeCirclePos(hitObjects, rng);
osuBeatmap.HitObjects = hitObjects;
base.ApplyToBeatmap(beatmap);
}
- private IEnumerable generateBeats(IBeatmap beatmap)
+ private IEnumerable generateBeats(IBeatmap beatmap, IReadOnlyCollection originalHitObjects)
{
double startTime = originalHitObjects.First().StartTime;
double endTime = originalHitObjects.Last().GetEndTime();
@@ -213,7 +208,7 @@ namespace osu.Game.Rulesets.Osu.Mods
// Remove beats before startTime
.Where(beat => almostBigger(beat, startTime))
// Remove beats during breaks
- .Where(beat => !isInsideBreakPeriod(beatmap.Breaks, beat))
+ .Where(beat => !isInsideBreakPeriod(originalHitObjects, beatmap.Breaks, beat))
.ToList();
// Remove beats that are too close to the next one (e.g. due to timing point changes)
@@ -228,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Mods
return beats;
}
- private void addHitSamples(IEnumerable hitObjects)
+ private void addHitSamples(IEnumerable hitObjects, List originalHitObjects)
{
foreach (var obj in hitObjects)
{
@@ -240,7 +235,7 @@ namespace osu.Game.Rulesets.Osu.Mods
}
}
- private void fixComboInfo(List hitObjects)
+ private void fixComboInfo(List hitObjects, List originalHitObjects)
{
// Copy combo indices from an original object at the same time or from the closest preceding object
// (Objects lying between two combos are assumed to belong to the preceding combo)
@@ -274,7 +269,7 @@ namespace osu.Game.Rulesets.Osu.Mods
}
}
- private void randomizeCirclePos(IReadOnlyList hitObjects)
+ private void randomizeCirclePos(IReadOnlyList hitObjects, Random rng)
{
if (hitObjects.Count == 0) return;
@@ -355,9 +350,10 @@ namespace osu.Game.Rulesets.Osu.Mods
/// The given time is also considered to be inside a break if it is earlier than the
/// start time of the first original hit object after the break.
///
+ /// Hit objects order by time.
/// The breaks of the beatmap.
/// The time to be checked.=
- private bool isInsideBreakPeriod(IEnumerable breaks, double time)
+ private bool isInsideBreakPeriod(IReadOnlyCollection originalHitObjects, IEnumerable breaks, double time)
{
return breaks.Any(breakPeriod =>
{
@@ -405,7 +401,7 @@ namespace osu.Game.Rulesets.Osu.Mods
/// The list of hit objects in a beatmap, ordered by StartTime
/// The point in time to get samples for
/// Hit samples
- private IList getSamplesAtTime(IEnumerable hitObjects, double time)
+ private IList? getSamplesAtTime(IEnumerable hitObjects, double time)
{
// Get a hit object that
// either has StartTime equal to the target time
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs
index f8c1e1639d..7276cc753c 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.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.Mods;
namespace osu.Game.Rulesets.Osu.Mods
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
index 6e5dd45a7a..d862d36670 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -59,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Mods
}
}
- private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable hitCircle = null)
+ private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable? hitCircle = null)
{
var h = hitObject.HitObject;
using (hitObject.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
index 5a08df3803..4354ecbe9a 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.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.Sprites;
@@ -22,7 +20,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..3f1c3aa812 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
@@ -1,11 +1,11 @@
// 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;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
@@ -22,10 +22,17 @@ 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
+ private const int wiggle_duration = 100; // (ms) Higher = fewer wiggles
+
+ [SettingSource("Strength", "Multiplier applied to the wiggling strength.")]
+ public BindableDouble Strength { get; } = new BindableDouble(1)
+ {
+ MinValue = 0.1f,
+ MaxValue = 2f,
+ Precision = 0.1f
+ };
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => drawableOnApplyCustomUpdateState(hitObject, state);
@@ -49,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Mods
void wiggle()
{
float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI);
- float nextDist = (float)(objRand.NextDouble() * wiggle_strength);
+ float nextDist = (float)(objRand.NextDouble() * Strength.Value * 7);
drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), wiggle_duration);
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
index 4949abccab..306b034645 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
{
InternalChildren = new Drawable[]
{
- connectionPool = new DrawablePool(1, 200),
+ connectionPool = new DrawablePool(10, 200),
pointPool = new DrawablePool(50, 1000)
};
}
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/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index 387342b4a9..7b98fc48e0 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,66 +55,80 @@ namespace osu.Game.Rulesets.Osu.Objects
public Vector2 StackedEndPosition => EndPosition + StackOffset;
- public readonly Bindable StackHeightBindable = new Bindable();
+ private HitObjectProperty stackHeight;
+
+ 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;
- public readonly Bindable ScaleBindable = new BindableFloat(1);
+ private HitObjectProperty scale = new HitObjectProperty(1);
+
+ 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; }
- public readonly Bindable ComboOffsetBindable = new Bindable();
+ private HitObjectProperty comboOffset;
+
+ public Bindable ComboOffsetBindable => comboOffset.Bindable;
public int ComboOffset
{
- get => ComboOffsetBindable.Value;
- set => ComboOffsetBindable.Value = value;
+ get => comboOffset.Value;
+ set => comboOffset.Value = value;
}
- public Bindable IndexInCurrentComboBindable { get; } = new Bindable();
+ private HitObjectProperty indexInCurrentCombo;
+
+ public Bindable IndexInCurrentComboBindable => indexInCurrentCombo.Bindable;
public virtual 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 virtual 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;
public bool LastInCombo
{
- get => LastInComboBindable.Value;
- set => LastInComboBindable.Value = value;
+ get => lastInCombo.Value;
+ set => lastInCombo.Value = value;
}
protected OsuHitObject()
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 120ce32612..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:
@@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Osu
new OsuModApproachDifferent(),
new OsuModMuted(),
new OsuModNoScope(),
- new OsuModMagnetised(),
+ new MultiMod(new OsuModMagnetised(), new OsuModRepel()),
new ModAdaptiveSpeed()
};
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.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs
index 8211448705..aaace89cd5 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs
@@ -4,16 +4,16 @@
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;
namespace osu.Game.Rulesets.Osu.Skinning.Default
{
- public class DefaultFollowCircle : CompositeDrawable
+ public class DefaultFollowCircle : FollowCircle
{
public DefaultFollowCircle()
{
- RelativeSizeAxes = Axes.Both;
-
InternalChild = new CircularContainer
{
RelativeSizeAxes = Axes.Both,
@@ -29,5 +29,43 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
}
};
}
+
+ protected override void OnSliderPress()
+ {
+ const float duration = 300f;
+
+ if (Precision.AlmostEquals(0, Alpha))
+ this.ScaleTo(1);
+
+ this.ScaleTo(DrawableSliderBall.FOLLOW_AREA, duration, Easing.OutQuint)
+ .FadeIn(duration, Easing.OutQuint);
+ }
+
+ protected override void OnSliderRelease()
+ {
+ const float duration = 150;
+
+ this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.2f, duration, Easing.OutQuint)
+ .FadeTo(0, duration, Easing.OutQuint);
+ }
+
+ protected override void OnSliderEnd()
+ {
+ const float duration = 300;
+
+ this.ScaleTo(1, duration, Easing.OutQuint)
+ .FadeOut(duration / 2, Easing.OutQuint);
+ }
+
+ protected override void OnSliderTick()
+ {
+ this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.08f, 40, Easing.OutQuint)
+ .Then()
+ .ScaleTo(DrawableSliderBall.FOLLOW_AREA, 200f, Easing.OutQuint);
+ }
+
+ protected override void OnSliderBreak()
+ {
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs
index 47308375e6..97bb4a3697 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,62 @@ 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)
+ {
+ // Gets called by slider ticks, tails, etc., leading to duplicated
+ // animations which may negatively affect performance
+ if (drawableObject is not DrawableSlider)
+ return;
+
+ const float fade_duration = 450f;
+
+ using (BeginAbsoluteSequence(drawableObject.StateUpdateTime))
+ {
+ this.FadeIn()
+ .ScaleTo(1f);
+ }
+
+ using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
+ {
+ // intentionally pile on an extra FadeOut to make it happen much faster
+ this.FadeOut(fade_duration / 4, Easing.Out);
+
+ switch (state)
+ {
+ case ArmedState.Hit:
+ this.ScaleTo(1.4f, fade_duration, 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/Default/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs
index ab14f939d4..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,6 +137,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
this.ScaleTo(initial_scale);
this.RotateTo(0);
+ updateDiscColour(false);
+
using (BeginDelayedSequence(spinner.TimePreempt / 2))
{
// constant ambient rotation to give the spinner "spinning" character.
@@ -177,12 +179,14 @@ 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))
+ updateDiscColour(true, 200);
+ }
}
- private void updateComplete(bool complete, double duration)
+ private void updateDiscColour(bool complete, double duration = 0)
{
var colour = complete ? completeColour : normalColour;
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs
index f9ed8b8721..554ea3ac90 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs
@@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
private bool rotationTransferred;
[Resolved(canBeNull: true)]
- private GameplayClock gameplayClock { get; set; }
+ private IGameplayClock gameplayClock { get; set; }
protected override void Update()
{
diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs
new file mode 100644
index 0000000000..9eb8e66c83
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.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.
+
+using System.Diagnostics;
+using osu.Framework.Allocation;
+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]
+ protected DrawableHitObject? ParentObject { get; private set; }
+
+ protected FollowCircle()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ ((DrawableSlider?)ParentObject)?.Tracking.BindValueChanged(tracking =>
+ {
+ Debug.Assert(ParentObject != null);
+ if (ParentObject.Judged)
+ return;
+
+ if (tracking.NewValue)
+ OnSliderPress();
+ else
+ OnSliderRelease();
+ }, 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)
+ {
+ Debug.Assert(ParentObject != null);
+
+ switch (state)
+ {
+ case ArmedState.Hit:
+ switch (drawableObject)
+ {
+ case DrawableSliderTail:
+ // Use ParentObject instead of drawableObject because slider tail's
+ // HitStateUpdateTime is ~36ms before the actual slider end (aka slider
+ // tail leniency)
+ using (BeginAbsoluteSequence(ParentObject.HitStateUpdateTime))
+ OnSliderEnd();
+ break;
+
+ case DrawableSliderTick:
+ case DrawableSliderRepeat:
+ using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
+ OnSliderTick();
+ break;
+ }
+
+ break;
+
+ case ArmedState.Miss:
+ switch (drawableObject)
+ {
+ case DrawableSliderTail:
+ case DrawableSliderTick:
+ case DrawableSliderRepeat:
+ // Despite above comment, ok to use drawableObject.HitStateUpdateTime
+ // here, since on stable, the break anim plays right when the tail is
+ // missed, not when the slider ends
+ using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
+ OnSliderBreak();
+ break;
+ }
+
+ break;
+ }
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (ParentObject != null)
+ {
+ ParentObject.HitObjectApplied -= onHitObjectApplied;
+ ParentObject.ApplyCustomUpdateState -= updateStateTransforms;
+ }
+ }
+
+ protected abstract void OnSliderPress();
+
+ protected abstract void OnSliderRelease();
+
+ protected abstract void OnSliderEnd();
+
+ protected abstract void OnSliderTick();
+
+ protected abstract void OnSliderBreak();
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs
index b8a559ce07..0d12fb01f5 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs
@@ -1,12 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Diagnostics;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
- public class LegacyFollowCircle : CompositeDrawable
+ public class LegacyFollowCircle : FollowCircle
{
public LegacyFollowCircle(Drawable animationContent)
{
@@ -18,5 +19,39 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
RelativeSizeAxes = Axes.Both;
InternalChild = animationContent;
}
+
+ protected override void OnSliderPress()
+ {
+ Debug.Assert(ParentObject != null);
+
+ 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).
+ this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out)
+ .FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime));
+ }
+
+ protected override void OnSliderRelease()
+ {
+ }
+
+ protected override void OnSliderEnd()
+ {
+ this.ScaleTo(1.6f, 200, Easing.Out)
+ .FadeOut(200, Easing.In);
+ }
+
+ protected override void OnSliderTick()
+ {
+ this.ScaleTo(2.2f)
+ .ScaleTo(2f, 200);
+ }
+
+ protected override void OnSliderBreak()
+ {
+ this.ScaleTo(4f, 100)
+ .FadeTo(0f, 100);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index c1d518a843..1b2ef23674 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -9,9 +9,9 @@ using System.Runtime.InteropServices;
using osu.Framework.Allocation;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Batches;
-using osu.Framework.Graphics.OpenGL.Vertices;
using osu.Framework.Graphics.Primitives;
+using osu.Framework.Graphics.Rendering;
+using osu.Framework.Graphics.Rendering.Vertices;
using osu.Framework.Graphics.Shaders;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
@@ -68,8 +68,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
}
[BackgroundDependencyLoader]
- private void load(ShaderManager shaders)
+ private void load(IRenderer renderer, ShaderManager shaders)
{
+ texture ??= renderer.WhitePixel;
shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE);
}
@@ -79,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
resetTime();
}
- private Texture texture = Texture.WhitePixel;
+ private Texture texture;
public Texture Texture
{
@@ -222,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private Vector2 size;
private Vector2 originPosition;
- private readonly QuadBatch vertexBatch = new QuadBatch(max_sprites, 1);
+ private IVertexBatch vertexBatch;
public TrailDrawNode(CursorTrail source)
: base(source)
@@ -254,15 +255,17 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
Source.parts.CopyTo(parts, 0);
}
- public override void Draw(Action vertexAction)
+ public override void Draw(IRenderer renderer)
{
- base.Draw(vertexAction);
+ base.Draw(renderer);
+
+ vertexBatch ??= renderer.CreateQuadBatch(max_sprites, 1);
shader.Bind();
shader.GetUniform("g_FadeClock").UpdateValue(ref time);
shader.GetUniform("g_FadeExponent").UpdateValue(ref fadeExponent);
- texture.TextureGL.Bind();
+ texture.Bind();
RectangleF textureRect = texture.GetTextureRect();
@@ -319,7 +322,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
base.Dispose(isDisposing);
- vertexBatch.Dispose();
+ vertexBatch?.Dispose();
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index 3179b37d5a..fc2ba8ea2f 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -11,9 +11,11 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
@@ -112,21 +114,36 @@ namespace osu.Game.Rulesets.Osu.UI
}
[BackgroundDependencyLoader(true)]
- private void load(OsuRulesetConfigManager config)
+ private void load(OsuRulesetConfigManager config, IBeatmap beatmap)
{
config?.BindWith(OsuRulesetSetting.PlayfieldBorderStyle, playfieldBorder.PlayfieldBorderStyle);
- RegisterPool(10, 100);
+ var osuBeatmap = (OsuBeatmap)beatmap;
- RegisterPool(10, 100);
- RegisterPool(10, 100);
- RegisterPool(10, 100);
- RegisterPool(10, 100);
- RegisterPool(5, 50);
+ RegisterPool(20, 100);
+
+ // handle edge cases where a beatmap has a slider with many repeats.
+ int maxRepeatsOnOneSlider = 0;
+ int maxTicksOnOneSlider = 0;
+
+ if (osuBeatmap != null)
+ {
+ foreach (var slider in osuBeatmap.HitObjects.OfType())
+ {
+ maxRepeatsOnOneSlider = Math.Max(maxRepeatsOnOneSlider, slider.RepeatCount);
+ maxTicksOnOneSlider = Math.Max(maxTicksOnOneSlider, slider.NestedHitObjects.OfType().Count());
+ }
+ }
+
+ RegisterPool(20, 100);
+ RegisterPool(20, 100);
+ RegisterPool(20, 100);
+ RegisterPool(Math.Max(maxTicksOnOneSlider, 20), Math.Max(maxTicksOnOneSlider, 200));
+ RegisterPool(Math.Max(maxRepeatsOnOneSlider, 20), Math.Max(maxRepeatsOnOneSlider, 200));
RegisterPool(2, 20);
- RegisterPool(10, 100);
- RegisterPool(10, 100);
+ RegisterPool(10, 200);
+ RegisterPool(10, 200);
}
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject);
@@ -173,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.UI
private readonly Action onLoaded;
public DrawableJudgementPool(HitResult result, Action onLoaded)
- : base(10)
+ : base(20)
{
this.result = result;
this.onLoaded = onLoaded;
diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs
index 3a156d4d25..a9ae313a31 100644
--- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs
+++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs
@@ -194,7 +194,28 @@ 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);
+
+ // 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;
@@ -239,10 +260,12 @@ 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.
+ /// 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)
{
var pathPositions = new List();
slider.Path.GetPathToProgress(pathPositions, 0, 1);
@@ -353,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
{
///
@@ -395,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; }
@@ -405,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;
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs
new file mode 100644
index 0000000000..7210419c0e
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.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 NUnit.Framework;
+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 : OsuTestScene
+ {
+ private DrumTouchInputArea drumTouchInputArea = null!;
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("create drum", () =>
+ {
+ Child = new TaikoInputManager(new TaikoRuleset().RulesetInfo)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new InputDrum
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Height = 0.2f,
+ },
+ drumTouchInputArea = new DrumTouchInputArea
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ },
+ },
+ };
+ });
+ }
+
+ [Test]
+ public void TestDrum()
+ {
+ AddStep("show drum", () => drumTouchInputArea.Show());
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerLegacySkin.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerLegacySkin.cs
new file mode 100644
index 0000000000..13df24c988
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerLegacySkin.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Taiko.Mods;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ public class TestSceneTaikoPlayerLegacySkin : LegacySkinPlayerTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset();
+
+ protected override TestPlayer CreatePlayer(Ruleset ruleset)
+ {
+ SelectedMods.Value = new[] { new TaikoModClassic() };
+ return base.CreatePlayer(ruleset);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs
new file mode 100644
index 0000000000..b65e2af3d8
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs
@@ -0,0 +1,59 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using 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 : CompositeDrawable, IKeyBindingHandler
+ {
+ private readonly DrumSampleTriggerSource leftRimSampleTriggerSource;
+ private readonly DrumSampleTriggerSource leftCentreSampleTriggerSource;
+ private readonly DrumSampleTriggerSource rightCentreSampleTriggerSource;
+ private readonly DrumSampleTriggerSource rightRimSampleTriggerSource;
+
+ public DrumSamplePlayer(HitObjectContainer hitObjectContainer)
+ {
+ InternalChildren = new Drawable[]
+ {
+ leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer),
+ leftCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer),
+ rightCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer),
+ rightRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer),
+ };
+ }
+
+ public bool OnPressed(KeyBindingPressEvent e)
+ {
+ switch (e.Action)
+ {
+ 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;
+ }
+
+ public void OnReleased(KeyBindingReleaseEvent e)
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs
new file mode 100644
index 0000000000..a7d9bd18c5
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs
@@ -0,0 +1,243 @@
+// 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.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Game.Graphics;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Taiko.UI
+{
+ ///
+ /// An overlay that captures and displays osu!taiko mouse and touch input.
+ ///
+ 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
[TestFixture]
- public class TestSubmittableScoreJsonSerialization
+ public class TestSoloScoreInfoJsonSerialization
{
[Test]
public void TestScoreSerialisationViaExtensionMethod()
{
- var score = new SubmittableScore(TestResources.CreateTestScoreInfo());
+ var score = SoloScoreInfo.ForSubmission(TestResources.CreateTestScoreInfo());
string serialised = score.Serialize();
@@ -31,7 +31,7 @@ namespace osu.Game.Tests.Online
[Test]
public void TestScoreSerialisationWithoutSettings()
{
- var score = new SubmittableScore(TestResources.CreateTestScoreInfo());
+ var score = SoloScoreInfo.ForSubmission(TestResources.CreateTestScoreInfo());
string serialised = JsonConvert.SerializeObject(score);
diff --git a/osu.Game.Tests/Resources/Archives/modified-classic-20220723.osk b/osu.Game.Tests/Resources/Archives/modified-classic-20220723.osk
new file mode 100644
index 0000000000..8e7a1b42df
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/modified-classic-20220723.osk differ
diff --git a/osu.Game.Tests/Resources/Archives/modified-classic-20220801.osk b/osu.Game.Tests/Resources/Archives/modified-classic-20220801.osk
new file mode 100644
index 0000000000..9236e1d77f
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/modified-classic-20220801.osk differ
diff --git a/osu.Game.Tests/Resources/Archives/modified-default-20220723.osk b/osu.Game.Tests/Resources/Archives/modified-default-20220723.osk
new file mode 100644
index 0000000000..7547162165
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/modified-default-20220723.osk differ
diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs
index 91d4eb70e8..6bce03869d 100644
--- a/osu.Game.Tests/Resources/TestResources.cs
+++ b/osu.Game.Tests/Resources/TestResources.cs
@@ -128,16 +128,20 @@ namespace osu.Game.Tests.Resources
var rulesetInfo = getRuleset();
+ string hash = Guid.NewGuid().ToString().ComputeMD5Hash();
+
yield return new BeatmapInfo
{
OnlineID = beatmapId,
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(),
+ Hash = hash,
+ MD5Hash = hash,
Ruleset = rulesetInfo,
- Metadata = metadata,
+ Metadata = metadata.DeepClone(),
Difficulty = new BeatmapDifficulty
{
OverallDifficulty = diff,
diff --git a/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs
index 2622db464f..4601737558 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;
@@ -19,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()
diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs
index 9f732be9e3..29534348dc 100644
--- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs
+++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs
@@ -15,11 +15,12 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.OpenGL.Textures;
+using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Shaders;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
+using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.UI;
@@ -77,9 +78,9 @@ namespace osu.Game.Tests.Rulesets
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
- dependencies.CacheAs(ParentTextureStore = new TestTextureStore());
+ dependencies.CacheAs(ParentTextureStore = new TestTextureStore(parent.Get().Renderer));
dependencies.CacheAs(ParentSampleStore = new TestSampleStore());
- dependencies.CacheAs(ParentShaderManager = new TestShaderManager());
+ dependencies.CacheAs(ParentShaderManager = new TestShaderManager(parent.Get().Renderer));
return new DrawableRulesetDependencies(new OsuRuleset(), dependencies);
}
@@ -95,6 +96,11 @@ namespace osu.Game.Tests.Rulesets
private class TestTextureStore : TextureStore
{
+ public TestTextureStore(IRenderer renderer)
+ : base(renderer)
+ {
+ }
+
public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => null;
public bool IsDisposed { get; private set; }
@@ -148,8 +154,8 @@ namespace osu.Game.Tests.Rulesets
private class TestShaderManager : ShaderManager
{
- public TestShaderManager()
- : base(new ResourceStore())
+ public TestShaderManager(IRenderer renderer)
+ : base(renderer, new ResourceStore())
{
}
diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs
index f1ecd3b526..320373699d 100644
--- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs
+++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs
@@ -10,7 +10,6 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;
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);
}
}
}
diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs
new file mode 100644
index 0000000000..c7eb334f25
--- /dev/null
+++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs
@@ -0,0 +1,127 @@
+// 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.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.IO.Stores;
+using osu.Game.Audio;
+using osu.Game.IO;
+using osu.Game.IO.Archives;
+using osu.Game.Screens.Play.HUD;
+using osu.Game.Screens.Play.HUD.HitErrorMeters;
+using osu.Game.Skinning;
+using osu.Game.Tests.Resources;
+
+namespace osu.Game.Tests.Skins
+{
+ ///
+ /// Test that the main components (which are serialised based on namespace/class name)
+ /// remain compatible with any changes.
+ ///
+ ///
+ /// If this test breaks, check any naming or class structure changes.
+ /// Migration rules may need to be added to .
+ ///
+ [TestFixture]
+ public class SkinDeserialisationTest
+ {
+ private static readonly string[] available_skins =
+ {
+ // Covers song progress before namespace changes, and most other components.
+ "Archives/modified-default-20220723.osk",
+ "Archives/modified-classic-20220723.osk",
+ // Covers legacy song progress, UR counter, colour hit error metre.
+ "Archives/modified-classic-20220801.osk"
+ };
+
+ ///
+ /// If this test fails, new test resources should be added to include new components.
+ ///
+ [Test]
+ public void TestSkinnableComponentsCoveredByDeserialisationTests()
+ {
+ HashSet instantiatedTypes = new HashSet();
+
+ foreach (string oskFile in available_skins)
+ {
+ using (var stream = TestResources.OpenResource(oskFile))
+ using (var storage = new ZipArchiveReader(stream))
+ {
+ var skin = new TestSkin(new SkinInfo(), null, storage);
+
+ foreach (var target in skin.DrawableComponentInfo)
+ {
+ foreach (var info in target.Value)
+ instantiatedTypes.Add(info.Type);
+ }
+ }
+ }
+
+ var editableTypes = SkinnableInfo.GetAllAvailableDrawables().Where(t => (Activator.CreateInstance(t) as ISkinnableDrawable)?.IsEditable == true);
+
+ Assert.That(instantiatedTypes, Is.EquivalentTo(editableTypes));
+ }
+
+ [Test]
+ public void TestDeserialiseModifiedDefault()
+ {
+ using (var stream = TestResources.OpenResource("Archives/modified-default-20220723.osk"))
+ using (var storage = new ZipArchiveReader(stream))
+ {
+ var skin = new TestSkin(new SkinInfo(), null, storage);
+
+ Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2));
+ Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents], Has.Length.EqualTo(9));
+ }
+ }
+
+ [Test]
+ public void TestDeserialiseModifiedClassic()
+ {
+ using (var stream = TestResources.OpenResource("Archives/modified-classic-20220723.osk"))
+ using (var storage = new ZipArchiveReader(stream))
+ {
+ var skin = new TestSkin(new SkinInfo(), null, storage);
+
+ Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2));
+ Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents], Has.Length.EqualTo(6));
+ Assert.That(skin.DrawableComponentInfo[SkinnableTarget.SongSelect], Has.Length.EqualTo(1));
+
+ var skinnableInfo = skin.DrawableComponentInfo[SkinnableTarget.SongSelect].First();
+
+ Assert.That(skinnableInfo.Type, Is.EqualTo(typeof(SkinnableSprite)));
+ Assert.That(skinnableInfo.Settings.First().Key, Is.EqualTo("sprite_name"));
+ Assert.That(skinnableInfo.Settings.First().Value, Is.EqualTo("ppy_logo-2.png"));
+ }
+
+ using (var stream = TestResources.OpenResource("Archives/modified-classic-20220801.osk"))
+ using (var storage = new ZipArchiveReader(stream))
+ {
+ var skin = new TestSkin(new SkinInfo(), null, storage);
+ Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents], Has.Length.EqualTo(8));
+ Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter)));
+ Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter)));
+ Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(LegacySongProgress)));
+ }
+ }
+
+ private class TestSkin : Skin
+ {
+ public TestSkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? storage = null, string configurationFilename = "skin.ini")
+ : base(skin, resources, storage, configurationFilename)
+ {
+ }
+
+ public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
+
+ public override IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
+
+ public override ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
+ }
+ }
+}
diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs
index 1493c10969..004e253c1f 100644
--- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs
+++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs
@@ -10,7 +10,6 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;
diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
index f4cea2c8cc..e82a5b57d9 100644
--- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
+++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
@@ -32,7 +32,6 @@ namespace osu.Game.Tests.Skins
imported?.PerformRead(s =>
{
beatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps[0]);
- beatmap.LoadTrack();
});
}
@@ -40,6 +39,10 @@ namespace osu.Game.Tests.Skins
public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo("sample")) != null);
[Test]
- public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => !(beatmap.Track is TrackVirtual));
+ public void TestRetrieveOggTrack() => AddAssert("track is non-null", () =>
+ {
+ using (var track = beatmap.LoadTrack())
+ return track is not TrackVirtual;
+ });
}
}
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
index 5452bfc939..b0c26f7280 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
@@ -13,7 +13,6 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;
diff --git a/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs b/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs
index 1229b63a90..f7c88643fe 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs
@@ -6,10 +6,11 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.OpenGL.Textures;
+using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;
@@ -21,6 +22,9 @@ namespace osu.Game.Tests.Skins
[HeadlessTest]
public class TestSceneSkinProvidingContainer : OsuTestScene
{
+ [Resolved]
+ private IRenderer renderer { get; set; }
+
///
/// Ensures that the first inserted skin after resetting (via source change)
/// is always prioritised over others when providing the same resource.
@@ -35,7 +39,7 @@ namespace osu.Game.Tests.Skins
{
var sources = new List();
for (int i = 0; i < 10; i++)
- sources.Add(new TestSkin());
+ sources.Add(new TestSkin(renderer));
mostPrioritisedSource = sources.First();
@@ -76,12 +80,19 @@ namespace osu.Game.Tests.Skins
{
public const string TEXTURE_NAME = "virtual-texture";
+ private readonly IRenderer renderer;
+
+ public TestSkin(IRenderer renderer)
+ {
+ this.renderer = renderer;
+ }
+
public Drawable GetDrawableComponent(ISkinComponent component) => throw new System.NotImplementedException();
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
{
if (componentName == TEXTURE_NAME)
- return Texture.WhitePixel;
+ return renderer.WhitePixel;
return null;
}
diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs
index 42c1eeb6d1..22d6d08355 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs
@@ -1,14 +1,24 @@
// 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.Linq;
+using Moq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Extensions;
+using osu.Framework.Extensions.ObjectExtensions;
+using osu.Framework.Graphics.Rendering.Dummy;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.IO.Stores;
using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Database;
+using osu.Game.IO;
using osu.Game.Skinning;
using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual;
@@ -19,9 +29,9 @@ namespace osu.Game.Tests.Skins
public class TestSceneSkinResources : OsuTestScene
{
[Resolved]
- private SkinManager skins { get; set; }
+ private SkinManager skins { get; set; } = null!;
- private ISkin skin;
+ private ISkin skin = null!;
[BackgroundDependencyLoader]
private void load()
@@ -32,5 +42,56 @@ namespace osu.Game.Tests.Skins
[Test]
public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => skin.GetSample(new SampleInfo("sample")) != null);
+
+ [Test]
+ public void TestSampleRetrievalOrder()
+ {
+ Mock mockResourceProvider = null!;
+ Mock> mockResourceStore = null!;
+ List lookedUpFileNames = null!;
+
+ AddStep("setup mock providers provider", () =>
+ {
+ lookedUpFileNames = new List();
+ mockResourceProvider = new Mock();
+ mockResourceProvider.Setup(m => m.AudioManager).Returns(Audio);
+ mockResourceProvider.Setup(m => m.Renderer).Returns(new DummyRenderer());
+ mockResourceStore = new Mock>();
+ mockResourceStore.Setup(r => r.Get(It.IsAny()))
+ .Callback(n => lookedUpFileNames.Add(n))
+ .Returns(null);
+ });
+
+ AddStep("query sample", () =>
+ {
+ TestSkin testSkin = new TestSkin(new SkinInfo(), mockResourceProvider.Object, new ResourceStore(mockResourceStore.Object));
+ testSkin.GetSample(new SampleInfo());
+ });
+
+ AddAssert("sample lookups were in correct order", () =>
+ {
+ string[] lookups = lookedUpFileNames.Where(f => f.StartsWith(TestSkin.SAMPLE_NAME, StringComparison.Ordinal)).ToArray();
+ return Path.GetExtension(lookups[0]) == string.Empty
+ && Path.GetExtension(lookups[1]) == ".wav"
+ && Path.GetExtension(lookups[2]) == ".mp3"
+ && Path.GetExtension(lookups[3]) == ".ogg";
+ });
+ }
+
+ private class TestSkin : Skin
+ {
+ public const string SAMPLE_NAME = "test-sample";
+
+ public TestSkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? storage = null, string configurationFilename = "skin.ini")
+ : base(skin, resources, storage, configurationFilename)
+ {
+ }
+
+ public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
+
+ public override IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
+
+ public override ISample GetSample(ISampleInfo sampleInfo) => Samples.AsNonNull().Get(SAMPLE_NAME);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs
index 5aaaca2b2c..d9dfa1d876 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs
@@ -10,6 +10,7 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Textures;
using osu.Framework.Screens;
using osu.Framework.Testing;
@@ -43,6 +44,9 @@ namespace osu.Game.Tests.Visual.Background
[Resolved]
private OsuConfigManager config { get; set; }
+ [Resolved]
+ private IRenderer renderer { get; set; }
+
[SetUpSteps]
public void SetUpSteps()
{
@@ -245,7 +249,7 @@ namespace osu.Game.Tests.Visual.Background
Id = API.LocalUser.Value.Id + 1,
});
- private WorkingBeatmap createTestWorkingBeatmapWithUniqueBackground() => new UniqueBackgroundTestWorkingBeatmap(Audio);
+ private WorkingBeatmap createTestWorkingBeatmapWithUniqueBackground() => new UniqueBackgroundTestWorkingBeatmap(renderer, Audio);
private WorkingBeatmap createTestWorkingBeatmapWithStoryboard() => new TestWorkingBeatmapWithStoryboard(Audio);
private class TestBackgroundScreenDefault : BackgroundScreenDefault
@@ -274,12 +278,15 @@ namespace osu.Game.Tests.Visual.Background
private class UniqueBackgroundTestWorkingBeatmap : TestWorkingBeatmap
{
- public UniqueBackgroundTestWorkingBeatmap(AudioManager audioManager)
+ private readonly IRenderer renderer;
+
+ public UniqueBackgroundTestWorkingBeatmap(IRenderer renderer, AudioManager audioManager)
: base(new Beatmap(), null, audioManager)
{
+ this.renderer = renderer;
}
- protected override Texture GetBackground() => new Texture(1, 1);
+ protected override Texture GetBackground() => renderer.CreateTexture(1, 1);
}
private class TestWorkingBeatmapWithStoryboard : TestWorkingBeatmap
diff --git a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs
index a44ab41d03..cce7ae1922 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs
@@ -10,7 +10,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.OpenGL.Textures;
+using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Textures;
using osu.Game.Configuration;
using osu.Game.Graphics.Backgrounds;
@@ -28,11 +28,9 @@ namespace osu.Game.Tests.Visual.Background
[Resolved]
private SessionStatics statics { get; set; }
- [Cached(typeof(LargeTextureStore))]
- private LookupLoggingTextureStore textureStore = new LookupLoggingTextureStore();
-
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
+ private LookupLoggingTextureStore textureStore;
private SeasonalBackgroundLoader backgroundLoader;
private Container backgroundContainer;
@@ -45,15 +43,32 @@ namespace osu.Game.Tests.Visual.Background
"Backgrounds/bg3"
};
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ {
+ var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+
+ textureStore = new LookupLoggingTextureStore(dependencies.Get());
+ dependencies.CacheAs(typeof(LargeTextureStore), textureStore);
+
+ return dependencies;
+ }
+
[BackgroundDependencyLoader]
private void load(LargeTextureStore wrappedStore)
{
textureStore.AddStore(wrappedStore);
- Add(backgroundContainer = new Container
+ Child = new DependencyProvidingContainer
{
- RelativeSizeAxes = Axes.Both
- });
+ CachedDependencies = new (Type, object)[]
+ {
+ (typeof(LargeTextureStore), textureStore)
+ },
+ Child = backgroundContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both
+ }
+ };
}
[SetUp]
@@ -193,6 +208,11 @@ namespace osu.Game.Tests.Visual.Background
{
public List PerformedLookups { get; } = new List();
+ public LookupLoggingTextureStore(IRenderer renderer)
+ : base(renderer)
+ {
+ }
+
public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT)
{
PerformedLookups.Add(name);
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
index aaccea09d4..5aadd6f56a 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Background
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new OsuConfigManager(LocalStorage));
Dependencies.Cache(Realm);
diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs
index 3f30fa367c..77e781bfe4 100644
--- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs
+++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.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.Allocation;
@@ -27,38 +25,32 @@ namespace osu.Game.Tests.Visual.Collections
{
protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
- private DialogOverlay dialogOverlay;
- private CollectionManager manager;
-
- private RulesetStore rulesets;
- private BeatmapManager beatmapManager;
-
- private ManageCollectionsDialog dialog;
+ private DialogOverlay dialogOverlay = null!;
+ private BeatmapManager beatmapManager = null!;
+ private ManageCollectionsDialog dialog = null!;
[BackgroundDependencyLoader]
private void load(GameHost host)
{
- Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
- Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(new RealmRulesetStore(Realm));
+ Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
base.Content.AddRange(new Drawable[]
{
- manager = new CollectionManager(LocalStorage),
Content,
dialogOverlay = new DialogOverlay(),
});
- Dependencies.Cache(manager);
Dependencies.CacheAs(dialogOverlay);
}
[SetUp]
public void SetUp() => Schedule(() =>
{
- manager.Collections.Clear();
+ Realm.Write(r => r.RemoveAll());
Child = dialog = new ManageCollectionsDialog();
});
@@ -78,17 +70,17 @@ namespace osu.Game.Tests.Visual.Collections
[Test]
public void TestLastItemIsPlaceholder()
{
- AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType().Last().Model));
+ AddAssert("last item is placeholder", () => !dialog.ChildrenOfType().Last().Model.IsManaged);
}
[Test]
public void TestAddCollectionExternal()
{
- AddStep("add collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "First collection" } }));
+ AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "First collection"))));
assertCollectionCount(1);
assertCollectionName(0, "First collection");
- AddStep("add another collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "Second collection" } }));
+ AddStep("add another collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "Second collection"))));
assertCollectionCount(2);
assertCollectionName(1, "Second collection");
}
@@ -108,7 +100,7 @@ namespace osu.Game.Tests.Visual.Collections
[Test]
public void TestAddCollectionViaPlaceholder()
{
- DrawableCollectionListItem placeholderItem = null;
+ DrawableCollectionListItem placeholderItem = null!;
AddStep("focus placeholder", () =>
{
@@ -116,24 +108,37 @@ namespace osu.Game.Tests.Visual.Collections
InputManager.Click(MouseButton.Left);
});
- // Done directly via the collection since InputManager methods cannot add text to textbox...
- AddStep("change collection name", () => placeholderItem.Model.Name.Value = "a");
- assertCollectionCount(1);
- AddAssert("collection now exists", () => manager.Collections.Contains(placeholderItem.Model));
+ assertCollectionCount(0);
- AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType().Last().Model));
+ AddStep("change collection name", () =>
+ {
+ placeholderItem.ChildrenOfType().First().Text = "test text";
+ InputManager.Key(Key.Enter);
+ });
+
+ assertCollectionCount(1);
+
+ AddAssert("last item is placeholder", () => !dialog.ChildrenOfType().Last().Model.IsManaged);
}
[Test]
public void TestRemoveCollectionExternal()
{
- AddStep("add two collections", () => manager.Collections.AddRange(new[]
- {
- new BeatmapCollection { Name = { Value = "1" } },
- new BeatmapCollection { Name = { Value = "2" } },
- }));
+ BeatmapCollection first = null!;
- AddStep("remove first collection", () => manager.Collections.RemoveAt(0));
+ AddStep("add two collections", () =>
+ {
+ Realm.Write(r =>
+ {
+ r.Add(new[]
+ {
+ first = new BeatmapCollection(name: "1"),
+ new BeatmapCollection(name: "2"),
+ });
+ });
+ });
+
+ AddStep("remove first collection", () => Realm.Write(r => r.Remove(first)));
assertCollectionCount(1);
assertCollectionName(0, "2");
}
@@ -143,7 +148,7 @@ namespace osu.Game.Tests.Visual.Collections
{
AddStep("add dropdown", () =>
{
- Add(new CollectionFilterDropdown
+ Add(new CollectionDropdown
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
@@ -151,21 +156,27 @@ namespace osu.Game.Tests.Visual.Collections
Width = 0.4f,
});
});
- AddStep("add two collections with same name", () => manager.Collections.AddRange(new[]
+ AddStep("add two collections with same name", () => Realm.Write(r => r.Add(new[]
{
- new BeatmapCollection { Name = { Value = "1" } },
- new BeatmapCollection { Name = { Value = "1" }, BeatmapHashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } },
- }));
+ new BeatmapCollection(name: "1"),
+ new BeatmapCollection(name: "1")
+ {
+ BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash }
+ },
+ })));
}
[Test]
public void TestRemoveCollectionViaButton()
{
- AddStep("add two collections", () => manager.Collections.AddRange(new[]
+ AddStep("add two collections", () => Realm.Write(r => r.Add(new[]
{
- new BeatmapCollection { Name = { Value = "1" } },
- new BeatmapCollection { Name = { Value = "2" }, BeatmapHashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } },
- }));
+ new BeatmapCollection(name: "1"),
+ new BeatmapCollection(name: "2")
+ {
+ BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash }
+ },
+ })));
assertCollectionCount(2);
@@ -189,19 +200,24 @@ namespace osu.Game.Tests.Visual.Collections
AddStep("click confirmation", () =>
{
InputManager.MoveMouseTo(dialogOverlay.CurrentDialog.ChildrenOfType().First());
- InputManager.Click(MouseButton.Left);
+ InputManager.PressButton(MouseButton.Left);
});
assertCollectionCount(0);
+
+ AddStep("release mouse button", () => InputManager.ReleaseButton(MouseButton.Left));
}
[Test]
public void TestCollectionNotRemovedWhenDialogCancelled()
{
- AddStep("add two collections", () => manager.Collections.AddRange(new[]
+ AddStep("add collection", () => Realm.Write(r => r.Add(new[]
{
- new BeatmapCollection { Name = { Value = "1" }, BeatmapHashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } },
- }));
+ new BeatmapCollection(name: "1")
+ {
+ BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash }
+ },
+ })));
assertCollectionCount(1);
@@ -224,34 +240,67 @@ namespace osu.Game.Tests.Visual.Collections
[Test]
public void TestCollectionRenamedExternal()
{
- AddStep("add two collections", () => manager.Collections.AddRange(new[]
+ BeatmapCollection first = null!;
+
+ AddStep("add two collections", () =>
{
- new BeatmapCollection { Name = { Value = "1" } },
- new BeatmapCollection { Name = { Value = "2" } },
- }));
+ Realm.Write(r =>
+ {
+ r.Add(new[]
+ {
+ first = new BeatmapCollection(name: "1"),
+ new BeatmapCollection(name: "2"),
+ });
+ });
+ });
- AddStep("change first collection name", () => manager.Collections[0].Name.Value = "First");
+ assertCollectionName(0, "1");
+ assertCollectionName(1, "2");
- assertCollectionName(0, "First");
+ AddStep("change first collection name", () => Realm.Write(_ => first.Name = "First"));
+
+ // Item will have moved due to alphabetical sorting.
+ assertCollectionName(0, "2");
+ assertCollectionName(1, "First");
}
[Test]
public void TestCollectionRenamedOnTextChange()
{
- AddStep("add two collections", () => manager.Collections.AddRange(new[]
+ BeatmapCollection first = null!;
+ DrawableCollectionListItem firstItem = null!;
+
+ AddStep("add two collections", () =>
{
- new BeatmapCollection { Name = { Value = "1" } },
- new BeatmapCollection { Name = { Value = "2" } },
- }));
+ Realm.Write(r =>
+ {
+ r.Add(new[]
+ {
+ first = new BeatmapCollection(name: "1"),
+ new BeatmapCollection(name: "2"),
+ });
+ });
+ });
assertCollectionCount(2);
- AddStep("change first collection name", () => dialog.ChildrenOfType().First().Text = "First");
- AddAssert("collection has new name", () => manager.Collections[0].Name.Value == "First");
+ AddStep("focus first collection", () =>
+ {
+ InputManager.MoveMouseTo(firstItem = dialog.ChildrenOfType().First());
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddStep("change first collection name", () =>
+ {
+ firstItem.ChildrenOfType().First().Text = "First";
+ InputManager.Key(Key.Enter);
+ });
+
+ AddUntilStep("collection has new name", () => first.Name == "First");
}
private void assertCollectionCount(int count)
- => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count(i => i.IsCreated.Value) == count);
+ => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count() == count + 1); // +1 for placeholder
private void assertCollectionName(int index, string name)
=> AddUntilStep($"item {index + 1} has correct name", () => dialog.ChildrenOfType().ElementAt(index).ChildrenOfType().First().Text == name);
diff --git a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs
index cb29475ba5..94a71ed50c 100644
--- a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs
+++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs
@@ -190,7 +190,7 @@ namespace osu.Game.Tests.Visual.Components
AddAssert("track stopped", () => !track.IsRunning);
}
- private TestPreviewTrackManager.TestPreviewTrack getTrack() => (TestPreviewTrackManager.TestPreviewTrack)trackManager.Get(null);
+ private TestPreviewTrackManager.TestPreviewTrack getTrack() => (TestPreviewTrackManager.TestPreviewTrack)trackManager.Get(CreateAPIBeatmapSet());
private TestPreviewTrackManager.TestPreviewTrack getOwnedTrack()
{
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 b711d55e15..80a5b4832b 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
@@ -1,13 +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.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;
@@ -39,7 +38,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 +51,21 @@ 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()
{
+ AddAssert("status is none", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.None);
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);
+ AddAssert("status is modified", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.LocallyModified);
}
[Test]
public void TestExitWithoutSave()
{
- EditorBeatmap editorBeatmap = null;
+ EditorBeatmap editorBeatmap = null!;
AddStep("store editor beatmap", () => editorBeatmap = EditorBeatmap);
@@ -78,12 +81,33 @@ 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]
+ [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("track is virtual", () => Beatmap.Value.Track is TrackVirtual);
+
AddAssert("switch track to real track", () =>
{
var setup = Editor.ChildrenOfType().First();
@@ -112,7 +136,22 @@ 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);
+
+ AddStep("test play", () => Editor.TestGameplay());
+
+ AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null);
+ AddStep("confirm save", () => InputManager.Key(Key.Number1));
+
+ AddUntilStep("wait for return to editor", () => Editor.IsCurrentScreen());
+
+ AddAssert("track is still not virtual", () => Beatmap.Value.Track is not TrackVirtual);
+ AddAssert("track length correct", () => Beatmap.Value.Track.Length > 60000);
+
+ AddUntilStep("track not playing", () => !EditorClock.IsRunning);
+ AddStep("play track", () => InputManager.Key(Key.Space));
+ AddUntilStep("wait for track playing", () => EditorClock.IsRunning);
}
[Test]
@@ -141,7 +180,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
@@ -160,7 +199,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;
});
@@ -171,17 +210,19 @@ namespace osu.Game.Tests.Visual.Editing
});
AddAssert("created difficulty has no objects", () => EditorBeatmap.HitObjects.Count == 0);
+ AddAssert("status is modified", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.LocallyModified);
+
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName);
AddStep("save beatmap", () => Editor.Save());
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
&& set != null
- && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName));
+ && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName) && s.Beatmaps.All(b => s.Status == BeatmapOnlineStatus.LocallyModified));
});
}
@@ -227,7 +268,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
@@ -243,7 +284,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;
});
@@ -257,18 +298,18 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("approach rate correctly copied", () => EditorBeatmap.Difficulty.ApproachRate == 4);
AddAssert("combo colours correctly copied", () => EditorBeatmap.BeatmapSkin.AsNonNull().ComboColours.Count == 2);
- AddAssert("status not copied", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.None);
+ AddAssert("status is modified", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.LocallyModified);
AddAssert("online ID not copied", () => EditorBeatmap.BeatmapInfo.OnlineID == -1);
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", () =>
@@ -304,7 +345,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)");
@@ -340,7 +381,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/Editing/TestSceneEditorClock.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs
index 3be6371f28..3c6820e49b 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.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.Allocation;
using osu.Framework.Graphics;
@@ -59,15 +57,15 @@ namespace osu.Game.Tests.Visual.Editing
{
AddStep("reset clock", () => Clock.Seek(0));
- AddStep("start clock", Clock.Start);
+ AddStep("start clock", () => Clock.Start());
AddAssert("clock running", () => Clock.IsRunning);
AddStep("seek near end", () => Clock.Seek(Clock.TrackLength - 250));
AddUntilStep("clock stops", () => !Clock.IsRunning);
- AddAssert("clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength);
+ AddUntilStep("clock stopped at end", () => Clock.CurrentTime, () => Is.EqualTo(Clock.TrackLength));
- AddStep("start clock again", Clock.Start);
+ AddStep("start clock again", () => Clock.Start());
AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
}
@@ -76,32 +74,32 @@ namespace osu.Game.Tests.Visual.Editing
{
AddStep("reset clock", () => Clock.Seek(0));
- AddStep("stop clock", Clock.Stop);
+ AddStep("stop clock", () => Clock.Stop());
AddAssert("clock stopped", () => !Clock.IsRunning);
AddStep("seek exactly to end", () => Clock.Seek(Clock.TrackLength));
- AddAssert("clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength);
+ AddAssert("clock stopped at end", () => Clock.CurrentTime, () => Is.EqualTo(Clock.TrackLength));
- AddStep("start clock again", Clock.Start);
+ AddStep("start clock again", () => Clock.Start());
AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
}
[Test]
public void TestClampWhenSeekOutsideBeatmapBounds()
{
- AddStep("stop clock", Clock.Stop);
+ AddStep("stop clock", () => Clock.Stop());
AddStep("seek before start time", () => Clock.Seek(-1000));
- AddAssert("time is clamped to 0", () => Clock.CurrentTime == 0);
+ AddAssert("time is clamped to 0", () => Clock.CurrentTime, () => Is.EqualTo(0));
AddStep("seek beyond track length", () => Clock.Seek(Clock.TrackLength + 1000));
- AddAssert("time is clamped to track length", () => Clock.CurrentTime == Clock.TrackLength);
+ AddAssert("time is clamped to track length", () => Clock.CurrentTime, () => Is.EqualTo(Clock.TrackLength));
AddStep("seek smoothly before start time", () => Clock.SeekSmoothlyTo(-1000));
- AddAssert("time is clamped to 0", () => Clock.CurrentTime == 0);
+ AddUntilStep("time is clamped to 0", () => Clock.CurrentTime, () => Is.EqualTo(0));
AddStep("seek smoothly beyond track length", () => Clock.SeekSmoothlyTo(Clock.TrackLength + 1000));
- AddAssert("time is clamped to track length", () => Clock.CurrentTime == Clock.TrackLength);
+ AddUntilStep("time is clamped to track length", () => Clock.CurrentTime, () => Is.EqualTo(Clock.TrackLength));
}
protected override void Dispose(bool isDisposing)
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
index bcf02cd814..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,7 +25,9 @@ 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);
}
@@ -40,6 +44,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.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs
index c8ca273db5..d24baa6f63 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs
@@ -60,17 +60,17 @@ namespace osu.Game.Tests.Visual.Editing
// Forwards
AddStep("Seek(0)", () => Clock.Seek(0));
- AddAssert("Time = 0", () => Clock.CurrentTime == 0);
+ checkTime(0);
AddStep("Seek(33)", () => Clock.Seek(33));
- AddAssert("Time = 33", () => Clock.CurrentTime == 33);
+ checkTime(33);
AddStep("Seek(89)", () => Clock.Seek(89));
- AddAssert("Time = 89", () => Clock.CurrentTime == 89);
+ checkTime(89);
// Backwards
AddStep("Seek(25)", () => Clock.Seek(25));
- AddAssert("Time = 25", () => Clock.CurrentTime == 25);
+ checkTime(25);
AddStep("Seek(0)", () => Clock.Seek(0));
- AddAssert("Time = 0", () => Clock.CurrentTime == 0);
+ checkTime(0);
}
///
@@ -83,19 +83,19 @@ namespace osu.Game.Tests.Visual.Editing
reset();
AddStep("Seek(0), Snap", () => Clock.SeekSnapped(0));
- AddAssert("Time = 0", () => Clock.CurrentTime == 0);
+ checkTime(0);
AddStep("Seek(50), Snap", () => Clock.SeekSnapped(50));
- AddAssert("Time = 50", () => Clock.CurrentTime == 50);
+ checkTime(50);
AddStep("Seek(100), Snap", () => Clock.SeekSnapped(100));
- AddAssert("Time = 100", () => Clock.CurrentTime == 100);
+ checkTime(100);
AddStep("Seek(175), Snap", () => Clock.SeekSnapped(175));
- AddAssert("Time = 175", () => Clock.CurrentTime == 175);
+ checkTime(175);
AddStep("Seek(350), Snap", () => Clock.SeekSnapped(350));
- AddAssert("Time = 350", () => Clock.CurrentTime == 350);
+ checkTime(350);
AddStep("Seek(400), Snap", () => Clock.SeekSnapped(400));
- AddAssert("Time = 400", () => Clock.CurrentTime == 400);
+ checkTime(400);
AddStep("Seek(450), Snap", () => Clock.SeekSnapped(450));
- AddAssert("Time = 450", () => Clock.CurrentTime == 450);
+ checkTime(450);
}
///
@@ -108,17 +108,17 @@ namespace osu.Game.Tests.Visual.Editing
reset();
AddStep("Seek(24), Snap", () => Clock.SeekSnapped(24));
- AddAssert("Time = 0", () => Clock.CurrentTime == 0);
+ checkTime(0);
AddStep("Seek(26), Snap", () => Clock.SeekSnapped(26));
- AddAssert("Time = 50", () => Clock.CurrentTime == 50);
+ checkTime(50);
AddStep("Seek(150), Snap", () => Clock.SeekSnapped(150));
- AddAssert("Time = 100", () => Clock.CurrentTime == 100);
+ checkTime(100);
AddStep("Seek(170), Snap", () => Clock.SeekSnapped(170));
- AddAssert("Time = 175", () => Clock.CurrentTime == 175);
+ checkTime(175);
AddStep("Seek(274), Snap", () => Clock.SeekSnapped(274));
- AddAssert("Time = 175", () => Clock.CurrentTime == 175);
+ checkTime(175);
AddStep("Seek(276), Snap", () => Clock.SeekSnapped(276));
- AddAssert("Time = 350", () => Clock.CurrentTime == 350);
+ checkTime(350);
}
///
@@ -130,15 +130,15 @@ namespace osu.Game.Tests.Visual.Editing
reset();
AddStep("SeekForward", () => Clock.SeekForward());
- AddAssert("Time = 50", () => Clock.CurrentTime == 50);
+ checkTime(50);
AddStep("SeekForward", () => Clock.SeekForward());
- AddAssert("Time = 100", () => Clock.CurrentTime == 100);
+ checkTime(100);
AddStep("SeekForward", () => Clock.SeekForward());
- AddAssert("Time = 200", () => Clock.CurrentTime == 200);
+ checkTime(200);
AddStep("SeekForward", () => Clock.SeekForward());
- AddAssert("Time = 400", () => Clock.CurrentTime == 400);
+ checkTime(400);
AddStep("SeekForward", () => Clock.SeekForward());
- AddAssert("Time = 450", () => Clock.CurrentTime == 450);
+ checkTime(450);
}
///
@@ -150,17 +150,17 @@ namespace osu.Game.Tests.Visual.Editing
reset();
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
- AddAssert("Time = 50", () => Clock.CurrentTime == 50);
+ checkTime(50);
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
- AddAssert("Time = 100", () => Clock.CurrentTime == 100);
+ checkTime(100);
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
- AddAssert("Time = 175", () => Clock.CurrentTime == 175);
+ checkTime(175);
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
- AddAssert("Time = 350", () => Clock.CurrentTime == 350);
+ checkTime(350);
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
- AddAssert("Time = 400", () => Clock.CurrentTime == 400);
+ checkTime(400);
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
- AddAssert("Time = 450", () => Clock.CurrentTime == 450);
+ checkTime(450);
}
///
@@ -174,28 +174,28 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("Seek(49)", () => Clock.Seek(49));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
- AddAssert("Time = 50", () => Clock.CurrentTime == 50);
+ checkTime(50);
AddStep("Seek(49.999)", () => Clock.Seek(49.999));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
- AddAssert("Time = 100", () => Clock.CurrentTime == 100);
+ checkTime(100);
AddStep("Seek(99)", () => Clock.Seek(99));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
- AddAssert("Time = 100", () => Clock.CurrentTime == 100);
+ checkTime(100);
AddStep("Seek(99.999)", () => Clock.Seek(99.999));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
- AddAssert("Time = 100", () => Clock.CurrentTime == 150);
+ checkTime(150);
AddStep("Seek(174)", () => Clock.Seek(174));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
- AddAssert("Time = 175", () => Clock.CurrentTime == 175);
+ checkTime(175);
AddStep("Seek(349)", () => Clock.Seek(349));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
- AddAssert("Time = 350", () => Clock.CurrentTime == 350);
+ checkTime(350);
AddStep("Seek(399)", () => Clock.Seek(399));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
- AddAssert("Time = 400", () => Clock.CurrentTime == 400);
+ checkTime(400);
AddStep("Seek(449)", () => Clock.Seek(449));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
- AddAssert("Time = 450", () => Clock.CurrentTime == 450);
+ checkTime(450);
}
///
@@ -208,15 +208,15 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("Seek(450)", () => Clock.Seek(450));
AddStep("SeekBackward", () => Clock.SeekBackward());
- AddAssert("Time = 400", () => Clock.CurrentTime == 400);
+ checkTime(400);
AddStep("SeekBackward", () => Clock.SeekBackward());
- AddAssert("Time = 350", () => Clock.CurrentTime == 350);
+ checkTime(350);
AddStep("SeekBackward", () => Clock.SeekBackward());
- AddAssert("Time = 150", () => Clock.CurrentTime == 150);
+ checkTime(150);
AddStep("SeekBackward", () => Clock.SeekBackward());
- AddAssert("Time = 50", () => Clock.CurrentTime == 50);
+ checkTime(50);
AddStep("SeekBackward", () => Clock.SeekBackward());
- AddAssert("Time = 0", () => Clock.CurrentTime == 0);
+ checkTime(0);
}
///
@@ -229,17 +229,17 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("Seek(450)", () => Clock.Seek(450));
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
- AddAssert("Time = 400", () => Clock.CurrentTime == 400);
+ checkTime(400);
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
- AddAssert("Time = 350", () => Clock.CurrentTime == 350);
+ checkTime(350);
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
- AddAssert("Time = 175", () => Clock.CurrentTime == 175);
+ checkTime(175);
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
- AddAssert("Time = 100", () => Clock.CurrentTime == 100);
+ checkTime(100);
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
- AddAssert("Time = 50", () => Clock.CurrentTime == 50);
+ checkTime(50);
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
- AddAssert("Time = 0", () => Clock.CurrentTime == 0);
+ checkTime(0);
}
///
@@ -253,16 +253,16 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("Seek(451)", () => Clock.Seek(451));
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
- AddAssert("Time = 450", () => Clock.CurrentTime == 450);
+ checkTime(450);
AddStep("Seek(450.999)", () => Clock.Seek(450.999));
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
- AddAssert("Time = 450", () => Clock.CurrentTime == 450);
+ checkTime(450);
AddStep("Seek(401)", () => Clock.Seek(401));
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
- AddAssert("Time = 400", () => Clock.CurrentTime == 400);
+ checkTime(400);
AddStep("Seek(401.999)", () => Clock.Seek(401.999));
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
- AddAssert("Time = 400", () => Clock.CurrentTime == 400);
+ checkTime(400);
}
///
@@ -297,9 +297,11 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("Time < lastTime", () => Clock.CurrentTime < lastTime);
}
- AddAssert("Time = 0", () => Clock.CurrentTime == 0);
+ checkTime(0);
}
+ private void checkTime(double expectedTime) => AddAssert($"Current time is {expectedTime}", () => Clock.CurrentTime, () => Is.EqualTo(expectedTime));
+
private void reset()
{
AddStep("Reset", () => Clock.Seek(0));
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs
index 6f248f1247..924396ce03 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs
@@ -4,7 +4,6 @@
#nullable disable
using NUnit.Framework;
-using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets;
@@ -120,7 +119,7 @@ namespace osu.Game.Tests.Visual.Editing
private void pressAndCheckTime(Key key, double expectedTime)
{
AddStep($"press {key}", () => InputManager.Key(key));
- AddUntilStep($"time is {expectedTime}", () => Precision.AlmostEquals(expectedTime, EditorClock.CurrentTime, 1));
+ AddUntilStep($"time is {expectedTime}", () => EditorClock.CurrentTime, () => Is.EqualTo(expectedTime).Within(1));
}
}
}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs
index fd103ff70f..630d048867 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
{
@@ -18,16 +17,18 @@ namespace osu.Game.Tests.Visual.Editing
{
double initialVisibleRange = 0;
+ AddUntilStep("wait for load", () => MusicController.TrackLoaded);
+
AddStep("reset zoom", () => TimelineArea.Timeline.Zoom = 100);
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]
@@ -35,6 +36,8 @@ namespace osu.Game.Tests.Visual.Editing
{
double initialVisibleRange = 0;
+ AddUntilStep("wait for load", () => MusicController.TrackLoaded);
+
AddStep("reset timeline size", () => TimelineArea.Timeline.Width = 1);
AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs
index 9dc403814b..ce418f33f0 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Editing
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(30)
},
- scrollContainer = new ZoomableScrollContainer
+ scrollContainer = new ZoomableScrollContainer(1, 60, 1)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -80,21 +80,6 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
}
- [Test]
- public void TestZoomRangeUpdate()
- {
- AddStep("set zoom to 2", () => scrollContainer.Zoom = 2);
- AddStep("set min zoom to 5", () => scrollContainer.MinZoom = 5);
- AddAssert("zoom = 5", () => scrollContainer.Zoom == 5);
-
- AddStep("set max zoom to 10", () => scrollContainer.MaxZoom = 10);
- AddAssert("zoom = 5", () => scrollContainer.Zoom == 5);
-
- AddStep("set min zoom to 20", () => scrollContainer.MinZoom = 20);
- AddStep("set max zoom to 40", () => scrollContainer.MaxZoom = 40);
- AddAssert("zoom = 20", () => scrollContainer.Zoom == 20);
- }
-
[Test]
public void TestZoom0()
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
index 47c8dc0f8d..f2fe55d719 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
@@ -31,20 +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);
+ // we only want this beatmap for time reference.
+ var referenceBeatmap = CreateBeatmap(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));
- seekTo(beatmap.Beatmap.Breaks[0].StartTime);
+ seekTo(referenceBeatmap.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));
- seekTo(beatmap.Beatmap.HitObjects[^1].GetEndTime());
+ seekTo(referenceBeatmap.HitObjects[^1].GetEndTime());
AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true);
AddAssert("score has combo", () => getResultsScreen().Score.Combo > 100);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
index d4f3d0f390..d6c49b026e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
@@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Gameplay
(typeof(ScoreProcessor), actualComponentsContainer.Dependencies.Get()),
(typeof(HealthProcessor), actualComponentsContainer.Dependencies.Get()),
(typeof(GameplayState), actualComponentsContainer.Dependencies.Get()),
- (typeof(GameplayClock), actualComponentsContainer.Dependencies.Get())
+ (typeof(IGameplayClock), actualComponentsContainer.Dependencies.Get())
},
};
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/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
index a79ba0ae5d..334d8f1452 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
@@ -263,27 +263,30 @@ namespace osu.Game.Tests.Visual.Gameplay
return beatmap;
}
- private void createTest(IBeatmap beatmap, Action overrideAction = null) => AddStep("create test", () =>
+ private void createTest(IBeatmap beatmap, Action overrideAction = null)
{
- var ruleset = new TestScrollingRuleset();
-
- drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo));
- drawableRuleset.FrameStablePlayback = false;
-
- overrideAction?.Invoke(drawableRuleset);
-
- Child = new Container
+ AddStep("create test", () =>
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Y,
- Height = 0.75f,
- Width = 400,
- Masking = true,
- Clock = new FramedClock(testClock),
- Child = drawableRuleset
- };
- });
+ var ruleset = new TestScrollingRuleset();
+
+ drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo));
+ drawableRuleset.FrameStablePlayback = false;
+
+ overrideAction?.Invoke(drawableRuleset);
+
+ Child = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Y,
+ Height = 0.75f,
+ Width = 400,
+ Masking = true,
+ Clock = new FramedClock(testClock),
+ Child = drawableRuleset
+ };
+ });
+ }
#region Ruleset
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs
index 97ffbfc796..ef74024b4b 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs
@@ -137,13 +137,13 @@ namespace osu.Game.Tests.Visual.Gameplay
private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time);
- private void confirmSeek(double time) => AddUntilStep($"wait for seek to {time}", () => consumer.Clock.CurrentTime == time);
+ private void confirmSeek(double time) => AddUntilStep($"wait for seek to {time}", () => consumer.Clock.CurrentTime, () => Is.EqualTo(time));
private void checkFrameCount(int frames) =>
- AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames == frames);
+ AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames, () => Is.EqualTo(frames));
private void checkRate(double rate) =>
- AddAssert($"clock rate is {rate}", () => consumer.Clock.Rate == rate);
+ AddAssert($"clock rate is {rate}", () => consumer.Clock.Rate, () => Is.EqualTo(rate));
public class ClockConsumingChild : CompositeDrawable
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs
index f1084bca5f..1fe2dfd4df 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.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;
@@ -21,22 +19,22 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestAllSamplesStopDuringSeek()
{
- DrawableSlider slider = null;
- PoolableSkinnableSample[] samples = null;
- ISamplePlaybackDisabler sampleDisabler = null;
+ DrawableSlider? slider = null;
+ PoolableSkinnableSample[] samples = null!;
+ ISamplePlaybackDisabler sampleDisabler = null!;
AddUntilStep("get variables", () =>
{
sampleDisabler = Player;
slider = Player.ChildrenOfType().MinBy(s => s.HitObject.StartTime);
- samples = slider?.ChildrenOfType().ToArray();
+ samples = slider.ChildrenOfType().ToArray();
return slider != null;
});
AddUntilStep("wait for slider sliding then seek", () =>
{
- if (!slider.Tracking.Value)
+ if (slider?.Tracking.Value != true)
return false;
if (!samples.Any(s => s.Playing))
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
index dd0f965914..3e2698bc05 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
@@ -38,8 +38,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached]
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
- [Cached]
- private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());
+ [Cached(typeof(IGameplayClock))]
+ private readonly IGameplayClock gameplayClock = new GameplayClock(new FramedClock());
// best way to check without exposing.
private Drawable hideTarget => hudOverlay.KeyCounter;
@@ -159,6 +159,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType().Single().Alpha == 0);
+ AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType().All(c => c.ComponentsLoaded));
AddStep("bind on update", () =>
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs
index 7f4276f819..0d80d29cab 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs
@@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public double FirstHitObjectTime => DrawableRuleset.Objects.First().StartTime;
- public double GameplayClockTime => GameplayClockContainer.GameplayClock.CurrentTime;
+ public double GameplayClockTime => GameplayClockContainer.CurrentTime;
protected override void UpdateAfterChildren()
{
@@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Gameplay
if (!FirstFrameClockTime.HasValue)
{
- FirstFrameClockTime = GameplayClockContainer.GameplayClock.CurrentTime;
+ FirstFrameClockTime = GameplayClockContainer.CurrentTime;
AddInternal(new OsuSpriteText
{
Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} "
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);
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs
index 3e637f1870..789e7e770f 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Gameplay
base.SetUpSteps();
AddUntilStep("gameplay has started",
- () => Player.GameplayClockContainer.GameplayClock.CurrentTime > Player.DrawableRuleset.GameplayStartTime);
+ () => Player.GameplayClockContainer.CurrentTime > Player.DrawableRuleset.GameplayStartTime);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
index 5bd0a29308..cad8c62233 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public TestScenePause()
{
- base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both });
+ base.Content.Add(content = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both });
}
[SetUpSteps]
@@ -313,7 +313,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("pause again", () =>
{
Player.Pause();
- return !Player.GameplayClockContainer.GameplayClock.IsRunning;
+ return !Player.GameplayClockContainer.IsRunning;
});
AddAssert("loop is playing", () => getLoop().IsPlaying);
@@ -378,7 +378,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("pause overlay " + (isShown ? "shown" : "hidden"), () => Player.PauseOverlayVisible == isShown);
private void confirmClockRunning(bool isRunning) =>
- AddUntilStep("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.GameplayClock.IsRunning == isRunning);
+ AddUntilStep("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.IsRunning == isRunning);
protected override bool AllowFail => true;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
index 56588e4d4e..05474e3d39 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
@@ -308,17 +308,18 @@ namespace osu.Game.Tests.Visual.Gameplay
}
}
- [TestCase(false, 1.0, false)] // not charging, above cutoff --> no warning
- [TestCase(true, 0.1, false)] // charging, below cutoff --> no warning
- [TestCase(false, 0.25, true)] // not charging, at cutoff --> warning
- public void TestLowBatteryNotification(bool isCharging, double chargeLevel, bool shouldWarn)
+ [TestCase(true, 1.0, false)] // on battery, above cutoff --> no warning
+ [TestCase(false, 0.1, false)] // not on battery, below cutoff --> no warning
+ [TestCase(true, 0.25, true)] // on battery, at cutoff --> warning
+ [TestCase(true, null, false)] // on battery, level unknown --> no warning
+ public void TestLowBatteryNotification(bool onBattery, double? chargeLevel, bool shouldWarn)
{
AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce).Value = false);
// set charge status and level
AddStep("load player", () => resetPlayer(false, () =>
{
- batteryInfo.SetCharging(isCharging);
+ batteryInfo.SetOnBattery(onBattery);
batteryInfo.SetChargeLevel(chargeLevel);
}));
AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready);
@@ -408,19 +409,19 @@ namespace osu.Game.Tests.Visual.Gameplay
///
private class LocalBatteryInfo : BatteryInfo
{
- private bool isCharging = true;
- private double chargeLevel = 1;
+ private bool onBattery;
+ private double? chargeLevel;
- public override bool IsCharging => isCharging;
+ public override bool OnBattery => onBattery;
- public override double ChargeLevel => chargeLevel;
+ public override double? ChargeLevel => chargeLevel;
- public void SetCharging(bool value)
+ public void SetOnBattery(bool value)
{
- isCharging = value;
+ onBattery = value;
}
- public void SetChargeLevel(double value)
+ public void SetChargeLevel(double? value)
{
chargeLevel = value;
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs
new file mode 100644
index 0000000000..ddb585a73c
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs
@@ -0,0 +1,145 @@
+// 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;
+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;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestScenePlayerLocalScoreImport : PlayerTestScene
+ {
+ private BeatmapManager beatmaps = null!;
+ private RulesetStore rulesets = null!;
+
+ private BeatmapSetInfo? importedSet;
+
+ [BackgroundDependencyLoader]
+ private void load(GameHost host, AudioManager audio)
+ {
+ Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, Scheduler, API));
+ 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();
+
+ protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false);
+
+ protected override bool HasCustomSteps => true;
+
+ protected override bool AllowFail => allowFail;
+
+ private bool allowFail;
+
+ [SetUp]
+ public void SetUp()
+ {
+ allowFail = false;
+ customRuleset = null;
+ }
+
+ [Test]
+ public void TestSaveFailedReplay()
+ {
+ AddStep("allow fail", () => allowFail = true);
+
+ 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);
+
+ 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()
+ {
+ 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 CustomRuleset();
+
+ AddStep("import custom ruleset", () => Realm.Write(r => r.Add(createCustomRuleset().RulesetInfo)));
+ AddStep("set custom ruleset", () => customRuleset = createCustomRuleset());
+
+ 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()));
+
+ 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";
+
+ int ILegacyRuleset.LegacyID => -1;
+
+ public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
index e0c8989389..d1bdfb1dfa 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
@@ -239,7 +239,7 @@ namespace osu.Game.Tests.Visual.Gameplay
createPlayerTest(false, r =>
{
var beatmap = createTestBeatmap(r);
- beatmap.BeatmapInfo.OnlineID = -1;
+ beatmap.BeatmapInfo.ResetOnlineInfo();
return beatmap;
});
@@ -365,21 +365,9 @@ namespace osu.Game.Tests.Visual.Gameplay
ImportedScore = 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.
+ // 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.
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs
index 1fa4885b7a..618ffbcb0e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs
@@ -158,21 +158,24 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("clean up", () => drawableRuleset.NewResult -= onNewResult);
}
- private void createTest(IBeatmap beatmap, int poolSize, Func createClock = null) => AddStep("create test", () =>
+ private void createTest(IBeatmap beatmap, int poolSize, Func createClock = null)
{
- var ruleset = new TestPoolingRuleset();
-
- drawableRuleset = (TestDrawablePoolingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo));
- drawableRuleset.FrameStablePlayback = true;
- drawableRuleset.PoolSize = poolSize;
-
- Child = new Container
+ AddStep("create test", () =>
{
- RelativeSizeAxes = Axes.Both,
- Clock = createClock?.Invoke() ?? new FramedOffsetClock(Clock, false) { Offset = -Clock.CurrentTime },
- Child = drawableRuleset
- };
- });
+ var ruleset = new TestPoolingRuleset();
+
+ drawableRuleset = (TestDrawablePoolingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo));
+ drawableRuleset.FrameStablePlayback = true;
+ drawableRuleset.PoolSize = poolSize;
+
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Clock = createClock?.Invoke() ?? new FramedOffsetClock(Clock, false) { Offset = -Clock.CurrentTime },
+ Child = drawableRuleset
+ };
+ });
+ }
#region Ruleset
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
index 10a6b196b0..9d70d1ef33 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
@@ -7,7 +7,6 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online;
-using osu.Game.Online.API.Requests.Responses;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
@@ -15,7 +14,6 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
-using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
@@ -30,9 +28,6 @@ namespace osu.Game.Tests.Visual.Gameplay
{
private const long online_score_id = 2553163309;
- [Resolved]
- private RulesetStore rulesets { get; set; }
-
private TestReplayDownloadButton downloadButton;
[Resolved]
@@ -142,6 +137,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,21 +206,18 @@ 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) => new ScoreInfo
{
- return new APIScore
+ OnlineID = hasOnlineId ? online_score_id : 0,
+ Ruleset = new OsuRuleset().RulesetInfo,
+ BeatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(),
+ Hash = replayAvailable ? "online" : string.Empty,
+ User = new APIUser
{
- OnlineID = online_score_id,
- RulesetID = 0,
- Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(),
- HasReplay = replayAvailable,
- User = new APIUser
- {
- Id = 39828,
- Username = @"WubWoofWolf",
- }
- }.CreateScoreInfo(rulesets, beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First());
- }
+ Id = 39828,
+ Username = @"WubWoofWolf",
+ }
+ };
private class TestReplayDownloadButton : ReplayDownloadButton
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
index 54b2e66f2f..b3401c916b 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
@@ -3,10 +3,10 @@
#nullable disable
+using System;
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.Framework.Graphics.Shapes;
@@ -40,8 +40,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private TestReplayRecorder recorder;
- [Cached]
- private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
+ private GameplayState gameplayState;
[SetUpSteps]
public void SetUpSteps()
@@ -52,81 +51,15 @@ namespace osu.Game.Tests.Visual.Gameplay
{
replay = new Replay();
- Add(new GridContainer
+ gameplayState = TestGameplayState.Create(new OsuRuleset());
+ gameplayState.Score.Replay = replay;
+
+ Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
- Content = new[]
- {
- new Drawable[]
- {
- recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
- {
- Recorder = recorder = new TestReplayRecorder(new Score
- {
- Replay = replay,
- ScoreInfo =
- {
- BeatmapInfo = gameplayState.Beatmap.BeatmapInfo,
- Ruleset = new OsuRuleset().RulesetInfo,
- }
- })
- {
- ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
- },
- Child = new Container
- {
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- new Box
- {
- Colour = Color4.Brown,
- RelativeSizeAxes = Axes.Both,
- },
- new OsuSpriteText
- {
- Text = "Recording",
- Scale = new Vector2(3),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- },
- new TestInputConsumer()
- }
- },
- }
- },
- new Drawable[]
- {
- playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
- {
- ReplayInputHandler = new TestFramedReplayInputHandler(replay)
- {
- GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
- },
- Child = new Container
- {
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- new Box
- {
- Colour = Color4.DarkBlue,
- RelativeSizeAxes = Axes.Both,
- },
- new OsuSpriteText
- {
- Text = "Playback",
- Scale = new Vector2(3),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- },
- new TestInputConsumer()
- }
- },
- }
- }
- }
- });
+ CachedDependencies = new (Type, object)[] { (typeof(GameplayState), gameplayState) },
+ Child = createContent(),
+ };
});
}
@@ -203,6 +136,74 @@ namespace osu.Game.Tests.Visual.Gameplay
recorder = null;
}
+ private Drawable createContent() => new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
+ {
+ Recorder = recorder = new TestReplayRecorder(gameplayState.Score)
+ {
+ ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
+ },
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Brown,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Text = "Recording",
+ Scale = new Vector2(3),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new TestInputConsumer()
+ }
+ },
+ }
+ },
+ new Drawable[]
+ {
+ playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
+ {
+ ReplayInputHandler = new TestFramedReplayInputHandler(replay)
+ {
+ GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
+ },
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.DarkBlue,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Text = "Playback",
+ Scale = new Vector2(3),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new TestInputConsumer()
+ }
+ },
+ }
+ }
+ }
+ };
+
public class TestFramedReplayInputHandler : FramedReplayInputHandler
{
public TestFramedReplayInputHandler(Replay replay)
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
index f319290441..bd274dfef5 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
@@ -14,6 +14,7 @@ using osu.Game.Overlays.Settings;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
+using osu.Game.Skinning;
using osu.Game.Skinning.Editor;
using osuTK.Input;
@@ -33,6 +34,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{
base.SetUpSteps();
+ AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded));
+
AddStep("reload skin editor", () =>
{
skinEditor?.Expire();
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs
index 3eb92b3e97..e29101ba8d 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs
@@ -29,8 +29,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached]
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
- [Cached]
- private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());
+ [Cached(typeof(IGameplayClock))]
+ private readonly IGameplayClock gameplayClock = new GameplayClock(new FramedClock());
[SetUpSteps]
public void SetUpSteps()
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
index eacab6d34f..ef56f456ea 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
@@ -10,6 +10,7 @@ using osu.Framework.Testing;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD;
+using osu.Game.Skinning;
namespace osu.Game.Tests.Visual.Gameplay
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
index 16593effd6..d4fba76c37 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
@@ -13,7 +13,6 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs
index ee2827122d..00e4171eac 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs
@@ -36,8 +36,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached]
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
- [Cached]
- private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());
+ [Cached(typeof(IGameplayClock))]
+ private readonly IGameplayClock gameplayClock = new GameplayClock(new FramedClock());
private IEnumerable hudOverlays => CreatedDrawables.OfType();
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs
index 36b07043dc..0ba112ec40 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs
@@ -13,7 +13,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
index e1fc65404d..b6b3650c83 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
@@ -6,6 +6,7 @@
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Timing;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osuTK;
@@ -22,7 +23,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private double increment;
private GameplayClockContainer gameplayClockContainer;
- private GameplayClock gameplayClock;
+ private IFrameBasedClock gameplayClock;
private const double skip_time = 6000;
@@ -33,7 +34,6 @@ namespace osu.Game.Tests.Visual.Gameplay
increment = skip_time;
var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
- working.LoadTrack();
Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0)
{
@@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Gameplay
};
gameplayClockContainer.Start();
- gameplayClock = gameplayClockContainer.GameplayClock;
+ gameplayClock = gameplayClockContainer;
});
[Test]
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
index 07efb25b46..3487f4dbff 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
@@ -1,161 +1,76 @@
// 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.Allocation;
+using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
-using osu.Framework.Utils;
-using osu.Framework.Timing;
-using osu.Game.Graphics;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.HUD;
+using osu.Game.Skinning;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
- public class TestSceneSongProgress : OsuTestScene
+ public class TestSceneSongProgress : SkinnableHUDComponentTestScene
{
- private SongProgress progress;
- private TestSongProgressGraph graph;
- private readonly Container progressContainer;
+ private GameplayClockContainer gameplayClockContainer = null!;
- private readonly StopwatchClock clock;
- private readonly FramedClock framedClock;
+ private const double skip_target_time = -2000;
- [Cached]
- private readonly GameplayClock gameplayClock;
-
- public TestSceneSongProgress()
+ [BackgroundDependencyLoader]
+ private void load()
{
- clock = new StopwatchClock();
- gameplayClock = new GameplayClock(framedClock = new FramedClock(clock));
+ Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
- Add(progressContainer = new Container
- {
- RelativeSizeAxes = Axes.X,
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
- Height = 100,
- Y = -100,
- Child = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = OsuColour.Gray(1),
- }
- });
+ Add(gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time));
+
+ Dependencies.CacheAs(gameplayClockContainer);
}
[SetUpSteps]
public void SetupSteps()
{
- AddStep("add new song progress", () =>
- {
- if (progress != null)
- {
- progress.Expire();
- progress = null;
- }
-
- progressContainer.Add(progress = new SongProgress
- {
- RelativeSizeAxes = Axes.X,
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- });
- });
-
- AddStep("add new big graph", () =>
- {
- if (graph != null)
- {
- graph.Expire();
- graph = null;
- }
-
- Add(graph = new TestSongProgressGraph
- {
- RelativeSizeAxes = Axes.X,
- Height = 200,
- Anchor = Anchor.TopLeft,
- Origin = Anchor.TopLeft,
- });
- });
-
- AddStep("reset clock", clock.Reset);
- }
-
- [Test]
- public void TestGraphRecreation()
- {
- AddAssert("ensure not created", () => graph.CreationCount == 0);
- AddStep("display values", displayRandomValues);
- AddUntilStep("wait for creation count", () => graph.CreationCount == 1);
- AddRepeatStep("new values", displayRandomValues, 5);
- AddWaitStep("wait some", 5);
- AddAssert("ensure recreation debounced", () => graph.CreationCount == 2);
+ AddStep("reset clock", () => gameplayClockContainer.Reset());
+ AddStep("set hit objects", setHitObjects);
}
[Test]
public void TestDisplay()
{
- AddStep("display max values", displayMaxValues);
- AddUntilStep("wait for graph", () => graph.CreationCount == 1);
- AddStep("start", clock.Start);
- AddStep("allow seeking", () => progress.AllowSeeking.Value = true);
- AddStep("hide graph", () => progress.ShowGraph.Value = false);
- AddStep("disallow seeking", () => progress.AllowSeeking.Value = false);
- AddStep("allow seeking", () => progress.AllowSeeking.Value = true);
- AddStep("show graph", () => progress.ShowGraph.Value = true);
- AddStep("stop", clock.Stop);
+ AddStep("seek to intro", () => gameplayClockContainer.Seek(skip_target_time));
+ AddStep("start", gameplayClockContainer.Start);
+ AddStep("stop", gameplayClockContainer.Stop);
}
- private void displayRandomValues()
+ [Test]
+ public void TestToggleSeeking()
{
- var objects = new List();
- for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000)
- objects.Add(new HitObject { StartTime = i });
+ DefaultSongProgress getDefaultProgress() => this.ChildrenOfType().Single();
- replaceObjects(objects);
+ AddStep("allow seeking", () => getDefaultProgress().AllowSeeking.Value = true);
+ AddStep("hide graph", () => getDefaultProgress().ShowGraph.Value = false);
+ AddStep("disallow seeking", () => getDefaultProgress().AllowSeeking.Value = false);
+ AddStep("allow seeking", () => getDefaultProgress().AllowSeeking.Value = true);
+ AddStep("show graph", () => getDefaultProgress().ShowGraph.Value = true);
}
- private void displayMaxValues()
+ private void setHitObjects()
{
var objects = new List();
for (double i = 0; i < 5000; i++)
objects.Add(new HitObject { StartTime = i });
- replaceObjects(objects);
+ this.ChildrenOfType().ForEach(progress => progress.Objects = objects);
}
- private void replaceObjects(List objects)
- {
- progress.Objects = objects;
- graph.Objects = objects;
+ protected override Drawable CreateDefaultImplementation() => new DefaultSongProgress();
- progress.RequestSeek = pos => clock.Seek(pos);
- }
-
- protected override void Update()
- {
- base.Update();
- framedClock.ProcessFrame();
- }
-
- private class TestSongProgressGraph : SongProgressGraph
- {
- public int CreationCount { get; private set; }
-
- protected override void RecreateGraph()
- {
- base.RecreateGraph();
- CreationCount++;
- }
- }
+ protected override Drawable CreateLegacyImplementation() => new LegacySongProgress();
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgressGraph.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgressGraph.cs
new file mode 100644
index 0000000000..2fa3c0c7ec
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgressGraph.cs
@@ -0,0 +1,73 @@
+// 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.Framework.Graphics;
+using osu.Framework.Testing;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Screens.Play.HUD;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ [TestFixture]
+ public class TestSceneSongProgressGraph : OsuTestScene
+ {
+ private TestSongProgressGraph graph;
+
+ [SetUpSteps]
+ public void SetupSteps()
+ {
+ AddStep("add new big graph", () =>
+ {
+ if (graph != null)
+ {
+ graph.Expire();
+ graph = null;
+ }
+
+ Add(graph = new TestSongProgressGraph
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 200,
+ Anchor = Anchor.TopLeft,
+ Origin = Anchor.TopLeft,
+ });
+ });
+ }
+
+ [Test]
+ public void TestGraphRecreation()
+ {
+ AddAssert("ensure not created", () => graph.CreationCount == 0);
+ AddStep("display values", displayRandomValues);
+ AddUntilStep("wait for creation count", () => graph.CreationCount == 1);
+ AddRepeatStep("new values", displayRandomValues, 5);
+ AddWaitStep("wait some", 5);
+ AddAssert("ensure recreation debounced", () => graph.CreationCount == 2);
+ }
+
+ private void displayRandomValues()
+ {
+ var objects = new List();
+ for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000)
+ objects.Add(new HitObject { StartTime = i });
+
+ graph.Objects = objects;
+ }
+
+ private class TestSongProgressGraph : SongProgressGraph
+ {
+ public int CreationCount { get; private set; }
+
+ protected override void RecreateGraph()
+ {
+ base.RecreateGraph();
+ CreationCount++;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs
index a42e86933f..0aa412a4fd 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs
@@ -363,7 +363,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private Player player => Stack.CurrentScreen as Player;
private double currentFrameStableTime
- => player.ChildrenOfType().First().FrameStableClock.CurrentTime;
+ => player.ChildrenOfType().First().CurrentTime;
private void waitForPlayer() => AddUntilStep("wait for player", () => (Stack.CurrentScreen as Player)?.IsLoaded == true);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs
index c55e98c1a8..9ad8ac086c 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs
@@ -5,6 +5,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Screens;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Spectator;
@@ -43,6 +44,21 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchedUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID);
}
+ [Test]
+ public void TestRestart()
+ {
+ AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
+
+ AddStep("exit player", () => Player.Exit());
+ AddStep("reload player", LoadPlayer);
+ AddUntilStep("wait for player load", () => Player.IsLoaded && Player.Alpha == 1);
+
+ AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
+
+ AddWaitStep("wait", 5);
+ AddUntilStep("spectator client still sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
+ }
+
public override void TearDownSteps()
{
base.TearDownSteps();
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
index 5fad661e9b..9c41c70a0e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
@@ -27,7 +27,6 @@ using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
-using osu.Game.Screens.Play;
using osu.Game.Tests.Gameplay;
using osu.Game.Tests.Mods;
using osu.Game.Tests.Visual.Spectator;
@@ -41,16 +40,12 @@ namespace osu.Game.Tests.Visual.Gameplay
private TestRulesetInputManager playbackManager;
private TestRulesetInputManager recordingManager;
- private Replay replay;
-
+ private Score recordingScore;
+ private Replay playbackReplay;
private TestSpectatorClient spectatorClient;
-
private ManualClock manualClock;
-
private TestReplayRecorder recorder;
-
private OsuSpriteText latencyDisplay;
-
private TestFramedReplayInputHandler replayHandler;
[SetUpSteps]
@@ -58,7 +53,16 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("Setup containers", () =>
{
- replay = new Replay();
+ recordingScore = new Score
+ {
+ ScoreInfo =
+ {
+ BeatmapInfo = new BeatmapInfo(),
+ Ruleset = new OsuRuleset().RulesetInfo,
+ }
+ };
+
+ playbackReplay = new Replay();
manualClock = new ManualClock();
Child = new DependencyProvidingContainer
@@ -67,7 +71,6 @@ namespace osu.Game.Tests.Visual.Gameplay
CachedDependencies = new[]
{
(typeof(SpectatorClient), (object)(spectatorClient = new TestSpectatorClient())),
- (typeof(GameplayState), TestGameplayState.Create(new OsuRuleset()))
},
Children = new Drawable[]
{
@@ -81,7 +84,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
- Recorder = recorder = new TestReplayRecorder
+ Recorder = recorder = new TestReplayRecorder(recordingScore)
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
},
@@ -112,7 +115,7 @@ namespace osu.Game.Tests.Visual.Gameplay
playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
Clock = new FramedClock(manualClock),
- ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay)
+ ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(playbackReplay)
{
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
},
@@ -144,6 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}
};
+ spectatorClient.BeginPlaying(TestGameplayState.Create(new OsuRuleset()), recordingScore);
spectatorClient.OnNewFrames += onNewFrames;
});
}
@@ -151,15 +155,15 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestBasic()
{
- AddUntilStep("received frames", () => replay.Frames.Count > 50);
+ AddUntilStep("received frames", () => playbackReplay.Frames.Count > 50);
AddStep("stop sending frames", () => recorder.Expire());
- AddUntilStep("wait for all frames received", () => replay.Frames.Count == recorder.SentFrames.Count);
+ AddUntilStep("wait for all frames received", () => playbackReplay.Frames.Count == recorder.SentFrames.Count);
}
[Test]
public void TestWithSendFailure()
{
- AddUntilStep("received frames", () => replay.Frames.Count > 50);
+ AddUntilStep("received frames", () => playbackReplay.Frames.Count > 50);
int framesReceivedSoFar = 0;
int frameSendAttemptsSoFar = 0;
@@ -172,21 +176,21 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for next send attempt", () =>
{
- framesReceivedSoFar = replay.Frames.Count;
+ framesReceivedSoFar = playbackReplay.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);
+ AddAssert("frames did not increase", () => framesReceivedSoFar == playbackReplay.Frames.Count);
AddStep("stop failing sends", () => spectatorClient.ShouldFailSendingFrames = false);
- AddUntilStep("wait for next frames", () => framesReceivedSoFar < replay.Frames.Count);
+ AddUntilStep("wait for next frames", () => framesReceivedSoFar < playbackReplay.Frames.Count);
AddStep("stop sending frames", () => recorder.Expire());
- AddUntilStep("wait for all frames received", () => replay.Frames.Count == recorder.SentFrames.Count);
- AddAssert("ensure frames were received in the correct sequence", () => replay.Frames.Select(f => f.Time).SequenceEqual(recorder.SentFrames.Select(f => f.Time)));
+ AddUntilStep("wait for all frames received", () => playbackReplay.Frames.Count == recorder.SentFrames.Count);
+ AddAssert("ensure frames were received in the correct sequence", () => playbackReplay.Frames.Select(f => f.Time).SequenceEqual(recorder.SentFrames.Select(f => f.Time)));
}
private void onNewFrames(int userId, FrameDataBundle frames)
@@ -195,10 +199,10 @@ namespace osu.Game.Tests.Visual.Gameplay
{
var frame = new TestReplayFrame();
frame.FromLegacy(legacyFrame, null);
- replay.Frames.Add(frame);
+ playbackReplay.Frames.Add(frame);
}
- Logger.Log($"Received {frames.Frames.Count} new frames (total {replay.Frames.Count} of {recorder.SentFrames.Count})");
+ Logger.Log($"Received {frames.Frames.Count} new frames (total {playbackReplay.Frames.Count} of {recorder.SentFrames.Count})");
}
private double latency = SpectatorClient.TIME_BETWEEN_SENDS;
@@ -219,7 +223,7 @@ namespace osu.Game.Tests.Visual.Gameplay
if (!replayHandler.HasFrames)
return;
- var lastFrame = replay.Frames.LastOrDefault();
+ var lastFrame = playbackReplay.Frames.LastOrDefault();
// this isn't perfect as we basically can't be aware of the rate-of-send here (the streamer is not sending data when not being moved).
// in gameplay playback, the case where NextFrame is null would pause gameplay and handle this correctly; it's strictly a test limitation / best effort implementation.
@@ -360,15 +364,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public List SentFrames = new List();
- public TestReplayRecorder()
- : base(new Score
- {
- ScoreInfo =
- {
- BeatmapInfo = new BeatmapInfo(),
- Ruleset = new OsuRuleset().RulesetInfo,
- }
- })
+ public TestReplayRecorder(Score score)
+ : base(score)
{
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
index e2b2ad85a3..e2c825df0b 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
@@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestStoryboardNoSkipOutro()
{
CreateTest();
- AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
+ AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
}
@@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.Gameplay
});
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
- AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
+ AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
}
@@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("set ShowResults = false", () => showResults = false);
});
- AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
+ AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
AddWaitStep("wait", 10);
AddAssert("no score shown", () => !Player.IsScoreShown);
}
@@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestStoryboardEndsBeforeCompletion()
{
CreateTest(() => AddStep("set storyboard duration to .1s", () => currentStoryboardDuration = 100));
- AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
+ AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
}
@@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("skip overlay content not visible", () => fadeContainer().State == Visibility.Hidden);
AddUntilStep("skip overlay content becomes visible", () => fadeContainer().State == Visibility.Visible);
- AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
+ AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
}
[Test]
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]
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs
new file mode 100644
index 0000000000..2901501b30
--- /dev/null
+++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs
@@ -0,0 +1,87 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Online.API;
+using osu.Game.Overlays.Toolbar;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual.Menus
+{
+ [TestFixture]
+ public class TestSceneToolbarUserButton : OsuManualInputManagerTestScene
+ {
+ public TestSceneToolbarUserButton()
+ {
+ Container mainContainer;
+
+ Children = new Drawable[]
+ {
+ mainContainer = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.X,
+ Height = Toolbar.HEIGHT,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Black,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.X,
+ Direction = FillDirection.Horizontal,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.DarkRed,
+ RelativeSizeAxes = Axes.Y,
+ Width = 2,
+ },
+ new ToolbarUserButton(),
+ new Box
+ {
+ Colour = Color4.DarkRed,
+ RelativeSizeAxes = Axes.Y,
+ Width = 2,
+ },
+ }
+ },
+ }
+ },
+ };
+
+ AddSliderStep("scale", 0.5, 4, 1, scale => mainContainer.Scale = new Vector2((float)scale));
+ }
+
+ [Test]
+ public void TestLoginLogout()
+ {
+ AddStep("Log out", () => ((DummyAPIAccess)API).Logout());
+ AddStep("Log in", () => ((DummyAPIAccess)API).Login("wang", "jang"));
+ }
+
+ [Test]
+ public void TestStates()
+ {
+ AddStep("Log in", () => ((DummyAPIAccess)API).Login("wang", "jang"));
+
+ foreach (var state in Enum.GetValues())
+ {
+ AddStep($"Change state to {state}", () => ((DummyAPIAccess)API).SetState(state));
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs
index 2b461cf6f6..ca4d926866 100644
--- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs
@@ -35,7 +35,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
protected IScreen CurrentSubScreen => multiplayerComponents.MultiplayerScreen.CurrentSubScreen;
private BeatmapManager beatmaps;
- private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
private TestMultiplayerComponents multiplayerComponents;
@@ -45,8 +44,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[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 RealmRulesetStore(Realm));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs
index 0a59e0e858..b26481387d 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs
@@ -19,29 +19,33 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
private DrawableRoomParticipantsList list;
- [SetUp]
- public new void Setup() => Schedule(() =>
+ public override void SetUpSteps()
{
- SelectedRoom.Value = new Room
- {
- Name = { Value = "test room" },
- Host =
- {
- Value = new APIUser
- {
- Id = 2,
- Username = "peppy",
- }
- }
- };
+ base.SetUpSteps();
- Child = list = new DrawableRoomParticipantsList
+ AddStep("create list", () =>
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- NumberOfCircles = 4
- };
- });
+ SelectedRoom.Value = new Room
+ {
+ Name = { Value = "test room" },
+ Host =
+ {
+ Value = new APIUser
+ {
+ Id = 2,
+ Username = "peppy",
+ }
+ }
+ };
+
+ Child = list = new DrawableRoomParticipantsList
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ NumberOfCircles = 4
+ };
+ });
+ }
[Test]
public void TestCircleCountNearLimit()
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
index 757dfff2b7..73d1222156 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
@@ -18,6 +18,7 @@ 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.Models;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
@@ -37,13 +38,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
private TestPlaylist playlist;
private BeatmapManager manager;
- private RulesetStore rulesets;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(new RealmRulesetStore(Realm));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
}
@@ -195,12 +195,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()));
@@ -245,40 +248,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,19 +319,44 @@ 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", () =>
{
- Child = playlist = new TestPlaylist
+ Child = new OsuContextMenuContainer
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(500, 300)
+ RelativeSizeAxes = Axes.Both,
+ Child = playlist = new TestPlaylist
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ 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 +383,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));
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
index 82e7bf8969..3d6d4f0a90 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
@@ -25,23 +25,27 @@ namespace osu.Game.Tests.Visual.Multiplayer
private RoomsContainer container;
- [SetUp]
- public new void Setup() => Schedule(() =>
+ public override void SetUpSteps()
{
- Child = new PopoverContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Width = 0.5f,
+ base.SetUpSteps();
- Child = container = new RoomsContainer
+ AddStep("create container", () =>
+ {
+ Child = new PopoverContainer
{
- SelectedRoom = { BindTarget = SelectedRoom }
- }
- };
- });
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Width = 0.5f,
+
+ Child = container = new RoomsContainer
+ {
+ SelectedRoom = { BindTarget = SelectedRoom }
+ }
+ };
+ });
+ }
[Test]
public void TestBasicListChanges()
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs
index 8cdcdfdfdf..b113352117 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs
@@ -3,7 +3,6 @@
#nullable disable
-using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
@@ -18,19 +17,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchBeatmapDetailArea : OnlinePlayTestScene
{
- [SetUp]
- public new void Setup() => Schedule(() =>
+ public override void SetUpSteps()
{
- SelectedRoom.Value = new Room();
+ base.SetUpSteps();
- Child = new MatchBeatmapDetailArea
+ AddStep("create area", () =>
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(500),
- CreateNewItem = createNewItem
- };
- });
+ SelectedRoom.Value = new Room();
+
+ Child = new MatchBeatmapDetailArea
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(500),
+ CreateNewItem = createNewItem
+ };
+ });
+ }
private void createNewItem()
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
index 506d7541a7..d2468ae005 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
@@ -4,8 +4,6 @@
#nullable disable
using System.Collections.Generic;
-using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
@@ -19,59 +17,62 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchLeaderboard : OnlinePlayTestScene
{
- [BackgroundDependencyLoader]
- private void load()
+ public override void SetUpSteps()
{
- ((DummyAPIAccess)API).HandleRequest = r =>
+ base.SetUpSteps();
+
+ AddStep("setup API", () =>
{
- switch (r)
+ ((DummyAPIAccess)API).HandleRequest = r =>
{
- case GetRoomLeaderboardRequest leaderboardRequest:
- leaderboardRequest.TriggerSuccess(new APILeaderboard
- {
- Leaderboard = new List
+ switch (r)
+ {
+ case GetRoomLeaderboardRequest leaderboardRequest:
+ leaderboardRequest.TriggerSuccess(new APILeaderboard
{
- new APIUserScoreAggregate
+ Leaderboard = new List
{
- UserID = 2,
- User = new APIUser { Id = 2, Username = "peppy" },
- TotalScore = 995533,
- RoomID = 3,
- CompletedBeatmaps = 1,
- TotalAttempts = 6,
- Accuracy = 0.9851
- },
- new APIUserScoreAggregate
- {
- UserID = 1040328,
- User = new APIUser { Id = 1040328, Username = "smoogipoo" },
- TotalScore = 981100,
- RoomID = 3,
- CompletedBeatmaps = 1,
- TotalAttempts = 9,
- Accuracy = 0.937
+ new APIUserScoreAggregate
+ {
+ UserID = 2,
+ User = new APIUser { Id = 2, Username = "peppy" },
+ TotalScore = 995533,
+ RoomID = 3,
+ CompletedBeatmaps = 1,
+ TotalAttempts = 6,
+ Accuracy = 0.9851
+ },
+ new APIUserScoreAggregate
+ {
+ UserID = 1040328,
+ User = new APIUser { Id = 1040328, Username = "smoogipoo" },
+ TotalScore = 981100,
+ RoomID = 3,
+ CompletedBeatmaps = 1,
+ TotalAttempts = 9,
+ Accuracy = 0.937
+ }
}
- }
- });
- return true;
- }
+ });
+ return true;
+ }
- return false;
- };
- }
+ return false;
+ };
+ });
- [SetUp]
- public new void Setup() => Schedule(() =>
- {
- SelectedRoom.Value = new Room { RoomID = { Value = 3 } };
-
- Child = new MatchLeaderboard
+ AddStep("create leaderboard", () =>
{
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- Size = new Vector2(550f, 450f),
- Scope = MatchLeaderboardScope.Overall,
- };
- });
+ SelectedRoom.Value = new Room { RoomID = { Value = 3 } };
+
+ Child = new MatchLeaderboard
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ Size = new Vector2(550f, 450f),
+ Scope = MatchLeaderboardScope.Overall,
+ };
+ });
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
index 80c356ec67..9e6941738a 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
@@ -22,8 +22,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
private MultiSpectatorLeaderboard leaderboard;
[SetUpSteps]
- public new void SetUpSteps()
+ public override void SetUpSteps()
{
+ base.SetUpSteps();
+
AddStep("reset", () =>
{
leaderboard?.RemoveAndDisposeImmediately();
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
index 877c986d61..a2e3ab7318 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]
@@ -57,8 +56,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
importedBeatmapId = importedBeatmap.OnlineID;
}
- [SetUp]
- public new void Setup() => Schedule(() => playingUsers.Clear());
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+
+ AddStep("clear playing users", () => playingUsers.Clear());
+ }
[Test]
public void TestDelayedStart()
@@ -340,7 +343,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 +372,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 +390,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
assertRunning(PLAYER_1_ID);
}
- private void loadSpectateScreen(bool waitForPlayerLoad = true, Action