diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 6444127594..985fc09df3 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -27,10 +27,10 @@
]
},
"ppy.localisationanalyser.tools": {
- "version": "2021.725.0",
+ "version": "2021.1210.0",
"commands": [
"localisation"
]
}
}
-}
\ No newline at end of file
+}
diff --git a/.idea/.idea.osu.Android/.idea/.name b/.idea/.idea.osu.Android/.idea/.name
new file mode 100644
index 0000000000..86363b495c
--- /dev/null
+++ b/.idea/.idea.osu.Android/.idea/.name
@@ -0,0 +1 @@
+osu.Android
\ No newline at end of file
diff --git a/.idea/.idea.osu.Android/.idea/indexLayout.xml b/.idea/.idea.osu.Android/.idea/indexLayout.xml
new file mode 100644
index 0000000000..7b08163ceb
--- /dev/null
+++ b/.idea/.idea.osu.Android/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Android/.idea/misc.xml b/.idea/.idea.osu.Android/.idea/misc.xml
new file mode 100644
index 0000000000..1d8c84d0af
--- /dev/null
+++ b/.idea/.idea.osu.Android/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Android/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu.Android/.idea/projectSettingsUpdater.xml
new file mode 100644
index 0000000000..4bb9f4d2a0
--- /dev/null
+++ b/.idea/.idea.osu.Android/.idea/projectSettingsUpdater.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Android/.idea/vcs.xml b/.idea/.idea.osu.Android/.idea/vcs.xml
new file mode 100644
index 0000000000..94a25f7f4c
--- /dev/null
+++ b/.idea/.idea.osu.Android/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/misc.xml b/.idea/.idea.osu/.idea/misc.xml
new file mode 100644
index 0000000000..1d8c84d0af
--- /dev/null
+++ b/.idea/.idea.osu/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/modules.xml b/.idea/.idea.osu/.idea/modules.xml
deleted file mode 100644
index 0360fdbc5e..0000000000
--- a/.idea/.idea.osu/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu/.idea/projectSettingsUpdater.xml
index 7515e76054..4bb9f4d2a0 100644
--- a/.idea/.idea.osu/.idea/projectSettingsUpdater.xml
+++ b/.idea/.idea.osu/.idea/projectSettingsUpdater.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e14be20642..ae2bdd2e82 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,6 +1,6 @@
# Contributing Guidelines
-Thank you for showing interest in the development of osu!lazer! We aim to provide a good collaborating environment for everyone involved, and as such have decided to list some of the most important things to keep in mind in the process. The guidelines below have been chosen based on past experience.
+Thank you for showing interest in the development of osu!. We aim to provide a good collaborating environment for everyone involved, and as such have decided to list some of the most important things to keep in mind in the process. The guidelines below have been chosen based on past experience.
These are not "official rules" *per se*, but following them will help everyone deal with things in the most efficient manner.
@@ -32,7 +32,7 @@ Issues, bug reports and feature suggestions are welcomed, though please keep in
* **Provide more information when asked to do so.**
- Sometimes when a bug is more elusive or complicated, none of the information listed above will pinpoint a concrete cause of the problem. In this case we will most likely ask you for additional info, such as a Windows Event Log dump or a copy of your local lazer database (`client.db`). Providing that information is beneficial to both parties - we can track down the problem better, and hopefully fix it for you at some point once we know where it is!
+ Sometimes when a bug is more elusive or complicated, none of the information listed above will pinpoint a concrete cause of the problem. In this case we will most likely ask you for additional info, such as a Windows Event Log dump or a copy of your local osu! database (`client.db`). Providing that information is beneficial to both parties - we can track down the problem better, and hopefully fix it for you at some point once we know where it is!
* **When submitting a feature proposal, please describe it in the most understandable way you can.**
@@ -54,7 +54,7 @@ Issues, bug reports and feature suggestions are welcomed, though please keep in
We also welcome pull requests from unaffiliated contributors. The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues that you can work on; we also mark issues that we think would be good for newcomers with the [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label.
-However, do keep in mind that the core team is committed to bringing osu!lazer up to par with stable first and foremost, so depending on what your contribution concerns, it might not be merged and released right away. Our approach to managing issues and their priorities is described [in the wiki](https://github.com/ppy/osu/wiki/Project-management).
+However, do keep in mind that the core team is committed to bringing osu!(lazer) up to par with osu!(stable) first and foremost, so depending on what your contribution concerns, it might not be merged and released right away. Our approach to managing issues and their priorities is described [in the wiki](https://github.com/ppy/osu/wiki/Project-management).
Here are some key things to note before jumping in:
@@ -128,7 +128,7 @@ Here are some key things to note before jumping in:
* **Don't mistake criticism of code for criticism of your person.**
- As mentioned before, we are highly committed to quality when it comes to the lazer project. This means that contributions from less experienced community members can take multiple rounds of review to get to a mergeable state. We try our utmost best to never conflate a person with the code they authored, and to keep the discussion focused on the code at all times. Please consider our comments and requests a learning experience, and don't treat it as a personal attack.
+ As mentioned before, we are highly committed to quality when it comes to the osu! project. This means that contributions from less experienced community members can take multiple rounds of review to get to a mergeable state. We try our utmost best to never conflate a person with the code they authored, and to keep the discussion focused on the code at all times. Please consider our comments and requests a learning experience, and don't treat it as a personal attack.
* **Feel free to reach out for help.**
diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt
index c567adc0ae..e96ad48325 100644
--- a/CodeAnalysis/BannedSymbols.txt
+++ b/CodeAnalysis/BannedSymbols.txt
@@ -13,3 +13,5 @@ M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.H
M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection,NotificationCallbackDelegate) instead.
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable,NotificationCallbackDelegate) instead.
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList,NotificationCallbackDelegate) instead.
+M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks.
+P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks.
diff --git a/README.md b/README.md
index 786ce2589d..b1dfcab416 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
A free-to-win rhythm game. Rhythm is just a *click* away!
-The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Currently known by and released under the codename "*lazer*". As in sharper than cutting-edge.
+The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Currently known by and released under the release codename "*lazer*". As in sharper than cutting-edge.
## Status
@@ -31,7 +31,7 @@ If you are looking to install or test osu! without setting up a development envi
**Latest build:**
-| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
+| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.15+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
| ------------- | ------------- | ------------- | ------------- | ------------- |
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
@@ -48,7 +48,7 @@ You can see some examples of custom rulesets by visiting the [custom ruleset dir
Please make sure you have the following prerequisites:
-- A desktop platform with the [.NET 5.0 SDK](https://dotnet.microsoft.com/download) or higher installed.
+- A desktop platform with the [.NET 5.0 SDK](https://dotnet.microsoft.com/download) installed.
- When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding.
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs
index 536fdfc6df..5973db908c 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs
@@ -4,7 +4,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Platform;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
@@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests
public class TestSceneOsuGame : OsuTestScene
{
[BackgroundDependencyLoader]
- private void load(GameHost host, OsuGameBase gameBase)
+ private void load()
{
Children = new Drawable[]
{
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
index 3cdf44e6f1..b75a5ec187 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
@@ -4,7 +4,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Platform;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
@@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
public class TestSceneOsuGame : OsuTestScene
{
[BackgroundDependencyLoader]
- private void load(GameHost host, OsuGameBase gameBase)
+ private void load()
{
Children = new Drawable[]
{
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs
index 4d3f5086d9..ffe921b54c 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs
@@ -4,7 +4,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Platform;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
@@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests
public class TestSceneOsuGame : OsuTestScene
{
[BackgroundDependencyLoader]
- private void load(GameHost host, OsuGameBase gameBase)
+ private void load()
{
Children = new Drawable[]
{
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
index 3cdf44e6f1..b75a5ec187 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
@@ -4,7 +4,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Platform;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
@@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
public class TestSceneOsuGame : OsuTestScene
{
[BackgroundDependencyLoader]
- private void load(GameHost host, OsuGameBase gameBase)
+ private void load()
{
Children = new Drawable[]
{
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs
index 0e50030162..ab8c6bb2e9 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs
@@ -7,7 +7,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
@@ -28,7 +27,7 @@ namespace osu.Game.Rulesets.Pippidon.UI
private PippidonCharacter pippidon;
[BackgroundDependencyLoader]
- private void load(TextureStore textures)
+ private void load()
{
AddRangeInternal(new Drawable[]
{
diff --git a/osu.Android.props b/osu.Android.props
index eff0eed278..b2e3b32916 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,8 +51,8 @@
-
-
+
+
diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs
index fec96c9165..c9fb539d8a 100644
--- a/osu.Android/OsuGameActivity.cs
+++ b/osu.Android/OsuGameActivity.cs
@@ -17,7 +17,7 @@ using osu.Game.Database;
namespace osu.Android
{
- [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)]
+ [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser)]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osr", DataHost = "*", DataMimeType = "*/*")]
@@ -47,11 +47,6 @@ namespace osu.Android
protected override void OnCreate(Bundle savedInstanceState)
{
- // The default current directory on android is '/'.
- // On some devices '/' maps to the app data directory. On others it maps to the root of the internal storage.
- // In order to have a consistent current directory on all devices the full path of the app data directory is set as the current directory.
- System.Environment.CurrentDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
-
base.OnCreate(savedInstanceState);
// OnNewIntent() only fires for an activity if it's *re-launched* while it's on top of the activity stack.
diff --git a/osu.Android/Properties/AndroidManifest.xml b/osu.Android/Properties/AndroidManifest.xml
index e717bab310..165a64a424 100644
--- a/osu.Android/Properties/AndroidManifest.xml
+++ b/osu.Android/Properties/AndroidManifest.xml
@@ -1,11 +1,5 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/osu.Android/Properties/AssemblyInfo.cs b/osu.Android/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..c0ba324d6e
--- /dev/null
+++ b/osu.Android/Properties/AssemblyInfo.cs
@@ -0,0 +1,8 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Android;
+using Android.App;
+
+// used for AndroidBatteryInfo
+[assembly: UsesPermission(Manifest.Permission.BatteryStats)]
diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj
index b2599535ae..fc50ca9fa1 100644
--- a/osu.Android/osu.Android.csproj
+++ b/osu.Android/osu.Android.csproj
@@ -29,6 +29,7 @@
+
diff --git a/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationRequest.cs b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationRequest.cs
new file mode 100644
index 0000000000..d6ef390a8f
--- /dev/null
+++ b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationRequest.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Desktop.LegacyIpc
+{
+ ///
+ /// A difficulty calculation request from the legacy client.
+ ///
+ ///
+ /// Synchronise any changes with osu!stable.
+ ///
+ public class LegacyIpcDifficultyCalculationRequest
+ {
+ public string BeatmapFile { get; set; }
+ public int RulesetId { get; set; }
+ public int Mods { get; set; }
+ }
+}
diff --git a/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationResponse.cs b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationResponse.cs
new file mode 100644
index 0000000000..7b9fae5797
--- /dev/null
+++ b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationResponse.cs
@@ -0,0 +1,16 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Desktop.LegacyIpc
+{
+ ///
+ /// A difficulty calculation response returned to the legacy client.
+ ///
+ ///
+ /// Synchronise any changes with osu!stable.
+ ///
+ public class LegacyIpcDifficultyCalculationResponse
+ {
+ public double StarRating { get; set; }
+ }
+}
diff --git a/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs b/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs
new file mode 100644
index 0000000000..0fa60e2068
--- /dev/null
+++ b/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs
@@ -0,0 +1,53 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Platform;
+using Newtonsoft.Json.Linq;
+
+namespace osu.Desktop.LegacyIpc
+{
+ ///
+ /// An that can be used to communicate to and from legacy clients.
+ ///
+ /// In order to deserialise types at either end, types must be serialised as their ,
+ /// however this cannot be done since osu!stable and osu!lazer live in two different assemblies.
+ ///
+ /// To get around this, this class exists which serialises a payload () as an type,
+ /// which can be deserialised at either end because it is part of the core library (mscorlib / System.Private.CorLib).
+ /// The payload contains the data to be sent over the IPC channel.
+ ///
+ /// At either end, Json.NET deserialises the payload into a which is manually converted back into the expected type,
+ /// which then further contains another representing the data sent over the IPC channel whose type can likewise be lazily matched through
+ /// .
+ ///
+ ///
+ ///
+ /// Synchronise any changes with osu-stable.
+ ///
+ public class LegacyIpcMessage : IpcMessage
+ {
+ public LegacyIpcMessage()
+ {
+ // Types/assemblies are not inter-compatible, so always serialise/deserialise into objects.
+ base.Type = typeof(object).FullName;
+ }
+
+ public new string Type => base.Type; // Hide setter.
+
+ public new object Value
+ {
+ get => base.Value;
+ set => base.Value = new Data
+ {
+ MessageType = value.GetType().Name,
+ MessageData = value
+ };
+ }
+
+ public class Data
+ {
+ public string MessageType { get; set; }
+ public object MessageData { get; set; }
+ }
+ }
+}
diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs
new file mode 100644
index 0000000000..97a4c57bf0
--- /dev/null
+++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs
@@ -0,0 +1,121 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using Newtonsoft.Json.Linq;
+using osu.Framework.Logging;
+using osu.Framework.Platform;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Catch;
+using osu.Game.Rulesets.Mania;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Taiko;
+
+#nullable enable
+
+namespace osu.Desktop.LegacyIpc
+{
+ ///
+ /// Provides IPC to legacy osu! clients.
+ ///
+ public class LegacyTcpIpcProvider : TcpIpcProvider
+ {
+ private static readonly Logger logger = Logger.GetLogger("legacy-ipc");
+
+ public LegacyTcpIpcProvider()
+ : base(45357)
+ {
+ MessageReceived += msg =>
+ {
+ try
+ {
+ logger.Add("Processing legacy IPC message...");
+ logger.Add($" {msg.Value}", LogLevel.Debug);
+
+ // See explanation in LegacyIpcMessage for why this is done this way.
+ var legacyData = ((JObject)msg.Value).ToObject();
+ object value = parseObject((JObject)legacyData!.MessageData, legacyData.MessageType);
+
+ return new LegacyIpcMessage
+ {
+ Value = onLegacyIpcMessageReceived(value)
+ };
+ }
+ catch (Exception ex)
+ {
+ logger.Add($"Processing IPC message failed: {msg.Value}", exception: ex);
+ return null;
+ }
+ };
+ }
+
+ private object parseObject(JObject value, string type)
+ {
+ switch (type)
+ {
+ case nameof(LegacyIpcDifficultyCalculationRequest):
+ return value.ToObject()
+ ?? throw new InvalidOperationException($"Failed to parse request {value}");
+
+ case nameof(LegacyIpcDifficultyCalculationResponse):
+ return value.ToObject()
+ ?? throw new InvalidOperationException($"Failed to parse request {value}");
+
+ default:
+ throw new ArgumentException($"Unsupported object type {type}");
+ }
+ }
+
+ private object onLegacyIpcMessageReceived(object message)
+ {
+ switch (message)
+ {
+ case LegacyIpcDifficultyCalculationRequest req:
+ try
+ {
+ var ruleset = getLegacyRulesetFromID(req.RulesetId);
+
+ Mod[] mods = ruleset.ConvertFromLegacyMods((LegacyMods)req.Mods).ToArray();
+ WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(req.BeatmapFile, _ => ruleset);
+
+ return new LegacyIpcDifficultyCalculationResponse
+ {
+ StarRating = ruleset.CreateDifficultyCalculator(beatmap).Calculate(mods).StarRating
+ };
+ }
+ catch
+ {
+ return new LegacyIpcDifficultyCalculationResponse();
+ }
+
+ default:
+ throw new ArgumentException($"Unsupported message type {message}");
+ }
+ }
+
+ private static Ruleset getLegacyRulesetFromID(int rulesetId)
+ {
+ switch (rulesetId)
+ {
+ case 0:
+ return new OsuRuleset();
+
+ case 1:
+ return new TaikoRuleset();
+
+ case 2:
+ return new CatchRuleset();
+
+ case 3:
+ return new ManiaRuleset();
+
+ default:
+ throw new ArgumentException("Invalid ruleset id");
+ }
+ }
+ }
+}
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 645ea66654..b234207848 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -70,7 +70,9 @@ namespace osu.Desktop
if (!string.IsNullOrEmpty(stableInstallPath) && checkExists(stableInstallPath))
return stableInstallPath;
}
- catch { }
+ catch
+ {
+ }
}
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!");
@@ -113,7 +115,7 @@ namespace osu.Desktop
base.LoadComplete();
if (!noVersionOverlay)
- LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add);
+ LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add);
LoadComponentAsync(new DiscordRichPresence(), Add);
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 898f7d5105..7ec7d53a7e 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -5,6 +5,7 @@ using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
+using osu.Desktop.LegacyIpc;
using osu.Framework;
using osu.Framework.Development;
using osu.Framework.Logging;
@@ -18,8 +19,10 @@ namespace osu.Desktop
{
private const string base_game_name = @"osu";
+ private static LegacyTcpIpcProvider legacyIpc;
+
[STAThread]
- public static int Main(string[] args)
+ public static void Main(string[] args)
{
// Back up the cwd before DesktopGameHost changes it
string cwd = Environment.CurrentDirectory;
@@ -69,14 +72,28 @@ namespace osu.Desktop
throw new TimeoutException(@"IPC took too long to send");
}
- return 0;
+ return;
}
// we want to allow multiple instances to be started when in debug.
if (!DebugUtils.IsDebugBuild)
{
Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error);
- return 0;
+ return;
+ }
+ }
+
+ if (host.IsPrimaryInstance)
+ {
+ try
+ {
+ Logger.Log("Starting legacy IPC provider...");
+ legacyIpc = new LegacyTcpIpcProvider();
+ legacyIpc.Bind();
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex, "Failed to start legacy IPC provider");
}
}
@@ -84,8 +101,6 @@ namespace osu.Desktop
host.Run(new TournamentGame());
else
host.Run(new OsuGameDesktop(args));
-
- return 0;
}
}
diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs
index 01458b4c37..8f3ad853dc 100644
--- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs
+++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs
@@ -73,10 +73,10 @@ namespace osu.Desktop.Security
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours, NotificationOverlay notificationOverlay)
+ private void load(OsuColour colours)
{
Icon = FontAwesome.Solid.ShieldAlt;
- IconBackgound.Colour = colours.YellowDark;
+ IconBackground.Colour = colours.YellowDark;
}
}
}
diff --git a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs
index dbfd170ea1..4acaf61cea 100644
--- a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs
+++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs
@@ -14,6 +14,7 @@ namespace osu.Desktop.Windows
{
private Bindable disableWinKey;
private IBindable localUserPlaying;
+ private IBindable isActive;
[Resolved]
private GameHost host { get; set; }
@@ -24,13 +25,16 @@ namespace osu.Desktop.Windows
localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
localUserPlaying.BindValueChanged(_ => updateBlocking());
+ isActive = host.IsActive.GetBoundCopy();
+ isActive.BindValueChanged(_ => updateBlocking());
+
disableWinKey = config.GetBindable(OsuSetting.GameplayDisableWinKey);
disableWinKey.BindValueChanged(_ => updateBlocking(), true);
}
private void updateBlocking()
{
- bool shouldDisable = disableWinKey.Value && localUserPlaying.Value;
+ bool shouldDisable = isActive.Value && disableWinKey.Value && localUserPlaying.Value;
if (shouldDisable)
host.InputThread.Scheduler.Add(WindowsKey.Disable);
diff --git a/osu.Desktop/Windows/WindowsKey.cs b/osu.Desktop/Windows/WindowsKey.cs
index f19d741107..fdca2028d3 100644
--- a/osu.Desktop/Windows/WindowsKey.cs
+++ b/osu.Desktop/Windows/WindowsKey.cs
@@ -4,6 +4,8 @@
using System;
using System.Runtime.InteropServices;
+// ReSharper disable IdentifierTypo
+
namespace osu.Desktop.Windows
{
internal class WindowsKey
diff --git a/osu.Game.Rulesets.Catch.Tests.Android/MainActivity.cs b/osu.Game.Rulesets.Catch.Tests.Android/MainActivity.cs
index d918305f3d..d8b729576d 100644
--- a/osu.Game.Rulesets.Catch.Tests.Android/MainActivity.cs
+++ b/osu.Game.Rulesets.Catch.Tests.Android/MainActivity.cs
@@ -2,13 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using Android.App;
-using Android.Content.PM;
using osu.Framework.Android;
using osu.Game.Tests;
namespace osu.Game.Rulesets.Catch.Tests.Android
{
- [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
+ [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)]
public class MainActivity : AndroidGameActivity
{
protected override Framework.Game CreateGame() => new OsuTestBrowser();
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist
index 5115746cbb..3ba1886d98 100644
--- a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist
@@ -32,5 +32,7 @@
XSAppIconAssets
Assets.xcassets/AppIcon.appiconset
+ CADisableMinimumFrameDurationOnPhone
+
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index 33fdcdaf1e..baca8166d1 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -14,7 +14,6 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- [Timeout(10000)]
public class CatchBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
@@ -27,6 +26,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestCase("hardrock-repeat-slider", new[] { typeof(CatchModHardRock) })]
[TestCase("hardrock-spinner", new[] { typeof(CatchModHardRock) })]
[TestCase("right-bound-hr-offset", new[] { typeof(CatchModHardRock) })]
+ [TestCase("basic-hyperdash")]
public new void Test(string name, params Type[] mods) => base.Test(name, mods);
protected override IEnumerable CreateConvertValue(HitObject hitObject)
@@ -70,6 +70,7 @@ namespace osu.Game.Rulesets.Catch.Tests
HitObject = hitObject;
startTime = 0;
position = 0;
+ hyperDash = false;
}
private double startTime;
@@ -88,8 +89,17 @@ namespace osu.Game.Rulesets.Catch.Tests
set => position = value;
}
+ private bool hyperDash;
+
+ public bool HyperDash
+ {
+ get => (HitObject as PalpableCatchHitObject)?.HyperDash ?? hyperDash;
+ set => hyperDash = value;
+ }
+
public bool Equals(ConvertValue other)
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
- && Precision.AlmostEquals(Position, other.Position, conversion_lenience);
+ && Precision.AlmostEquals(Position, other.Position, conversion_lenience)
+ && HyperDash == other.HyperDash;
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
index 459b8e1f6f..23f6222eb6 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
@@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Catch.Tests
}
[Test]
- public void TestJuicestream()
+ public void TestJuiceStream()
{
AddStep("hit juicestream", () => spawnJuiceStream(true));
AddUntilStep("wait for completion", () => playfieldIsEmpty);
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
index 943adbef52..12b98dc93c 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
@@ -37,20 +37,20 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("show hyperdash droplet", () => SetContents(_ => createDrawableDroplet(true)));
}
- private Drawable createDrawableFruit(int indexInBeatmap, bool hyperdash = false) =>
+ private Drawable createDrawableFruit(int indexInBeatmap, bool hyperDash = false) =>
new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit
{
IndexInBeatmap = indexInBeatmap,
- HyperDashBindable = { Value = hyperdash }
+ HyperDashBindable = { Value = hyperDash }
}));
private Drawable createDrawableBanana() =>
new TestDrawableCatchHitObjectSpecimen(new DrawableBanana(new Banana()));
- private Drawable createDrawableDroplet(bool hyperdash = false) =>
+ private Drawable createDrawableDroplet(bool hyperDash = false) =>
new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet
{
- HyperDashBindable = { Value = hyperdash }
+ HyperDashBindable = { Value = hyperDash }
}));
private Drawable createDrawableTinyDroplet() => new TestDrawableCatchHitObjectSpecimen(new DrawableTinyDroplet(new TinyDroplet()));
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
index 70b2c8c82a..14a4d02396 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
@@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private Drawable setupSkinHierarchy(Drawable child, ISkin skin)
{
- var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.Info));
+ var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.CreateInfo()));
var testSkinProvider = new SkinProvidingContainer(skin);
var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider));
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceAttributes.cs
new file mode 100644
index 0000000000..1335fc2d23
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceAttributes.cs
@@ -0,0 +1,11 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Difficulty;
+
+namespace osu.Game.Rulesets.Catch.Difficulty
+{
+ public class CatchPerformanceAttributes : PerformanceAttributes
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
index 439890dac2..8cdbe500f0 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
}
- public override double Calculate(Dictionary categoryDifficulty = null)
+ public override PerformanceAttributes Calculate()
{
mods = Score.Mods;
@@ -44,15 +44,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty
// Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo
int numTotalHits = totalComboHits();
- // Longer maps are worth more
double lengthBonus =
0.95 + 0.3 * Math.Min(1.0, numTotalHits / 2500.0) +
(numTotalHits > 2500 ? Math.Log10(numTotalHits / 2500.0) * 0.475 : 0.0);
-
- // Longer maps are worth more
value *= lengthBonus;
- // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
value *= Math.Pow(0.97, misses);
// Combo scaling
@@ -80,17 +76,17 @@ namespace osu.Game.Rulesets.Catch.Difficulty
}
if (mods.Any(m => m is ModFlashlight))
- // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
value *= 1.35 * lengthBonus;
- // Scale the aim value with accuracy _slightly_
value *= Math.Pow(accuracy(), 5.5);
- // Custom multipliers for NoFail. SpunOut is not applicable.
if (mods.Any(m => m is ModNoFail))
value *= 0.90;
- return value;
+ return new CatchPerformanceAttributes
+ {
+ Total = value
+ };
}
private double accuracy() => totalHits() == 0 ? 0 : Math.Clamp((double)totalSuccessfulHits() / totalHits(), 0, 1);
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs
index 8cb0804ab7..dd5835b4ed 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs
@@ -52,16 +52,25 @@ namespace osu.Game.Rulesets.Catch.Edit
return true;
}
- public override bool HandleFlip(Direction direction)
+ public override bool HandleFlip(Direction direction, bool flipOverOrigin)
{
+ if (SelectedItems.Count == 0)
+ return false;
+
+ // This could be implemented in the future if there's a requirement for it.
+ if (direction == Direction.Vertical)
+ return false;
+
var selectionRange = CatchHitObjectUtils.GetPositionRange(SelectedItems);
bool changed = false;
+
EditorBeatmap.PerformOnSelection(h =>
{
if (h is CatchHitObject catchObject)
- changed |= handleFlip(selectionRange, catchObject);
+ changed |= handleFlip(selectionRange, catchObject, flipOverOrigin);
});
+
return changed;
}
@@ -116,7 +125,7 @@ namespace osu.Game.Rulesets.Catch.Edit
return Math.Clamp(deltaX, lowerBound, upperBound);
}
- private bool handleFlip(PositionRange selectionRange, CatchHitObject hitObject)
+ private bool handleFlip(PositionRange selectionRange, CatchHitObject hitObject, bool flipOverOrigin)
{
switch (hitObject)
{
@@ -124,7 +133,7 @@ namespace osu.Game.Rulesets.Catch.Edit
return false;
case JuiceStream juiceStream:
- juiceStream.OriginalX = selectionRange.GetFlippedPosition(juiceStream.OriginalX);
+ juiceStream.OriginalX = getFlippedPosition(juiceStream.OriginalX);
foreach (var point in juiceStream.Path.ControlPoints)
point.Position *= new Vector2(-1, 1);
@@ -133,9 +142,11 @@ namespace osu.Game.Rulesets.Catch.Edit
return true;
default:
- hitObject.OriginalX = selectionRange.GetFlippedPosition(hitObject.OriginalX);
+ hitObject.OriginalX = getFlippedPosition(hitObject.OriginalX);
return true;
}
+
+ float getFlippedPosition(float original) => flipOverOrigin ? CatchPlayfield.WIDTH - original : selectionRange.GetFlippedPosition(original);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json
index b65d54a565..07ceb199bd 100644
--- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json
@@ -3,135 +3,168 @@
"StartTime": 500,
"Objects": [{
"StartTime": 500,
- "Position": 96
+ "Position": 96,
+ "HyperDash": false
},
{
"StartTime": 562,
- "Position": 100.84
+ "Position": 100.84,
+ "HyperDash": false
},
{
"StartTime": 625,
- "Position": 125
+ "Position": 125,
+ "HyperDash": false
},
{
"StartTime": 687,
- "Position": 152.84
+ "Position": 152.84,
+ "HyperDash": false
},
{
"StartTime": 750,
- "Position": 191
+ "Position": 191,
+ "HyperDash": false
},
{
"StartTime": 812,
- "Position": 212.84
+ "Position": 212.84,
+ "HyperDash": false
},
{
"StartTime": 875,
- "Position": 217
+ "Position": 217,
+ "HyperDash": false
},
{
"StartTime": 937,
- "Position": 234.84
+ "Position": 234.84,
+ "HyperDash": false
},
{
"StartTime": 1000,
- "Position": 256
+ "Position": 256,
+ "HyperDash": false
},
{
"StartTime": 1062,
- "Position": 267.84
+ "Position": 267.84,
+ "HyperDash": false
},
{
"StartTime": 1125,
- "Position": 284
+ "Position": 284,
+ "HyperDash": false
},
{
"StartTime": 1187,
- "Position": 311.84
+ "Position": 311.84,
+ "HyperDash": false
},
{
"StartTime": 1250,
- "Position": 350
+ "Position": 350,
+ "HyperDash": false
},
{
"StartTime": 1312,
- "Position": 359.84
+ "Position": 359.84,
+ "HyperDash": false
},
{
"StartTime": 1375,
- "Position": 367
+ "Position": 367,
+ "HyperDash": false
},
{
"StartTime": 1437,
- "Position": 400.84
+ "Position": 400.84,
+ "HyperDash": false
},
{
"StartTime": 1500,
- "Position": 416
+ "Position": 416,
+ "HyperDash": false
},
{
"StartTime": 1562,
- "Position": 377.159973
+ "Position": 377.159973,
+ "HyperDash": false
},
{
"StartTime": 1625,
- "Position": 367
+ "Position": 367,
+ "HyperDash": false
},
{
"StartTime": 1687,
- "Position": 374.159973
+ "Position": 374.159973,
+ "HyperDash": false
},
{
"StartTime": 1750,
- "Position": 353
+ "Position": 353,
+ "HyperDash": false
},
{
"StartTime": 1812,
- "Position": 329.159973
+ "Position": 329.159973,
+ "HyperDash": false
},
{
"StartTime": 1875,
- "Position": 288
+ "Position": 288,
+ "HyperDash": false
},
{
"StartTime": 1937,
- "Position": 259.159973
+ "Position": 259.159973,
+ "HyperDash": false
},
{
"StartTime": 2000,
- "Position": 256
+ "Position": 256,
+ "HyperDash": false
},
{
"StartTime": 2058,
- "Position": 232.44
+ "Position": 232.44,
+ "HyperDash": false
},
{
"StartTime": 2116,
- "Position": 222.879974
+ "Position": 222.879974,
+ "HyperDash": false
},
{
"StartTime": 2174,
- "Position": 185.319992
+ "Position": 185.319992,
+ "HyperDash": false
},
{
"StartTime": 2232,
- "Position": 177.76001
+ "Position": 177.76001,
+ "HyperDash": false
},
{
"StartTime": 2290,
- "Position": 162.200012
+ "Position": 162.200012,
+ "HyperDash": false
},
{
"StartTime": 2348,
- "Position": 158.639984
+ "Position": 158.639984,
+ "HyperDash": false
},
{
"StartTime": 2406,
- "Position": 111.079994
+ "Position": 111.079994,
+ "HyperDash": false
},
{
"StartTime": 2500,
- "Position": 96
+ "Position": 96,
+ "HyperDash": false
}
]
},
@@ -139,71 +172,88 @@
"StartTime": 3000,
"Objects": [{
"StartTime": 3000,
- "Position": 18
+ "Position": 18,
+ "HyperDash": false
},
{
"StartTime": 3062,
- "Position": 249
+ "Position": 249,
+ "HyperDash": false
},
{
"StartTime": 3125,
- "Position": 184
+ "Position": 184,
+ "HyperDash": false
},
{
"StartTime": 3187,
- "Position": 477
+ "Position": 477,
+ "HyperDash": false
},
{
"StartTime": 3250,
- "Position": 43
+ "Position": 43,
+ "HyperDash": false
},
{
"StartTime": 3312,
- "Position": 494
+ "Position": 494,
+ "HyperDash": false
},
{
"StartTime": 3375,
- "Position": 135
+ "Position": 135,
+ "HyperDash": false
},
{
"StartTime": 3437,
- "Position": 30
+ "Position": 30,
+ "HyperDash": false
},
{
"StartTime": 3500,
- "Position": 11
+ "Position": 11,
+ "HyperDash": false
},
{
"StartTime": 3562,
- "Position": 239
+ "Position": 239,
+ "HyperDash": false
},
{
"StartTime": 3625,
- "Position": 505
+ "Position": 505,
+ "HyperDash": false
},
{
"StartTime": 3687,
- "Position": 353
+ "Position": 353,
+ "HyperDash": false
},
{
"StartTime": 3750,
- "Position": 136
+ "Position": 136,
+ "HyperDash": false
},
{
"StartTime": 3812,
- "Position": 135
+ "Position": 135,
+ "HyperDash": false
},
{
"StartTime": 3875,
- "Position": 346
+ "Position": 346,
+ "HyperDash": false
},
{
"StartTime": 3937,
- "Position": 39
+ "Position": 39,
+ "HyperDash": false
},
{
"StartTime": 4000,
- "Position": 300
+ "Position": 300,
+ "HyperDash": false
}
]
},
@@ -211,71 +261,88 @@
"StartTime": 4500,
"Objects": [{
"StartTime": 4500,
- "Position": 398
+ "Position": 398,
+ "HyperDash": false
},
{
"StartTime": 4562,
- "Position": 151
+ "Position": 151,
+ "HyperDash": false
},
{
"StartTime": 4625,
- "Position": 73
+ "Position": 73,
+ "HyperDash": false
},
{
"StartTime": 4687,
- "Position": 311
+ "Position": 311,
+ "HyperDash": false
},
{
"StartTime": 4750,
- "Position": 90
+ "Position": 90,
+ "HyperDash": false
},
{
"StartTime": 4812,
- "Position": 264
+ "Position": 264,
+ "HyperDash": false
},
{
"StartTime": 4875,
- "Position": 477
+ "Position": 477,
+ "HyperDash": false
},
{
"StartTime": 4937,
- "Position": 473
+ "Position": 473,
+ "HyperDash": false
},
{
"StartTime": 5000,
- "Position": 120
+ "Position": 120,
+ "HyperDash": false
},
{
"StartTime": 5062,
- "Position": 115
+ "Position": 115,
+ "HyperDash": false
},
{
"StartTime": 5125,
- "Position": 163
+ "Position": 163,
+ "HyperDash": false
},
{
"StartTime": 5187,
- "Position": 447
+ "Position": 447,
+ "HyperDash": false
},
{
"StartTime": 5250,
- "Position": 72
+ "Position": 72,
+ "HyperDash": false
},
{
"StartTime": 5312,
- "Position": 257
+ "Position": 257,
+ "HyperDash": false
},
{
"StartTime": 5375,
- "Position": 153
+ "Position": 153,
+ "HyperDash": false
},
{
"StartTime": 5437,
- "Position": 388
+ "Position": 388,
+ "HyperDash": false
},
{
"StartTime": 5500,
- "Position": 336
+ "Position": 336,
+ "HyperDash": false
}
]
},
@@ -283,39 +350,48 @@
"StartTime": 6000,
"Objects": [{
"StartTime": 6000,
- "Position": 13
+ "Position": 13,
+ "HyperDash": false
},
{
"StartTime": 6062,
- "Position": 429
+ "Position": 429,
+ "HyperDash": false
},
{
"StartTime": 6125,
- "Position": 381
+ "Position": 381,
+ "HyperDash": false
},
{
"StartTime": 6187,
- "Position": 186
+ "Position": 186,
+ "HyperDash": false
},
{
"StartTime": 6250,
- "Position": 267
+ "Position": 267,
+ "HyperDash": false
},
{
"StartTime": 6312,
- "Position": 305
+ "Position": 305,
+ "HyperDash": false
},
{
"StartTime": 6375,
- "Position": 456
+ "Position": 456,
+ "HyperDash": false
},
{
"StartTime": 6437,
- "Position": 26
+ "Position": 26,
+ "HyperDash": false
},
{
"StartTime": 6500,
- "Position": 238
+ "Position": 238,
+ "HyperDash": false
}
]
},
@@ -323,71 +399,88 @@
"StartTime": 7000,
"Objects": [{
"StartTime": 7000,
- "Position": 256
+ "Position": 256,
+ "HyperDash": false
},
{
"StartTime": 7062,
- "Position": 262.84
+ "Position": 262.84,
+ "HyperDash": false
},
{
"StartTime": 7125,
- "Position": 295
+ "Position": 295,
+ "HyperDash": false
},
{
"StartTime": 7187,
- "Position": 303.84
+ "Position": 303.84,
+ "HyperDash": false
},
{
"StartTime": 7250,
- "Position": 336
+ "Position": 336,
+ "HyperDash": false
},
{
"StartTime": 7312,
- "Position": 319.16
+ "Position": 319.16,
+ "HyperDash": false
},
{
"StartTime": 7375,
- "Position": 306
+ "Position": 306,
+ "HyperDash": false
},
{
"StartTime": 7437,
- "Position": 272.16
+ "Position": 272.16,
+ "HyperDash": false
},
{
"StartTime": 7500,
- "Position": 256
+ "Position": 256,
+ "HyperDash": false
},
{
"StartTime": 7562,
- "Position": 255.84
+ "Position": 255.84,
+ "HyperDash": false
},
{
"StartTime": 7625,
- "Position": 300
+ "Position": 300,
+ "HyperDash": false
},
{
"StartTime": 7687,
- "Position": 320.84
+ "Position": 320.84,
+ "HyperDash": false
},
{
"StartTime": 7750,
- "Position": 336
+ "Position": 336,
+ "HyperDash": false
},
{
"StartTime": 7803,
- "Position": 319.04
+ "Position": 319.04,
+ "HyperDash": false
},
{
"StartTime": 7857,
- "Position": 283.76
+ "Position": 283.76,
+ "HyperDash": false
},
{
"StartTime": 7910,
- "Position": 265.8
+ "Position": 265.8,
+ "HyperDash": false
},
{
"StartTime": 8000,
- "Position": 256
+ "Position": 256,
+ "HyperDash": false
}
]
},
@@ -395,167 +488,208 @@
"StartTime": 8500,
"Objects": [{
"StartTime": 8500,
- "Position": 32
+ "Position": 32,
+ "HyperDash": false
},
{
"StartTime": 8562,
- "Position": 21.8515015
+ "Position": 21.8515015,
+ "HyperDash": false
},
{
"StartTime": 8625,
- "Position": 44.5659637
+ "Position": 44.5659637,
+ "HyperDash": false
},
{
"StartTime": 8687,
- "Position": 33.3433228
+ "Position": 33.3433228,
+ "HyperDash": false
},
{
"StartTime": 8750,
- "Position": 63.58974
+ "Position": 63.58974,
+ "HyperDash": false
},
{
"StartTime": 8812,
- "Position": 71.23422
+ "Position": 71.23422,
+ "HyperDash": false
},
{
"StartTime": 8875,
- "Position": 62.7117844
+ "Position": 62.7117844,
+ "HyperDash": false
},
{
"StartTime": 8937,
- "Position": 65.52607
+ "Position": 65.52607,
+ "HyperDash": false
},
{
"StartTime": 9000,
- "Position": 101.81015
+ "Position": 101.81015,
+ "HyperDash": false
},
{
"StartTime": 9062,
- "Position": 134.47818
+ "Position": 134.47818,
+ "HyperDash": false
},
{
"StartTime": 9125,
- "Position": 141.414444
+ "Position": 141.414444,
+ "HyperDash": false
},
{
"StartTime": 9187,
- "Position": 164.1861
+ "Position": 164.1861,
+ "HyperDash": false
},
{
"StartTime": 9250,
- "Position": 176.600418
+ "Position": 176.600418,
+ "HyperDash": false
},
{
"StartTime": 9312,
- "Position": 184.293015
+ "Position": 184.293015,
+ "HyperDash": false
},
{
"StartTime": 9375,
- "Position": 212.2076
+ "Position": 212.2076,
+ "HyperDash": false
},
{
"StartTime": 9437,
- "Position": 236.438324
+ "Position": 236.438324,
+ "HyperDash": false
},
{
"StartTime": 9500,
- "Position": 237.2304
+ "Position": 237.2304,
+ "HyperDash": false
},
{
"StartTime": 9562,
- "Position": 241.253983
+ "Position": 241.253983,
+ "HyperDash": false
},
{
"StartTime": 9625,
- "Position": 233.950623
+ "Position": 233.950623,
+ "HyperDash": false
},
{
"StartTime": 9687,
- "Position": 265.3786
+ "Position": 265.3786,
+ "HyperDash": false
},
{
"StartTime": 9750,
- "Position": 236.8865
+ "Position": 236.8865,
+ "HyperDash": false
},
{
"StartTime": 9812,
- "Position": 273.38974
+ "Position": 273.38974,
+ "HyperDash": false
},
{
"StartTime": 9875,
- "Position": 267.701874
+ "Position": 267.701874,
+ "HyperDash": false
},
{
"StartTime": 9937,
- "Position": 263.2331
+ "Position": 263.2331,
+ "HyperDash": false
},
{
"StartTime": 10000,
- "Position": 270.339874
+ "Position": 270.339874,
+ "HyperDash": false
},
{
"StartTime": 10062,
- "Position": 291.9349
+ "Position": 291.9349,
+ "HyperDash": false
},
{
"StartTime": 10125,
- "Position": 294.2969
+ "Position": 294.2969,
+ "HyperDash": false
},
{
"StartTime": 10187,
- "Position": 307.834137
+ "Position": 307.834137,
+ "HyperDash": false
},
{
"StartTime": 10250,
- "Position": 310.6449
+ "Position": 310.6449,
+ "HyperDash": false
},
{
"StartTime": 10312,
- "Position": 344.746338
+ "Position": 344.746338,
+ "HyperDash": false
},
{
"StartTime": 10375,
- "Position": 349.21875
+ "Position": 349.21875,
+ "HyperDash": false
},
{
"StartTime": 10437,
- "Position": 373.943
+ "Position": 373.943,
+ "HyperDash": false
},
{
"StartTime": 10500,
- "Position": 401.0588
+ "Position": 401.0588,
+ "HyperDash": false
},
{
"StartTime": 10558,
- "Position": 421.21347
+ "Position": 421.21347,
+ "HyperDash": false
},
{
"StartTime": 10616,
- "Position": 431.6034
+ "Position": 431.6034,
+ "HyperDash": false
},
{
"StartTime": 10674,
- "Position": 433.835754
+ "Position": 433.835754,
+ "HyperDash": false
},
{
"StartTime": 10732,
- "Position": 452.5042
+ "Position": 452.5042,
+ "HyperDash": false
},
{
"StartTime": 10790,
- "Position": 486.290955
+ "Position": 486.290955,
+ "HyperDash": false
},
{
"StartTime": 10848,
- "Position": 488.943237
+ "Position": 488.943237,
+ "HyperDash": false
},
{
"StartTime": 10906,
- "Position": 493.3372
+ "Position": 493.3372,
+ "HyperDash": false
},
{
"StartTime": 10999,
- "Position": 508.166229
+ "Position": 508.166229,
+ "HyperDash": false
}
]
},
@@ -563,39 +697,48 @@
"StartTime": 11500,
"Objects": [{
"StartTime": 11500,
- "Position": 97
+ "Position": 97,
+ "HyperDash": false
},
{
"StartTime": 11562,
- "Position": 267
+ "Position": 267,
+ "HyperDash": false
},
{
"StartTime": 11625,
- "Position": 116
+ "Position": 116,
+ "HyperDash": false
},
{
"StartTime": 11687,
- "Position": 451
+ "Position": 451,
+ "HyperDash": false
},
{
"StartTime": 11750,
- "Position": 414
+ "Position": 414,
+ "HyperDash": false
},
{
"StartTime": 11812,
- "Position": 88
+ "Position": 88,
+ "HyperDash": false
},
{
"StartTime": 11875,
- "Position": 257
+ "Position": 257,
+ "HyperDash": false
},
{
"StartTime": 11937,
- "Position": 175
+ "Position": 175,
+ "HyperDash": false
},
{
"StartTime": 12000,
- "Position": 38
+ "Position": 38,
+ "HyperDash": false
}
]
},
@@ -603,263 +746,328 @@
"StartTime": 12500,
"Objects": [{
"StartTime": 12500,
- "Position": 512
+ "Position": 512,
+ "HyperDash": false
},
{
"StartTime": 12562,
- "Position": 494.3132
+ "Position": 494.3132,
+ "HyperDash": false
},
{
"StartTime": 12625,
- "Position": 461.3089
+ "Position": 461.3089,
+ "HyperDash": false
},
{
"StartTime": 12687,
- "Position": 469.6221
+ "Position": 469.6221,
+ "HyperDash": false
},
{
"StartTime": 12750,
- "Position": 441.617767
+ "Position": 441.617767,
+ "HyperDash": false
},
{
"StartTime": 12812,
- "Position": 402.930969
+ "Position": 402.930969,
+ "HyperDash": false
},
{
"StartTime": 12875,
- "Position": 407.926666
+ "Position": 407.926666,
+ "HyperDash": false
},
{
"StartTime": 12937,
- "Position": 364.239868
+ "Position": 364.239868,
+ "HyperDash": false
},
{
"StartTime": 13000,
- "Position": 353.235535
+ "Position": 353.235535,
+ "HyperDash": false
},
{
"StartTime": 13062,
- "Position": 320.548767
+ "Position": 320.548767,
+ "HyperDash": false
},
{
"StartTime": 13125,
- "Position": 303.544434
+ "Position": 303.544434,
+ "HyperDash": false
},
{
"StartTime": 13187,
- "Position": 295.857635
+ "Position": 295.857635,
+ "HyperDash": false
},
{
"StartTime": 13250,
- "Position": 265.853333
+ "Position": 265.853333,
+ "HyperDash": false
},
{
"StartTime": 13312,
- "Position": 272.166534
+ "Position": 272.166534,
+ "HyperDash": false
},
{
"StartTime": 13375,
- "Position": 240.1622
+ "Position": 240.1622,
+ "HyperDash": false
},
{
"StartTime": 13437,
- "Position": 229.4754
+ "Position": 229.4754,
+ "HyperDash": false
},
{
"StartTime": 13500,
- "Position": 194.471069
+ "Position": 194.471069,
+ "HyperDash": false
},
{
"StartTime": 13562,
- "Position": 158.784271
+ "Position": 158.784271,
+ "HyperDash": false
},
{
"StartTime": 13625,
- "Position": 137.779968
+ "Position": 137.779968,
+ "HyperDash": false
},
{
"StartTime": 13687,
- "Position": 147.09314
+ "Position": 147.09314,
+ "HyperDash": false
},
{
"StartTime": 13750,
- "Position": 122.088837
+ "Position": 122.088837,
+ "HyperDash": false
},
{
"StartTime": 13812,
- "Position": 77.40204
+ "Position": 77.40204,
+ "HyperDash": false
},
{
"StartTime": 13875,
- "Position": 79.3977356
+ "Position": 79.3977356,
+ "HyperDash": false
},
{
"StartTime": 13937,
- "Position": 56.710907
+ "Position": 56.710907,
+ "HyperDash": false
},
{
"StartTime": 14000,
- "Position": 35.7066345
+ "Position": 35.7066345,
+ "HyperDash": false
},
{
"StartTime": 14062,
- "Position": 1.01980591
+ "Position": 1.01980591,
+ "HyperDash": false
},
{
"StartTime": 14125,
- "Position": 0
+ "Position": 0,
+ "HyperDash": false
},
{
"StartTime": 14187,
- "Position": 21.7696266
+ "Position": 21.7696266,
+ "HyperDash": false
},
{
"StartTime": 14250,
- "Position": 49.0119171
+ "Position": 49.0119171,
+ "HyperDash": false
},
{
"StartTime": 14312,
- "Position": 48.9488258
+ "Position": 48.9488258,
+ "HyperDash": false
},
{
"StartTime": 14375,
- "Position": 87.19112
+ "Position": 87.19112,
+ "HyperDash": false
},
{
"StartTime": 14437,
- "Position": 97.12803
+ "Position": 97.12803,
+ "HyperDash": false
},
{
"StartTime": 14500,
- "Position": 118.370323
+ "Position": 118.370323,
+ "HyperDash": false
},
{
"StartTime": 14562,
- "Position": 130.307236
+ "Position": 130.307236,
+ "HyperDash": false
},
{
"StartTime": 14625,
- "Position": 154.549515
+ "Position": 154.549515,
+ "HyperDash": false
},
{
"StartTime": 14687,
- "Position": 190.486435
+ "Position": 190.486435,
+ "HyperDash": false
},
{
"StartTime": 14750,
- "Position": 211.728714
+ "Position": 211.728714,
+ "HyperDash": false
},
{
"StartTime": 14812,
- "Position": 197.665634
+ "Position": 197.665634,
+ "HyperDash": false
},
{
"StartTime": 14875,
- "Position": 214.907928
+ "Position": 214.907928,
+ "HyperDash": false
},
{
"StartTime": 14937,
- "Position": 263.844849
+ "Position": 263.844849,
+ "HyperDash": false
},
{
"StartTime": 15000,
- "Position": 271.087128
+ "Position": 271.087128,
+ "HyperDash": false
},
{
"StartTime": 15062,
- "Position": 270.024017
+ "Position": 270.024017,
+ "HyperDash": false
},
{
"StartTime": 15125,
- "Position": 308.266327
+ "Position": 308.266327,
+ "HyperDash": false
},
{
"StartTime": 15187,
- "Position": 313.203247
+ "Position": 313.203247,
+ "HyperDash": false
},
{
"StartTime": 15250,
- "Position": 328.445526
+ "Position": 328.445526,
+ "HyperDash": false
},
{
"StartTime": 15312,
- "Position": 370.382446
+ "Position": 370.382446,
+ "HyperDash": false
},
{
"StartTime": 15375,
- "Position": 387.624725
+ "Position": 387.624725,
+ "HyperDash": false
},
{
"StartTime": 15437,
- "Position": 421.561646
+ "Position": 421.561646,
+ "HyperDash": false
},
{
"StartTime": 15500,
- "Position": 423.803925
+ "Position": 423.803925,
+ "HyperDash": false
},
{
"StartTime": 15562,
- "Position": 444.740845
+ "Position": 444.740845,
+ "HyperDash": false
},
{
"StartTime": 15625,
- "Position": 469.983124
+ "Position": 469.983124,
+ "HyperDash": false
},
{
"StartTime": 15687,
- "Position": 473.920044
+ "Position": 473.920044,
+ "HyperDash": false
},
{
"StartTime": 15750,
- "Position": 501.162323
+ "Position": 501.162323,
+ "HyperDash": false
},
{
"StartTime": 15812,
- "Position": 488.784332
+ "Position": 488.784332,
+ "HyperDash": false
},
{
"StartTime": 15875,
- "Position": 466.226227
+ "Position": 466.226227,
+ "HyperDash": false
},
{
"StartTime": 15937,
- "Position": 445.978638
+ "Position": 445.978638,
+ "HyperDash": false
},
{
"StartTime": 16000,
- "Position": 446.420532
+ "Position": 446.420532,
+ "HyperDash": false
},
{
"StartTime": 16058,
- "Position": 428.4146
+ "Position": 428.4146,
+ "HyperDash": false
},
{
"StartTime": 16116,
- "Position": 420.408844
+ "Position": 420.408844,
+ "HyperDash": false
},
{
"StartTime": 16174,
- "Position": 374.402924
+ "Position": 374.402924,
+ "HyperDash": false
},
{
"StartTime": 16232,
- "Position": 371.397156
+ "Position": 371.397156,
+ "HyperDash": false
},
{
"StartTime": 16290,
- "Position": 350.391235
+ "Position": 350.391235,
+ "HyperDash": false
},
{
"StartTime": 16348,
- "Position": 340.385468
+ "Position": 340.385468,
+ "HyperDash": false
},
{
"StartTime": 16406,
- "Position": 337.3797
+ "Position": 337.3797,
+ "HyperDash": false
},
{
"StartTime": 16500,
- "Position": 291.1977
+ "Position": 291.1977,
+ "HyperDash": false
}
]
},
@@ -867,71 +1075,88 @@
"StartTime": 17000,
"Objects": [{
"StartTime": 17000,
- "Position": 256
+ "Position": 256,
+ "HyperDash": false
},
{
"StartTime": 17062,
- "Position": 247.16
+ "Position": 247.16,
+ "HyperDash": false
},
{
"StartTime": 17125,
- "Position": 211
+ "Position": 211,
+ "HyperDash": false
},
{
"StartTime": 17187,
- "Position": 183.16
+ "Position": 183.16,
+ "HyperDash": false
},
{
"StartTime": 17250,
- "Position": 176
+ "Position": 176,
+ "HyperDash": false
},
{
"StartTime": 17312,
- "Position": 204.84
+ "Position": 204.84,
+ "HyperDash": false
},
{
"StartTime": 17375,
- "Position": 218
+ "Position": 218,
+ "HyperDash": false
},
{
"StartTime": 17437,
- "Position": 231.84
+ "Position": 231.84,
+ "HyperDash": false
},
{
"StartTime": 17500,
- "Position": 256
+ "Position": 256,
+ "HyperDash": false
},
{
"StartTime": 17562,
- "Position": 229.16
+ "Position": 229.16,
+ "HyperDash": false
},
{
"StartTime": 17625,
- "Position": 227
+ "Position": 227,
+ "HyperDash": false
},
{
"StartTime": 17687,
- "Position": 186.16
+ "Position": 186.16,
+ "HyperDash": false
},
{
"StartTime": 17750,
- "Position": 176
+ "Position": 176,
+ "HyperDash": false
},
{
"StartTime": 17803,
- "Position": 211.959991
+ "Position": 211.959991,
+ "HyperDash": false
},
{
"StartTime": 17857,
- "Position": 197.23999
+ "Position": 197.23999,
+ "HyperDash": false
},
{
"StartTime": 17910,
- "Position": 225.200012
+ "Position": 225.200012,
+ "HyperDash": false
},
{
"StartTime": 18000,
- "Position": 256
+ "Position": 256,
+ "HyperDash": false
}
]
},
@@ -939,71 +1164,88 @@
"StartTime": 18500,
"Objects": [{
"StartTime": 18500,
- "Position": 437
+ "Position": 437,
+ "HyperDash": false
},
{
"StartTime": 18559,
- "Position": 289
+ "Position": 289,
+ "HyperDash": false
},
{
"StartTime": 18618,
- "Position": 464
+ "Position": 464,
+ "HyperDash": false
},
{
"StartTime": 18678,
- "Position": 36
+ "Position": 36,
+ "HyperDash": false
},
{
"StartTime": 18737,
- "Position": 378
+ "Position": 378,
+ "HyperDash": false
},
{
"StartTime": 18796,
- "Position": 297
+ "Position": 297,
+ "HyperDash": false
},
{
"StartTime": 18856,
- "Position": 418
+ "Position": 418,
+ "HyperDash": false
},
{
"StartTime": 18915,
- "Position": 329
+ "Position": 329,
+ "HyperDash": false
},
{
"StartTime": 18975,
- "Position": 338
+ "Position": 338,
+ "HyperDash": false
},
{
"StartTime": 19034,
- "Position": 394
+ "Position": 394,
+ "HyperDash": false
},
{
"StartTime": 19093,
- "Position": 40
+ "Position": 40,
+ "HyperDash": false
},
{
"StartTime": 19153,
- "Position": 13
+ "Position": 13,
+ "HyperDash": false
},
{
"StartTime": 19212,
- "Position": 80
+ "Position": 80,
+ "HyperDash": false
},
{
"StartTime": 19271,
- "Position": 138
+ "Position": 138,
+ "HyperDash": false
},
{
"StartTime": 19331,
- "Position": 311
+ "Position": 311,
+ "HyperDash": false
},
{
"StartTime": 19390,
- "Position": 216
+ "Position": 216,
+ "HyperDash": false
},
{
"StartTime": 19450,
- "Position": 310
+ "Position": 310,
+ "HyperDash": false
}
]
},
@@ -1011,263 +1253,328 @@
"StartTime": 19875,
"Objects": [{
"StartTime": 19875,
- "Position": 216
+ "Position": 216,
+ "HyperDash": false
},
{
"StartTime": 19937,
- "Position": 228.307053
+ "Position": 228.307053,
+ "HyperDash": false
},
{
"StartTime": 20000,
- "Position": 214.036865
+ "Position": 214.036865,
+ "HyperDash": false
},
{
"StartTime": 20062,
- "Position": 224.312088
+ "Position": 224.312088,
+ "HyperDash": false
},
{
"StartTime": 20125,
- "Position": 253.838928
+ "Position": 253.838928,
+ "HyperDash": false
},
{
"StartTime": 20187,
- "Position": 259.9743
+ "Position": 259.9743,
+ "HyperDash": false
},
{
"StartTime": 20250,
- "Position": 299.999146
+ "Position": 299.999146,
+ "HyperDash": false
},
{
"StartTime": 20312,
- "Position": 289.669067
+ "Position": 289.669067,
+ "HyperDash": false
},
{
"StartTime": 20375,
- "Position": 317.446747
+ "Position": 317.446747,
+ "HyperDash": false
},
{
"StartTime": 20437,
- "Position": 344.750275
+ "Position": 344.750275,
+ "HyperDash": false
},
{
"StartTime": 20500,
- "Position": 328.0156
+ "Position": 328.0156,
+ "HyperDash": false
},
{
"StartTime": 20562,
- "Position": 331.472168
+ "Position": 331.472168,
+ "HyperDash": false
},
{
"StartTime": 20625,
- "Position": 302.165466
+ "Position": 302.165466,
+ "HyperDash": false
},
{
"StartTime": 20687,
- "Position": 303.044617
+ "Position": 303.044617,
+ "HyperDash": false
},
{
"StartTime": 20750,
- "Position": 306.457367
+ "Position": 306.457367,
+ "HyperDash": false
},
{
"StartTime": 20812,
- "Position": 265.220581
+ "Position": 265.220581,
+ "HyperDash": false
},
{
"StartTime": 20875,
- "Position": 270.3294
+ "Position": 270.3294,
+ "HyperDash": false
},
{
"StartTime": 20937,
- "Position": 257.57605
+ "Position": 257.57605,
+ "HyperDash": false
},
{
"StartTime": 21000,
- "Position": 247.803329
+ "Position": 247.803329,
+ "HyperDash": false
},
{
"StartTime": 21062,
- "Position": 225.958359
+ "Position": 225.958359,
+ "HyperDash": false
},
{
"StartTime": 21125,
- "Position": 201.79332
+ "Position": 201.79332,
+ "HyperDash": false
},
{
"StartTime": 21187,
- "Position": 170.948349
+ "Position": 170.948349,
+ "HyperDash": false
},
{
"StartTime": 21250,
- "Position": 146.78334
+ "Position": 146.78334,
+ "HyperDash": false
},
{
"StartTime": 21312,
- "Position": 149.93837
+ "Position": 149.93837,
+ "HyperDash": false
},
{
"StartTime": 21375,
- "Position": 119.121056
+ "Position": 119.121056,
+ "HyperDash": false
},
{
"StartTime": 21437,
- "Position": 133.387573
+ "Position": 133.387573,
+ "HyperDash": false
},
{
"StartTime": 21500,
- "Position": 117.503014
+ "Position": 117.503014,
+ "HyperDash": false
},
{
"StartTime": 21562,
- "Position": 103.749374
+ "Position": 103.749374,
+ "HyperDash": false
},
{
"StartTime": 21625,
- "Position": 127.165535
+ "Position": 127.165535,
+ "HyperDash": false
},
{
"StartTime": 21687,
- "Position": 113.029991
+ "Position": 113.029991,
+ "HyperDash": false
},
{
"StartTime": 21750,
- "Position": 101.547928
+ "Position": 101.547928,
+ "HyperDash": false
},
{
"StartTime": 21812,
- "Position": 133.856232
+ "Position": 133.856232,
+ "HyperDash": false
},
{
"StartTime": 21875,
- "Position": 124.28746
+ "Position": 124.28746,
+ "HyperDash": false
},
{
"StartTime": 21937,
- "Position": 121.754929
+ "Position": 121.754929,
+ "HyperDash": false
},
{
"StartTime": 22000,
- "Position": 155.528732
+ "Position": 155.528732,
+ "HyperDash": false
},
{
"StartTime": 22062,
- "Position": 142.1691
+ "Position": 142.1691,
+ "HyperDash": false
},
{
"StartTime": 22125,
- "Position": 186.802155
+ "Position": 186.802155,
+ "HyperDash": false
},
{
"StartTime": 22187,
- "Position": 198.6452
+ "Position": 198.6452,
+ "HyperDash": false
},
{
"StartTime": 22250,
- "Position": 191.892181
+ "Position": 191.892181,
+ "HyperDash": false
},
{
"StartTime": 22312,
- "Position": 232.713028
+ "Position": 232.713028,
+ "HyperDash": false
},
{
"StartTime": 22375,
- "Position": 240.4715
+ "Position": 240.4715,
+ "HyperDash": false
},
{
"StartTime": 22437,
- "Position": 278.3719
+ "Position": 278.3719,
+ "HyperDash": false
},
{
"StartTime": 22500,
- "Position": 288.907257
+ "Position": 288.907257,
+ "HyperDash": false
},
{
"StartTime": 22562,
- "Position": 297.353119
+ "Position": 297.353119,
+ "HyperDash": false
},
{
"StartTime": 22625,
- "Position": 301.273376
+ "Position": 301.273376,
+ "HyperDash": false
},
{
"StartTime": 22687,
- "Position": 339.98288
+ "Position": 339.98288,
+ "HyperDash": false
},
{
"StartTime": 22750,
- "Position": 353.078552
+ "Position": 353.078552,
+ "HyperDash": false
},
{
"StartTime": 22812,
- "Position": 363.8958
+ "Position": 363.8958,
+ "HyperDash": false
},
{
"StartTime": 22875,
- "Position": 398.054047
+ "Position": 398.054047,
+ "HyperDash": false
},
{
"StartTime": 22937,
- "Position": 419.739441
+ "Position": 419.739441,
+ "HyperDash": false
},
{
"StartTime": 23000,
- "Position": 435.178467
+ "Position": 435.178467,
+ "HyperDash": false
},
{
"StartTime": 23062,
- "Position": 420.8687
+ "Position": 420.8687,
+ "HyperDash": false
},
{
"StartTime": 23125,
- "Position": 448.069977
+ "Position": 448.069977,
+ "HyperDash": false
},
{
"StartTime": 23187,
- "Position": 425.688477
+ "Position": 425.688477,
+ "HyperDash": false
},
{
"StartTime": 23250,
- "Position": 426.9612
+ "Position": 426.9612,
+ "HyperDash": false
},
{
"StartTime": 23312,
- "Position": 454.92807
+ "Position": 454.92807,
+ "HyperDash": false
},
{
"StartTime": 23375,
- "Position": 439.749878
+ "Position": 439.749878,
+ "HyperDash": false
},
{
"StartTime": 23433,
- "Position": 440.644684
+ "Position": 440.644684,
+ "HyperDash": false
},
{
"StartTime": 23491,
- "Position": 445.7359
+ "Position": 445.7359,
+ "HyperDash": false
},
{
"StartTime": 23549,
- "Position": 432.0944
+ "Position": 432.0944,
+ "HyperDash": false
},
{
"StartTime": 23607,
- "Position": 415.796173
+ "Position": 415.796173,
+ "HyperDash": false
},
{
"StartTime": 23665,
- "Position": 407.897461
+ "Position": 407.897461,
+ "HyperDash": false
},
{
"StartTime": 23723,
- "Position": 409.462555
+ "Position": 409.462555,
+ "HyperDash": false
},
{
"StartTime": 23781,
- "Position": 406.53775
+ "Position": 406.53775,
+ "HyperDash": false
},
{
"StartTime": 23874,
- "Position": 408.720825
+ "Position": 408.720825,
+ "HyperDash": false
}
]
}
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash-expected-conversion.json
new file mode 100644
index 0000000000..b2e9431f13
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash-expected-conversion.json
@@ -0,0 +1,19 @@
+{
+ "Mappings": [{
+ "StartTime": 369,
+ "Objects": [{
+ "StartTime": 369,
+ "Position": 0,
+ "HyperDash": true
+ }]
+ },
+ {
+ "StartTime": 450,
+ "Objects": [{
+ "StartTime": 450,
+ "Position": 512,
+ "HyperDash": false
+ }]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash.osu
new file mode 100644
index 0000000000..db07f8c30e
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash.osu
@@ -0,0 +1,21 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.7
+Mode: 2
+
+[Difficulty]
+HPDrainRate:6
+CircleSize:4
+OverallDifficulty:9.6
+ApproachRate:9.6
+SliderMultiplier:1.9
+SliderTickRate:1
+
+[TimingPoints]
+2169,266.666666666667,4,2,1,70,1,0
+
+
+[HitObjects]
+0,192,369,1,0,0:0:0:0:
+512,192,450,1,0,0:0:0:0:
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json
index 83f9e30800..081b574c5b 100644
--- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json
@@ -3,147 +3,183 @@
"StartTime": 369,
"Objects": [{
"StartTime": 369,
- "Position": 177
+ "Position": 177,
+ "HyperDash": false
},
{
"StartTime": 450,
- "Position": 216.539276
+ "Position": 216.539276,
+ "HyperDash": false
},
{
"StartTime": 532,
- "Position": 256.5667
+ "Position": 256.5667,
+ "HyperDash": false
},
{
"StartTime": 614,
- "Position": 296.594116
+ "Position": 296.594116,
+ "HyperDash": false
},
{
"StartTime": 696,
- "Position": 336.621521
+ "Position": 336.621521,
+ "HyperDash": false
},
{
"StartTime": 778,
- "Position": 376.99762
+ "Position": 376.99762,
+ "HyperDash": false
},
{
"StartTime": 860,
- "Position": 337.318878
+ "Position": 337.318878,
+ "HyperDash": false
},
{
"StartTime": 942,
- "Position": 297.291443
+ "Position": 297.291443,
+ "HyperDash": false
},
{
"StartTime": 1024,
- "Position": 257.264038
+ "Position": 257.264038,
+ "HyperDash": false
},
{
"StartTime": 1106,
- "Position": 217.2366
+ "Position": 217.2366,
+ "HyperDash": false
},
{
"StartTime": 1188,
- "Position": 177
+ "Position": 177,
+ "HyperDash": false
},
{
"StartTime": 1270,
- "Position": 216.818192
+ "Position": 216.818192,
+ "HyperDash": false
},
{
"StartTime": 1352,
- "Position": 256.8456
+ "Position": 256.8456,
+ "HyperDash": false
},
{
"StartTime": 1434,
- "Position": 296.873047
+ "Position": 296.873047,
+ "HyperDash": false
},
{
"StartTime": 1516,
- "Position": 336.900452
+ "Position": 336.900452,
+ "HyperDash": false
},
{
"StartTime": 1598,
- "Position": 376.99762
+ "Position": 376.99762,
+ "HyperDash": false
},
{
"StartTime": 1680,
- "Position": 337.039948
+ "Position": 337.039948,
+ "HyperDash": false
},
{
"StartTime": 1762,
- "Position": 297.0125
+ "Position": 297.0125,
+ "HyperDash": false
},
{
"StartTime": 1844,
- "Position": 256.9851
+ "Position": 256.9851,
+ "HyperDash": false
},
{
"StartTime": 1926,
- "Position": 216.957672
+ "Position": 216.957672,
+ "HyperDash": false
},
{
"StartTime": 2008,
- "Position": 177
+ "Position": 177,
+ "HyperDash": false
},
{
"StartTime": 2090,
- "Position": 217.097137
+ "Position": 217.097137,
+ "HyperDash": false
},
{
"StartTime": 2172,
- "Position": 257.124573
+ "Position": 257.124573,
+ "HyperDash": false
},
{
"StartTime": 2254,
- "Position": 297.152
+ "Position": 297.152,
+ "HyperDash": false
},
{
"StartTime": 2336,
- "Position": 337.179443
+ "Position": 337.179443,
+ "HyperDash": false
},
{
"StartTime": 2418,
- "Position": 376.99762
+ "Position": 376.99762,
+ "HyperDash": false
},
{
"StartTime": 2500,
- "Position": 336.760956
+ "Position": 336.760956,
+ "HyperDash": false
},
{
"StartTime": 2582,
- "Position": 296.733643
+ "Position": 296.733643,
+ "HyperDash": false
},
{
"StartTime": 2664,
- "Position": 256.7062
+ "Position": 256.7062,
+ "HyperDash": false
},
{
"StartTime": 2746,
- "Position": 216.678772
+ "Position": 216.678772,
+ "HyperDash": false
},
{
"StartTime": 2828,
- "Position": 177
+ "Position": 177,
+ "HyperDash": false
},
{
"StartTime": 2909,
- "Position": 216.887909
+ "Position": 216.887909,
+ "HyperDash": false
},
{
"StartTime": 2991,
- "Position": 256.915344
+ "Position": 256.915344,
+ "HyperDash": false
},
{
"StartTime": 3073,
- "Position": 296.942749
+ "Position": 296.942749,
+ "HyperDash": false
},
{
"StartTime": 3155,
- "Position": 336.970184
+ "Position": 336.970184,
+ "HyperDash": false
},
{
"StartTime": 3237,
- "Position": 376.99762
+ "Position": 376.99762,
+ "HyperDash": false
}
]
}]
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json
index 7333b600fb..01f474c149 100644
--- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json
@@ -3,71 +3,88 @@
"StartTime": 369,
"Objects": [{
"StartTime": 369,
- "Position": 65
+ "Position": 65,
+ "HyperDash": false
},
{
"StartTime": 450,
- "Position": 482
+ "Position": 482,
+ "HyperDash": false
},
{
"StartTime": 532,
- "Position": 164
+ "Position": 164,
+ "HyperDash": false
},
{
"StartTime": 614,
- "Position": 315
+ "Position": 315,
+ "HyperDash": false
},
{
"StartTime": 696,
- "Position": 145
+ "Position": 145,
+ "HyperDash": false
},
{
"StartTime": 778,
- "Position": 159
+ "Position": 159,
+ "HyperDash": false
},
{
"StartTime": 860,
- "Position": 310
+ "Position": 310,
+ "HyperDash": false
},
{
"StartTime": 942,
- "Position": 441
+ "Position": 441,
+ "HyperDash": false
},
{
"StartTime": 1024,
- "Position": 428
+ "Position": 428,
+ "HyperDash": false
},
{
"StartTime": 1106,
- "Position": 243
+ "Position": 243,
+ "HyperDash": false
},
{
"StartTime": 1188,
- "Position": 422
+ "Position": 422,
+ "HyperDash": false
},
{
"StartTime": 1270,
- "Position": 481
+ "Position": 481,
+ "HyperDash": false
},
{
"StartTime": 1352,
- "Position": 104
+ "Position": 104,
+ "HyperDash": false
},
{
"StartTime": 1434,
- "Position": 473
+ "Position": 473,
+ "HyperDash": false
},
{
"StartTime": 1516,
- "Position": 135
+ "Position": 135,
+ "HyperDash": false
},
{
"StartTime": 1598,
- "Position": 360
+ "Position": 360,
+ "HyperDash": false
},
{
"StartTime": 1680,
- "Position": 123
+ "Position": 123,
+ "HyperDash": false
}
]
}]
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json
index bbc16ab912..8eaaf3bb90 100644
--- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json
@@ -3,231 +3,264 @@
"StartTime": 369,
"Objects": [{
"StartTime": 369,
- "Position": 258
+ "Position": 258,
+ "HyperDash": false
}]
},
{
"StartTime": 450,
"Objects": [{
"StartTime": 450,
- "Position": 254
+ "Position": 254,
+ "HyperDash": false
}]
},
{
"StartTime": 532,
"Objects": [{
"StartTime": 532,
- "Position": 241
+ "Position": 241,
+ "HyperDash": false
}]
},
{
"StartTime": 614,
"Objects": [{
"StartTime": 614,
- "Position": 238
+ "Position": 238,
+ "HyperDash": false
}]
},
{
"StartTime": 696,
"Objects": [{
"StartTime": 696,
- "Position": 238
+ "Position": 238,
+ "HyperDash": false
}]
},
{
"StartTime": 778,
"Objects": [{
"StartTime": 778,
- "Position": 278
+ "Position": 278,
+ "HyperDash": false
}]
},
{
"StartTime": 860,
"Objects": [{
"StartTime": 860,
- "Position": 238
+ "Position": 238,
+ "HyperDash": false
}]
},
{
"StartTime": 942,
"Objects": [{
"StartTime": 942,
- "Position": 278
+ "Position": 278,
+ "HyperDash": false
}]
},
{
"StartTime": 1024,
"Objects": [{
"StartTime": 1024,
- "Position": 238
+ "Position": 238,
+ "HyperDash": false
}]
},
{
"StartTime": 1106,
"Objects": [{
"StartTime": 1106,
- "Position": 278
+ "Position": 278,
+ "HyperDash": false
}]
},
{
"StartTime": 1188,
"Objects": [{
"StartTime": 1188,
- "Position": 278
+ "Position": 278,
+ "HyperDash": false
}]
},
{
"StartTime": 1270,
"Objects": [{
"StartTime": 1270,
- "Position": 278
+ "Position": 278,
+ "HyperDash": false
}]
},
{
"StartTime": 1352,
"Objects": [{
"StartTime": 1352,
- "Position": 238
+ "Position": 238,
+ "HyperDash": false
}]
},
{
"StartTime": 1434,
"Objects": [{
"StartTime": 1434,
- "Position": 258
+ "Position": 258,
+ "HyperDash": false
}]
},
{
"StartTime": 1516,
"Objects": [{
"StartTime": 1516,
- "Position": 253
+ "Position": 253,
+ "HyperDash": false
}]
},
{
"StartTime": 1598,
"Objects": [{
"StartTime": 1598,
- "Position": 238
+ "Position": 238,
+ "HyperDash": false
}]
},
{
"StartTime": 1680,
"Objects": [{
"StartTime": 1680,
- "Position": 260
+ "Position": 260,
+ "HyperDash": false
}]
},
{
"StartTime": 1762,
"Objects": [{
"StartTime": 1762,
- "Position": 238
+ "Position": 238,
+ "HyperDash": false
}]
},
{
"StartTime": 1844,
"Objects": [{
"StartTime": 1844,
- "Position": 278
+ "Position": 278,
+ "HyperDash": false
}]
},
{
"StartTime": 1926,
"Objects": [{
"StartTime": 1926,
- "Position": 278
+ "Position": 278,
+ "HyperDash": false
}]
},
{
"StartTime": 2008,
"Objects": [{
"StartTime": 2008,
- "Position": 238
+ "Position": 238,
+ "HyperDash": false
}]
},
{
"StartTime": 2090,
"Objects": [{
"StartTime": 2090,
- "Position": 238
+ "Position": 238,
+ "HyperDash": false
}]
},
{
"StartTime": 2172,
"Objects": [{
"StartTime": 2172,
- "Position": 243
+ "Position": 243,
+ "HyperDash": false
}]
},
{
"StartTime": 2254,
"Objects": [{
"StartTime": 2254,
- "Position": 278
+ "Position": 278,
+ "HyperDash": false
}]
},
{
"StartTime": 2336,
"Objects": [{
"StartTime": 2336,
- "Position": 278
+ "Position": 278,
+ "HyperDash": false
}]
},
{
"StartTime": 2418,
"Objects": [{
"StartTime": 2418,
- "Position": 238
+ "Position": 238,
+ "HyperDash": false
}]
},
{
"StartTime": 2500,
"Objects": [{
"StartTime": 2500,
- "Position": 258
+ "Position": 258,
+ "HyperDash": false
}]
},
{
"StartTime": 2582,
"Objects": [{
"StartTime": 2582,
- "Position": 256
+ "Position": 256,
+ "HyperDash": false
}]
},
{
"StartTime": 2664,
"Objects": [{
"StartTime": 2664,
- "Position": 242
+ "Position": 242,
+ "HyperDash": false
}]
},
{
"StartTime": 2746,
"Objects": [{
"StartTime": 2746,
- "Position": 238
+ "Position": 238,
+ "HyperDash": false
}]
},
{
"StartTime": 2828,
"Objects": [{
"StartTime": 2828,
- "Position": 238
+ "Position": 238,
+ "HyperDash": false
}]
},
{
"StartTime": 2909,
"Objects": [{
"StartTime": 2909,
- "Position": 271
+ "Position": 271,
+ "HyperDash": false
}]
},
{
"StartTime": 2991,
"Objects": [{
"StartTime": 2991,
- "Position": 254
+ "Position": 254,
+ "HyperDash": false
}]
}
]
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json
index 3bde97070c..5060389ad8 100644
--- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json
@@ -3,14 +3,16 @@
"StartTime": 3368,
"Objects": [{
"StartTime": 3368,
- "Position": 374
+ "Position": 374,
+ "HyperDash": false
}]
},
{
"StartTime": 3501,
"Objects": [{
"StartTime": 3501,
- "Position": 446
+ "Position": 446,
+ "HyperDash": false
}]
}
]
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json
index 58c52b6867..2378ba5511 100644
--- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json
@@ -1 +1,71 @@
-{"Mappings":[{"StartTime":19184.0,"Objects":[{"StartTime":19184.0,"Position":320.0},{"StartTime":19263.0,"Position":311.730255},{"StartTime":19343.0,"Position":324.6205},{"StartTime":19423.0,"Position":343.0907},{"StartTime":19503.0,"Position":372.2917},{"StartTime":19582.0,"Position":385.194733},{"StartTime":19662.0,"Position":379.0426},{"StartTime":19742.0,"Position":385.1066},{"StartTime":19822.0,"Position":391.624664},{"StartTime":19919.0,"Position":386.27832},{"StartTime":20016.0,"Position":380.117035},{"StartTime":20113.0,"Position":381.664154},{"StartTime":20247.0,"Position":370.872864}]}]}
\ No newline at end of file
+{
+ "Mappings": [{
+ "StartTime": 19184,
+ "Objects": [{
+ "StartTime": 19184,
+ "Position": 320,
+ "HyperDash": false
+ },
+ {
+ "StartTime": 19263,
+ "Position": 311.730255,
+ "HyperDash": false
+ },
+ {
+ "StartTime": 19343,
+ "Position": 324.6205,
+ "HyperDash": false
+ },
+ {
+ "StartTime": 19423,
+ "Position": 343.0907,
+ "HyperDash": false
+ },
+ {
+ "StartTime": 19503,
+ "Position": 372.2917,
+ "HyperDash": false
+ },
+ {
+ "StartTime": 19582,
+ "Position": 385.194733,
+ "HyperDash": false
+ },
+ {
+ "StartTime": 19662,
+ "Position": 379.0426,
+ "HyperDash": false
+ },
+ {
+ "StartTime": 19742,
+ "Position": 385.1066,
+ "HyperDash": false
+ },
+ {
+ "StartTime": 19822,
+ "Position": 391.624664,
+ "HyperDash": false
+ },
+ {
+ "StartTime": 19919,
+ "Position": 386.27832,
+ "HyperDash": false
+ },
+ {
+ "StartTime": 20016,
+ "Position": 380.117035,
+ "HyperDash": false
+ },
+ {
+ "StartTime": 20113,
+ "Position": 381.664154,
+ "HyperDash": false
+ },
+ {
+ "StartTime": 20247,
+ "Position": 370.872864,
+ "HyperDash": false
+ }
+ ]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles-expected-conversion.json
index dd81947f1c..abd5b2afd1 100644
--- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles-expected-conversion.json
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles-expected-conversion.json
@@ -3,18 +3,21 @@
"StartTime": 2589,
"Objects": [{
"StartTime": 2589,
- "Position": 256
+ "Position": 256,
+ "HyperDash": false
}]
},
{
"StartTime": 2915,
"Objects": [{
"StartTime": 2915,
- "Position": 65
+ "Position": 65,
+ "HyperDash": false
},
{
"StartTime": 2916,
- "Position": 482
+ "Position": 482,
+ "HyperDash": false
}
]
},
@@ -22,11 +25,13 @@
"StartTime": 3078,
"Objects": [{
"StartTime": 3078,
- "Position": 164
+ "Position": 164,
+ "HyperDash": false
},
{
"StartTime": 3079,
- "Position": 315
+ "Position": 315,
+ "HyperDash": false
}
]
},
@@ -34,11 +39,13 @@
"StartTime": 3241,
"Objects": [{
"StartTime": 3241,
- "Position": 145
+ "Position": 145,
+ "HyperDash": false
},
{
"StartTime": 3242,
- "Position": 159
+ "Position": 159,
+ "HyperDash": false
}
]
},
@@ -46,11 +53,13 @@
"StartTime": 3404,
"Objects": [{
"StartTime": 3404,
- "Position": 310
+ "Position": 310,
+ "HyperDash": false
},
{
"StartTime": 3405,
- "Position": 441
+ "Position": 441,
+ "HyperDash": false
}
]
},
@@ -58,7 +67,8 @@
"StartTime": 5197,
"Objects": [{
"StartTime": 5197,
- "Position": 256
+ "Position": 256,
+ "HyperDash": false
}]
}
]
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-expected-conversion.json
index b69b1ae056..8a7847e065 100644
--- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-expected-conversion.json
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-expected-conversion.json
@@ -3,71 +3,88 @@
"StartTime": 18500,
"Objects": [{
"StartTime": 18500,
- "Position": 65
+ "Position": 65,
+ "HyperDash": false
},
{
"StartTime": 18559,
- "Position": 482
+ "Position": 482,
+ "HyperDash": false
},
{
"StartTime": 18618,
- "Position": 164
+ "Position": 164,
+ "HyperDash": false
},
{
"StartTime": 18678,
- "Position": 315
+ "Position": 315,
+ "HyperDash": false
},
{
"StartTime": 18737,
- "Position": 145
+ "Position": 145,
+ "HyperDash": false
},
{
"StartTime": 18796,
- "Position": 159
+ "Position": 159,
+ "HyperDash": false
},
{
"StartTime": 18856,
- "Position": 310
+ "Position": 310,
+ "HyperDash": false
},
{
"StartTime": 18915,
- "Position": 441
+ "Position": 441,
+ "HyperDash": false
},
{
"StartTime": 18975,
- "Position": 428
+ "Position": 428,
+ "HyperDash": false
},
{
"StartTime": 19034,
- "Position": 243
+ "Position": 243,
+ "HyperDash": false
},
{
"StartTime": 19093,
- "Position": 422
+ "Position": 422,
+ "HyperDash": false
},
{
"StartTime": 19153,
- "Position": 481
+ "Position": 481,
+ "HyperDash": false
},
{
"StartTime": 19212,
- "Position": 104
+ "Position": 104,
+ "HyperDash": false
},
{
"StartTime": 19271,
- "Position": 473
+ "Position": 473,
+ "HyperDash": false
},
{
"StartTime": 19331,
- "Position": 135
+ "Position": 135,
+ "HyperDash": false
},
{
"StartTime": 19390,
- "Position": 360
+ "Position": 360,
+ "HyperDash": false
},
{
"StartTime": 19450,
- "Position": 123
+ "Position": 123,
+ "HyperDash": false
}
]
}]
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs
index e1fad564a3..f6b3c3d665 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs
@@ -90,9 +90,9 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
.ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
.FadeOut(duration * 2);
- const float angle_variangle = 15; // should be less than 45
- directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4);
- directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5);
+ const float angle_variance = 15; // should be less than 45
+ directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variance, angle_variance, randomSeed, 4);
+ directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variance, angle_variance, randomSeed, 5);
this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
}
diff --git a/osu.Game.Rulesets.Mania.Tests.Android/MainActivity.cs b/osu.Game.Rulesets.Mania.Tests.Android/MainActivity.cs
index 0a3f05ae54..518071fd49 100644
--- a/osu.Game.Rulesets.Mania.Tests.Android/MainActivity.cs
+++ b/osu.Game.Rulesets.Mania.Tests.Android/MainActivity.cs
@@ -2,13 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using Android.App;
-using Android.Content.PM;
using osu.Framework.Android;
using osu.Game.Tests;
namespace osu.Game.Rulesets.Mania.Tests.Android
{
- [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
+ [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)]
public class MainActivity : AndroidGameActivity
{
protected override Framework.Game CreateGame() => new OsuTestBrowser();
diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist
index 8780204d5b..09ed2dd007 100644
--- a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist
+++ b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist
@@ -32,5 +32,7 @@
XSAppIconAssets
Assets.xcassets/AppIcon.appiconset
+ CADisableMinimumFrameDurationOnPhone
+
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs
index d3afbc63eb..5300747633 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs
@@ -4,6 +4,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Tests.Visual;
@@ -24,9 +25,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
}
[BackgroundDependencyLoader]
- private void load(RulesetConfigCache configCache)
+ private void load()
{
- var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
+ var config = (ManiaRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
config.BindWith(ManiaRulesetSetting.ScrollDirection, direction);
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs
index 24d2a786a0..a30e09cd29 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs
@@ -8,6 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
+using osu.Game.Database;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Screens.Edit;
@@ -55,13 +56,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
[Test]
public void TestDefaultSkin()
{
- AddStep("set default skin", () => skins.CurrentSkinInfo.Value = SkinInfo.Default);
+ AddStep("set default skin", () => skins.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLiveUnmanaged());
}
[Test]
public void TestLegacySkin()
{
- AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info);
+ AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.CreateInfo().ToLiveUnmanaged());
}
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index 948f088b4e..837474ad9e 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -14,7 +14,6 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
- [Timeout(10000)]
public class ManiaBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs
index 004793e1e5..9e6e0a7776 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs
@@ -24,13 +24,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
public TestSceneHitExplosion()
{
- int runcount = 0;
+ int runCount = 0;
AddRepeatStep("explode", () =>
{
- runcount++;
+ runCount++;
- if (runcount % 15 > 12)
+ if (runCount % 15 > 12)
return;
int poolIndex = 0;
@@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
c.Add(hitExplosionPools[poolIndex].Get(e =>
{
- e.Apply(new JudgementResult(new HitObject(), runcount % 6 == 0 ? new HoldNoteTickJudgement() : new ManiaJudgement()));
+ e.Apply(new JudgementResult(new HitObject(), runCount % 6 == 0 ? new HoldNoteTickJudgement() : new ManiaJudgement()));
e.Anchor = Anchor.Centre;
e.Origin = Anchor.Centre;
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs
index 449a6ff23d..4c688520ef 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs
@@ -4,7 +4,6 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
-using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@@ -14,6 +13,7 @@ using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Rulesets.Mania.Objects.Drawables;
@@ -24,9 +24,6 @@ namespace osu.Game.Rulesets.Mania.Tests
[TestFixture]
public class TestSceneTimingBasedNoteColouring : OsuTestScene
{
- [Resolved]
- private RulesetConfigCache configCache { get; set; }
-
private Bindable configTimingBasedNoteColouring;
private ManualClock clock;
@@ -48,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Tests
});
AddStep("retrieve config bindable", () =>
{
- var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
+ var config = (ManiaRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
configTimingBasedNoteColouring = config.GetBindable(ManiaRulesetSetting.TimingBasedNoteColouring);
});
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
index 93a9ce3dbd..0058f6f884 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
public override IEnumerable GetStatistics()
{
int notes = HitObjects.Count(s => s is Note);
- int holdnotes = HitObjects.Count(s => s is HoldNote);
+ int holdNotes = HitObjects.Count(s => s is HoldNote);
return new[]
{
@@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
{
Name = @"Hold Note Count",
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
- Content = holdnotes.ToString(),
+ Content = holdNotes.ToString(),
},
};
}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
index bfdef893e9..979a04ddf8 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
yield return v;
// Todo: osu!mania doesn't output MaxCombo attribute for some reason.
- yield return (ATTRIB_ID_STRAIN, StarRating);
+ yield return (ATTRIB_ID_DIFFICULTY, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier);
}
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
base.FromDatabaseAttributes(values);
- StarRating = values[ATTRIB_ID_STRAIN];
+ StarRating = values[ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER];
}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs
new file mode 100644
index 0000000000..da9634ba47
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs
@@ -0,0 +1,20 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Newtonsoft.Json;
+using osu.Game.Rulesets.Difficulty;
+
+namespace osu.Game.Rulesets.Mania.Difficulty
+{
+ public class ManiaPerformanceAttributes : PerformanceAttributes
+ {
+ [JsonProperty("difficulty")]
+ public double Difficulty { get; set; }
+
+ [JsonProperty("accuracy")]
+ public double Accuracy { get; set; }
+
+ [JsonProperty("scaled_score")]
+ public double ScaledScore { get; set; }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
index b04ff3548f..8a8c41bb8a 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
}
- public override double Calculate(Dictionary categoryDifficulty = null)
+ public override PerformanceAttributes Calculate()
{
mods = Score.Mods;
scaledScore = Score.TotalScore;
@@ -61,48 +61,46 @@ namespace osu.Game.Rulesets.Mania.Difficulty
if (mods.Any(m => m is ModEasy))
multiplier *= 0.5;
- double strainValue = computeStrainValue();
- double accValue = computeAccuracyValue(strainValue);
+ double difficultyValue = computeDifficultyValue();
+ double accValue = computeAccuracyValue(difficultyValue);
double totalValue =
Math.Pow(
- Math.Pow(strainValue, 1.1) +
+ Math.Pow(difficultyValue, 1.1) +
Math.Pow(accValue, 1.1), 1.0 / 1.1
) * multiplier;
- if (categoryDifficulty != null)
+ return new ManiaPerformanceAttributes
{
- categoryDifficulty["Strain"] = strainValue;
- categoryDifficulty["Accuracy"] = accValue;
- }
-
- return totalValue;
+ Difficulty = difficultyValue,
+ Accuracy = accValue,
+ ScaledScore = scaledScore,
+ Total = totalValue
+ };
}
- private double computeStrainValue()
+ private double computeDifficultyValue()
{
- // Obtain strain difficulty
- double strainValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0;
+ double difficultyValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0;
- // Longer maps are worth more
- strainValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
+ difficultyValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
if (scaledScore <= 500000)
- strainValue = 0;
+ difficultyValue = 0;
else if (scaledScore <= 600000)
- strainValue *= (scaledScore - 500000) / 100000 * 0.3;
+ difficultyValue *= (scaledScore - 500000) / 100000 * 0.3;
else if (scaledScore <= 700000)
- strainValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.25;
+ difficultyValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.25;
else if (scaledScore <= 800000)
- strainValue *= 0.55 + (scaledScore - 700000) / 100000 * 0.20;
+ difficultyValue *= 0.55 + (scaledScore - 700000) / 100000 * 0.20;
else if (scaledScore <= 900000)
- strainValue *= 0.75 + (scaledScore - 800000) / 100000 * 0.15;
+ difficultyValue *= 0.75 + (scaledScore - 800000) / 100000 * 0.15;
else
- strainValue *= 0.90 + (scaledScore - 900000) / 100000 * 0.1;
+ difficultyValue *= 0.90 + (scaledScore - 900000) / 100000 * 0.1;
- return strainValue;
+ return difficultyValue;
}
- private double computeAccuracyValue(double strainValue)
+ private double computeAccuracyValue(double difficultyValue)
{
if (Attributes.GreatHitWindow <= 0)
return 0;
@@ -110,12 +108,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667)
- * strainValue
+ * difficultyValue
* Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
- // Bonus for many hitcircles - it's harder to keep good accuracy up for longer
- // accuracyValue *= Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
-
return accuracyValue;
}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
index 5259fcbd5f..35889aea0c 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
@@ -9,7 +9,6 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
@@ -28,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
}
[BackgroundDependencyLoader]
- private void load(IScrollingInfo scrollingInfo)
+ private void load()
{
InternalChildren = new Drawable[]
{
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs
index 9d1f5429a1..1aa20f4737 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs
@@ -9,7 +9,6 @@ using osu.Framework.Bindables;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.UI;
@@ -46,12 +45,6 @@ namespace osu.Game.Rulesets.Mania.Edit
[Resolved]
private EditorBeatmap beatmap { get; set; }
- [Resolved]
- private IScrollingInfo scrollingInfo { get; set; }
-
- [Resolved]
- private Bindable working { get; set; }
-
[Resolved]
private OsuColour colours { get; set; }
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
index dc858fb54f..9fe1eb7932 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
@@ -7,16 +7,12 @@ using osu.Framework.Allocation;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit.Compose.Components;
namespace osu.Game.Rulesets.Mania.Edit
{
public class ManiaSelectionHandler : EditorSelectionHandler
{
- [Resolved]
- private IScrollingInfo scrollingInfo { get; set; }
-
[Resolved]
private HitObjectComposer composer { get; set; }
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index b0e7545d3e..6fc7dc018b 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
- public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new DrainingHealthProcessor(drainStartTime, 0.5);
+ public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new ManiaHealthProcessor(drainStartTime, 0.5);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs
new file mode 100644
index 0000000000..57c2ba9c6d
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Scoring
+{
+ public class ManiaHealthProcessor : DrainingHealthProcessor
+ {
+ ///
+ public ManiaHealthProcessor(double drainStartTime, double drainLenience = 0)
+ : base(drainStartTime, drainLenience)
+ {
+ }
+
+ protected override HitResult GetSimulatedHitResult(Judgement judgement)
+ {
+ // Users are not expected to attain perfect judgements for all notes due to the tighter hit window.
+ return judgement.MaxResult == HitResult.Perfect ? HitResult.Great : judgement.MaxResult;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
index 0588ae57d7..431bd77402 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
/// Mapping of to their corresponding
/// value.
///
- private static readonly IReadOnlyDictionary hitresult_mapping
+ private static readonly IReadOnlyDictionary hit_result_mapping
= new Dictionary
{
{ HitResult.Perfect, LegacyManiaSkinConfigurationLookups.Hit300g },
@@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
/// Mapping of to their corresponding
/// default filenames.
///
- private static readonly IReadOnlyDictionary default_hitresult_skin_filenames
+ private static readonly IReadOnlyDictionary default_hit_result_skin_filenames
= new Dictionary
{
{ HitResult.Perfect, "mania-hit300g" },
@@ -126,11 +126,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
private Drawable getResult(HitResult result)
{
- if (!hitresult_mapping.ContainsKey(result))
+ if (!hit_result_mapping.ContainsKey(result))
return null;
- string filename = this.GetManiaSkinConfig(hitresult_mapping[result])?.Value
- ?? default_hitresult_skin_filenames[result];
+ string filename = this.GetManiaSkinConfig(hit_result_mapping[result])?.Value
+ ?? default_hit_result_skin_filenames[result];
var animation = this.GetAnimation(filename, true, true);
return animation == null ? null : new LegacyManiaJudgementPiece(result, animation);
diff --git a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs
index 69b81d6d5c..562d7b04c4 100644
--- a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
- const float angle_variangle = 15; // should be less than 45
+ const float angle_variance = 15; // should be less than 45
const float roundness = 80;
const float initial_height = 10;
@@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.UI
Masking = true,
Size = new Vector2(0.01f, initial_height),
Blending = BlendingParameters.Additive,
- Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
+ Rotation = RNG.NextSingle(-angle_variance, angle_variance),
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
@@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Mania.UI
Masking = true,
Size = new Vector2(0.01f, initial_height),
Blending = BlendingParameters.Additive,
- Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
+ Rotation = RNG.NextSingle(-angle_variance, angle_variance),
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
index 8830c440c0..4cd6624ac6 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.UI
public override bool Remove(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Remove(h);
- public void Add(BarLine barline) => stages.ForEach(s => s.Add(barline));
+ public void Add(BarLine barLine) => stages.ForEach(s => s.Add(barLine));
///
/// Retrieves a column from a screen-space position.
diff --git a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs
index 90d3c6c4c7..9f4963b022 100644
--- a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs
@@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.Mania.UI
public JudgementResult Result { get; private set; }
- [Resolved]
- private Column column { get; set; }
-
private SkinnableDrawable skinnableExplosion;
public PoolableHitExplosion()
diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs
index 8c703e7a8a..94910bb410 100644
--- a/osu.Game.Rulesets.Mania/UI/Stage.cs
+++ b/osu.Game.Rulesets.Mania/UI/Stage.cs
@@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Mania.UI
public override bool Remove(DrawableHitObject h) => Columns.ElementAt(((ManiaHitObject)h.HitObject).Column - firstColumnIndex).Remove(h);
- public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline));
+ public void Add(BarLine barLine) => base.Add(new DrawableBarLine(barLine));
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
{
diff --git a/osu.Game.Rulesets.Osu.Tests.Android/MainActivity.cs b/osu.Game.Rulesets.Osu.Tests.Android/MainActivity.cs
index e6c508d99e..46c60f06a5 100644
--- a/osu.Game.Rulesets.Osu.Tests.Android/MainActivity.cs
+++ b/osu.Game.Rulesets.Osu.Tests.Android/MainActivity.cs
@@ -2,13 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using Android.App;
-using Android.Content.PM;
using osu.Framework.Android;
using osu.Game.Tests;
namespace osu.Game.Rulesets.Osu.Tests.Android
{
- [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
+ [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)]
public class MainActivity : AndroidGameActivity
{
protected override Framework.Game CreateGame() => new OsuTestBrowser();
diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist
index f79215cf54..dd032ef1c1 100644
--- a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist
+++ b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist
@@ -32,5 +32,7 @@
XSAppIconAssets
Assets.xcassets/AppIcon.appiconset
+ CADisableMinimumFrameDurationOnPhone
+
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
index 6bfe7f892b..e7bcd2cadc 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
@@ -1,7 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
+using Humanizer;
using NUnit.Framework;
+using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@@ -47,6 +50,126 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddBlueprint(new TestSliderBlueprint(slider), drawableObject);
});
+ [Test]
+ public void TestSelection()
+ {
+ moveMouseToControlPoint(0);
+ AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
+ assertSelectionCount(1);
+ assertSelected(0);
+
+ AddStep("click right mouse", () => InputManager.Click(MouseButton.Right));
+ assertSelectionCount(1);
+ assertSelected(0);
+
+ moveMouseToControlPoint(3);
+ AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
+ assertSelectionCount(1);
+ assertSelected(3);
+
+ AddStep("press control", () => InputManager.PressKey(Key.ControlLeft));
+ moveMouseToControlPoint(2);
+ AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
+ assertSelectionCount(2);
+ assertSelected(2);
+ assertSelected(3);
+
+ moveMouseToControlPoint(0);
+ AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
+ assertSelectionCount(3);
+ assertSelected(0);
+ assertSelected(2);
+ assertSelected(3);
+
+ AddStep("click right mouse", () => InputManager.Click(MouseButton.Right));
+ assertSelectionCount(3);
+ assertSelected(0);
+ assertSelected(2);
+ assertSelected(3);
+
+ AddStep("release control", () => InputManager.ReleaseKey(Key.ControlLeft));
+ AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
+ assertSelectionCount(1);
+ assertSelected(0);
+
+ moveMouseToRelativePosition(new Vector2(350, 0));
+ AddStep("ctrl+click to create new point", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.PressButton(MouseButton.Left);
+ });
+ assertSelectionCount(1);
+ assertSelected(3);
+
+ AddStep("release ctrl+click", () =>
+ {
+ InputManager.ReleaseButton(MouseButton.Left);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+ assertSelectionCount(1);
+ assertSelected(3);
+ }
+
+ [Test]
+ public void TestNewControlPointCreation()
+ {
+ moveMouseToRelativePosition(new Vector2(350, 0));
+ AddStep("ctrl+click to create new point", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.PressButton(MouseButton.Left);
+ });
+ AddAssert("slider has 6 control points", () => slider.Path.ControlPoints.Count == 6);
+ AddStep("release ctrl+click", () =>
+ {
+ InputManager.ReleaseButton(MouseButton.Left);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+
+ // ensure that the next drag doesn't attempt to move the placement that just finished.
+ moveMouseToRelativePosition(new Vector2(0, 50));
+ AddStep("press left mouse", () => InputManager.PressButton(MouseButton.Left));
+ moveMouseToRelativePosition(new Vector2(0, 100));
+ AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
+ assertControlPointPosition(3, new Vector2(350, 0));
+
+ moveMouseToRelativePosition(new Vector2(400, 75));
+ AddStep("ctrl+click to create new point", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.PressButton(MouseButton.Left);
+ });
+ AddAssert("slider has 7 control points", () => slider.Path.ControlPoints.Count == 7);
+ moveMouseToRelativePosition(new Vector2(350, 75));
+ AddStep("release ctrl+click", () =>
+ {
+ InputManager.ReleaseButton(MouseButton.Left);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+ assertControlPointPosition(5, new Vector2(350, 75));
+
+ // ensure that the next drag doesn't attempt to move the placement that just finished.
+ moveMouseToRelativePosition(new Vector2(0, 50));
+ AddStep("press left mouse", () => InputManager.PressButton(MouseButton.Left));
+ moveMouseToRelativePosition(new Vector2(0, 100));
+ AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
+ assertControlPointPosition(5, new Vector2(350, 75));
+ }
+
+ private void assertSelectionCount(int count) =>
+ AddAssert($"{count} control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == count);
+
+ private void assertSelected(int index) =>
+ AddAssert($"{(index + 1).ToOrdinalWords()} control point piece selected",
+ () => this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[index]).IsSelected.Value);
+
+ private void moveMouseToRelativePosition(Vector2 relativePosition) =>
+ AddStep($"move mouse to {relativePosition}", () =>
+ {
+ Vector2 position = slider.Position + relativePosition;
+ InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
+ });
+
[Test]
public void TestDragControlPoint()
{
@@ -60,6 +183,83 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertControlPointType(0, PathType.PerfectCurve);
}
+ [Test]
+ public void TestDragMultipleControlPoints()
+ {
+ moveMouseToControlPoint(2);
+ AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
+
+ AddStep("hold control", () => InputManager.PressKey(Key.LControl));
+
+ moveMouseToControlPoint(3);
+ AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
+
+ moveMouseToControlPoint(4);
+ AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
+
+ moveMouseToControlPoint(2);
+ AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
+
+ AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3);
+
+ addMovementStep(new Vector2(450, 50));
+ AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
+
+ AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3);
+
+ assertControlPointPosition(2, new Vector2(450, 50));
+ assertControlPointType(2, PathType.PerfectCurve);
+
+ assertControlPointPosition(3, new Vector2(550, 50));
+
+ assertControlPointPosition(4, new Vector2(550, 200));
+
+ AddStep("release control", () => InputManager.ReleaseKey(Key.LControl));
+ }
+
+ [Test]
+ public void TestDragMultipleControlPointsIncludingHead()
+ {
+ moveMouseToControlPoint(0);
+ AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
+
+ AddStep("hold control", () => InputManager.PressKey(Key.LControl));
+
+ moveMouseToControlPoint(3);
+ AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
+
+ moveMouseToControlPoint(4);
+ AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
+
+ moveMouseToControlPoint(3);
+ AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
+
+ AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3);
+
+ addMovementStep(new Vector2(550, 50));
+ AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
+
+ AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3);
+
+ // note: if the head is part of the selection being moved, the entire slider is moved.
+ // the unselected nodes will therefore change position relative to the slider head.
+
+ AddAssert("slider moved", () => Precision.AlmostEquals(slider.Position, new Vector2(256, 192) + new Vector2(150, 50)));
+
+ assertControlPointPosition(0, Vector2.Zero);
+ assertControlPointType(0, PathType.PerfectCurve);
+
+ assertControlPointPosition(1, new Vector2(0, 100));
+
+ assertControlPointPosition(2, new Vector2(150, -50));
+
+ assertControlPointPosition(3, new Vector2(400, 0));
+
+ assertControlPointPosition(4, new Vector2(400, 150));
+
+ AddStep("release control", () => InputManager.ReleaseKey(Key.LControl));
+ }
+
[Test]
public void TestDragControlPointAlmostLinearlyExterior()
{
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs
new file mode 100644
index 0000000000..b43b2b1461
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs
@@ -0,0 +1,225 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Input.Events;
+using osu.Framework.Testing;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Input.Bindings;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu.Edit;
+using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Screens.Edit;
+using osu.Game.Screens.Edit.Compose.Components;
+using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Visual;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Osu.Tests.Editor
+{
+ public class TestSceneSliderSnapping : EditorTestScene
+ {
+ private const double beat_length = 1000;
+
+ protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
+ {
+ var controlPointInfo = new ControlPointInfo();
+ controlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
+ return new TestBeatmap(ruleset, false)
+ {
+ ControlPointInfo = controlPointInfo
+ };
+ }
+
+ private Slider slider;
+
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+
+ AddStep("add unsnapped slider", () => EditorBeatmap.Add(slider = new Slider
+ {
+ StartTime = 0,
+ Position = OsuPlayfield.BASE_SIZE / 5,
+ Path = new SliderPath
+ {
+ ControlPoints =
+ {
+ new PathControlPoint(Vector2.Zero),
+ new PathControlPoint(OsuPlayfield.BASE_SIZE * 2 / 5),
+ new PathControlPoint(OsuPlayfield.BASE_SIZE * 3 / 5)
+ }
+ }
+ }));
+ AddStep("set beat divisor to 1/1", () =>
+ {
+ var beatDivisor = (BindableBeatDivisor)Editor.Dependencies.Get(typeof(BindableBeatDivisor));
+ beatDivisor.Value = 1;
+ });
+ }
+
+ [Test]
+ public void TestMovingUnsnappedSliderNodesSnaps()
+ {
+ PathControlPointPiece sliderEnd = null;
+
+ assertSliderSnapped(false);
+
+ AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
+ AddStep("select slider end", () =>
+ {
+ sliderEnd = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints.Last());
+ InputManager.MoveMouseTo(sliderEnd.ScreenSpaceDrawQuad.Centre);
+ });
+ AddStep("move slider end", () =>
+ {
+ InputManager.PressButton(MouseButton.Left);
+ InputManager.MoveMouseTo(sliderEnd.ScreenSpaceDrawQuad.Centre - new Vector2(0, 20));
+ InputManager.ReleaseButton(MouseButton.Left);
+ });
+ assertSliderSnapped(true);
+ }
+
+ [Test]
+ public void TestAddingControlPointToUnsnappedSliderNodesSnaps()
+ {
+ assertSliderSnapped(false);
+
+ AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
+ AddStep("move mouse to new point location", () =>
+ {
+ var firstPiece = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]);
+ var secondPiece = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]);
+ InputManager.MoveMouseTo((firstPiece.ScreenSpaceDrawQuad.Centre + secondPiece.ScreenSpaceDrawQuad.Centre) / 2);
+ });
+ AddStep("move slider end", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.Click(MouseButton.Left);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+ assertSliderSnapped(true);
+ }
+
+ [Test]
+ public void TestRemovingControlPointFromUnsnappedSliderNodesSnaps()
+ {
+ assertSliderSnapped(false);
+
+ AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
+ AddStep("move mouse to second control point", () =>
+ {
+ var secondPiece = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]);
+ InputManager.MoveMouseTo(secondPiece);
+ });
+ AddStep("quick delete", () =>
+ {
+ InputManager.PressKey(Key.ShiftLeft);
+ InputManager.PressButton(MouseButton.Right);
+ InputManager.ReleaseKey(Key.ShiftLeft);
+ });
+ assertSliderSnapped(true);
+ }
+
+ [Test]
+ public void TestResizingUnsnappedSliderSnaps()
+ {
+ SelectionBoxScaleHandle handle = null;
+
+ assertSliderSnapped(false);
+
+ AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
+ AddStep("move mouse to scale handle", () =>
+ {
+ handle = this.ChildrenOfType().First();
+ InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre);
+ });
+ AddStep("scale slider", () =>
+ {
+ InputManager.PressButton(MouseButton.Left);
+ InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre + new Vector2(20, 20));
+ InputManager.ReleaseButton(MouseButton.Left);
+ });
+ assertSliderSnapped(true);
+ }
+
+ [Test]
+ public void TestRotatingUnsnappedSliderDoesNotSnap()
+ {
+ SelectionBoxRotationHandle handle = null;
+
+ assertSliderSnapped(false);
+
+ AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
+ AddStep("move mouse to rotate handle", () =>
+ {
+ handle = this.ChildrenOfType().First();
+ InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre);
+ });
+ AddStep("scale slider", () =>
+ {
+ InputManager.PressButton(MouseButton.Left);
+ InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre + new Vector2(0, 20));
+ InputManager.ReleaseButton(MouseButton.Left);
+ });
+ assertSliderSnapped(false);
+ }
+
+ [Test]
+ public void TestFlippingSliderDoesNotSnap()
+ {
+ OsuSelectionHandler selectionHandler = null;
+
+ assertSliderSnapped(false);
+
+ AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
+ AddStep("flip slider horizontally", () =>
+ {
+ selectionHandler = this.ChildrenOfType().Single();
+ selectionHandler.OnPressed(new KeyBindingPressEvent(InputManager.CurrentState, GlobalAction.EditorFlipHorizontally));
+ });
+
+ assertSliderSnapped(false);
+
+ AddStep("flip slider vertically", () =>
+ {
+ selectionHandler = this.ChildrenOfType().Single();
+ selectionHandler.OnPressed(new KeyBindingPressEvent(InputManager.CurrentState, GlobalAction.EditorFlipVertically));
+ });
+
+ assertSliderSnapped(false);
+ }
+
+ [Test]
+ public void TestReversingSliderDoesNotSnap()
+ {
+ assertSliderSnapped(false);
+
+ AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
+ AddStep("reverse slider", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.Key(Key.G);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+
+ assertSliderSnapped(false);
+ }
+
+ private void assertSliderSnapped(bool snapped)
+ => AddAssert($"slider is {(snapped ? "" : "not ")}snapped", () =>
+ {
+ double durationInBeatLengths = slider.Duration / beat_length;
+ double fractionalPart = durationInBeatLengths - (int)durationInBeatLengths;
+ return Precision.AlmostEquals(fractionalPart, 0) == snapped;
+ });
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index 5f44e1b6b6..4c11efcc7c 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
@@ -12,7 +12,6 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
- [Timeout(10000)]
public class OsuBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-0@2x.png
similarity index 100%
rename from osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png
rename to osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-0@2x.png
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-1@2x.png
new file mode 100755
index 0000000000..3b5e886933
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-1@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
index e698766aac..d673b7a6ac 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
@@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("create slider", () =>
{
- var tintingSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
+ var tintingSkin = skinManager.GetSkin(DefaultLegacySkin.CreateInfo());
tintingSkin.Configuration.ConfigDictionary["AllowSliderBallTint"] = "1";
Child = new SkinProvidingContainer(tintingSkin)
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
index 3252e6d912..a3aa84d0e7 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
@@ -9,6 +9,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Timing;
@@ -46,9 +47,9 @@ namespace osu.Game.Rulesets.Osu.Tests
=> new ClockBackedTestWorkingBeatmap(this.beatmap = beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
[BackgroundDependencyLoader]
- private void load(RulesetConfigCache configCache)
+ private void load()
{
- var config = (OsuRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
+ var config = (OsuRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
config.BindWith(OsuRulesetSetting.SnakingInSliders, snakingIn);
config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut);
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index 4b2e54da17..126a9b0183 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -12,14 +12,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
public class OsuDifficultyAttributes : DifficultyAttributes
{
- [JsonProperty("aim_strain")]
- public double AimStrain { get; set; }
+ [JsonProperty("aim_difficulty")]
+ public double AimDifficulty { get; set; }
- [JsonProperty("speed_strain")]
- public double SpeedStrain { get; set; }
+ [JsonProperty("speed_difficulty")]
+ public double SpeedDifficulty { get; set; }
- [JsonProperty("flashlight_rating")]
- public double FlashlightRating { get; set; }
+ [JsonProperty("flashlight_difficulty")]
+ public double FlashlightDifficulty { get; set; }
[JsonProperty("slider_factor")]
public double SliderFactor { get; set; }
@@ -43,15 +43,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty
foreach (var v in base.ToDatabaseAttributes())
yield return v;
- yield return (ATTRIB_ID_AIM, AimStrain);
- yield return (ATTRIB_ID_SPEED, SpeedStrain);
+ yield return (ATTRIB_ID_AIM, AimDifficulty);
+ yield return (ATTRIB_ID_SPEED, SpeedDifficulty);
yield return (ATTRIB_ID_OVERALL_DIFFICULTY, OverallDifficulty);
yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate);
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
- yield return (ATTRIB_ID_STRAIN, StarRating);
+ yield return (ATTRIB_ID_DIFFICULTY, StarRating);
if (ShouldSerializeFlashlightRating())
- yield return (ATTRIB_ID_FLASHLIGHT, FlashlightRating);
+ yield return (ATTRIB_ID_FLASHLIGHT, FlashlightDifficulty);
yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor);
}
@@ -60,18 +60,25 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
base.FromDatabaseAttributes(values);
- AimStrain = values[ATTRIB_ID_AIM];
- SpeedStrain = values[ATTRIB_ID_SPEED];
+ AimDifficulty = values[ATTRIB_ID_AIM];
+ SpeedDifficulty = values[ATTRIB_ID_SPEED];
OverallDifficulty = values[ATTRIB_ID_OVERALL_DIFFICULTY];
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
- StarRating = values[ATTRIB_ID_STRAIN];
- FlashlightRating = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT);
+ StarRating = values[ATTRIB_ID_DIFFICULTY];
+ FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT);
SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR];
}
- // Used implicitly by Newtonsoft.Json to not serialize flashlight property in some cases.
+ #region Newtonsoft.Json implicit ShouldSerialize() methods
+
+ // The properties in this region are used implicitly by Newtonsoft.Json to not serialise certain fields in some cases.
+ // They rely on being named exactly the same as the corresponding fields (casing included) and as such should NOT be renamed
+ // unless the fields are also renamed.
+
[UsedImplicitly]
public bool ShouldSerializeFlashlightRating() => Mods.Any(m => m is ModFlashlight);
+
+ #endregion
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index ed42f333c0..c5b1baaad1 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -74,9 +74,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
StarRating = starRating,
Mods = mods,
- AimStrain = aimRating,
- SpeedStrain = speedRating,
- FlashlightRating = flashlightRating,
+ AimDifficulty = aimRating,
+ SpeedDifficulty = speedRating,
+ FlashlightDifficulty = flashlightRating,
SliderFactor = sliderFactor,
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
OverallDifficulty = (80 - hitWindowGreat) / 6,
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs
new file mode 100644
index 0000000000..6c7760d144
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.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 Newtonsoft.Json;
+using osu.Game.Rulesets.Difficulty;
+
+namespace osu.Game.Rulesets.Osu.Difficulty
+{
+ public class OsuPerformanceAttributes : PerformanceAttributes
+ {
+ [JsonProperty("aim")]
+ public double Aim { get; set; }
+
+ [JsonProperty("speed")]
+ public double Speed { get; set; }
+
+ [JsonProperty("accuracy")]
+ public double Accuracy { get; set; }
+
+ [JsonProperty("flashlight")]
+ public double Flashlight { get; set; }
+
+ [JsonProperty("effective_miss_count")]
+ public double EffectiveMissCount { get; set; }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 8d45c7a8cc..fdf646ef85 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
}
- public override double Calculate(Dictionary categoryRatings = null)
+ public override PerformanceAttributes Calculate()
{
mods = Score.Mods;
accuracy = Score.Accuracy;
@@ -45,11 +45,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
- // Custom multipliers for NoFail and SpunOut.
if (mods.Any(m => m is OsuModNoFail))
multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount);
- if (mods.Any(m => m is OsuModSpunOut))
+ if (mods.Any(m => m is OsuModSpunOut) && totalHits > 0)
multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85);
if (mods.Any(h => h is OsuModRelax))
@@ -72,42 +71,35 @@ namespace osu.Game.Rulesets.Osu.Difficulty
Math.Pow(flashlightValue, 1.1), 1.0 / 1.1
) * multiplier;
- if (categoryRatings != null)
+ return new OsuPerformanceAttributes
{
- categoryRatings.Add("Aim", aimValue);
- categoryRatings.Add("Speed", speedValue);
- categoryRatings.Add("Accuracy", accuracyValue);
- categoryRatings.Add("Flashlight", flashlightValue);
- categoryRatings.Add("OD", Attributes.OverallDifficulty);
- categoryRatings.Add("AR", Attributes.ApproachRate);
- categoryRatings.Add("Max Combo", Attributes.MaxCombo);
- }
-
- return totalValue;
+ Aim = aimValue,
+ Speed = speedValue,
+ Accuracy = accuracyValue,
+ Flashlight = flashlightValue,
+ EffectiveMissCount = effectiveMissCount,
+ Total = totalValue
+ };
}
private double computeAimValue()
{
- double rawAim = Attributes.AimStrain;
+ double rawAim = Attributes.AimDifficulty;
if (mods.Any(m => m is OsuModTouchDevice))
rawAim = Math.Pow(rawAim, 0.8);
double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0;
- // Longer maps are worth more.
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
-
aimValue *= lengthBonus;
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount);
- // Combo scaling.
- if (Attributes.MaxCombo > 0)
- aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
+ aimValue *= getComboScalingFactor();
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -136,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
}
aimValue *= accuracy;
- // It is important to also consider accuracy difficulty when doing that.
+ // It is important to consider accuracy difficulty when scaling with accuracy.
aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
return aimValue;
@@ -144,9 +136,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double computeSpeedValue()
{
- double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0;
+ double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
- // Longer maps are worth more.
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
speedValue *= lengthBonus;
@@ -155,9 +146,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (effectiveMissCount > 0)
speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
- // Combo scaling.
- if (Attributes.MaxCombo > 0)
- speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
+ speedValue *= getComboScalingFactor();
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -227,14 +216,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (!mods.Any(h => h is OsuModFlashlight))
return 0.0;
- double rawFlashlight = Attributes.FlashlightRating;
+ double rawFlashlight = Attributes.FlashlightDifficulty;
if (mods.Any(m => m is OsuModTouchDevice))
rawFlashlight = Math.Pow(rawFlashlight, 0.8);
double flashlightValue = Math.Pow(rawFlashlight, 2.0) * 25.0;
- // Add an additional bonus for HDFL.
if (mods.Any(h => h is OsuModHidden))
flashlightValue *= 1.3;
@@ -242,9 +230,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (effectiveMissCount > 0)
flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
- // Combo scaling.
- if (Attributes.MaxCombo > 0)
- flashlightValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
+ flashlightValue *= getComboScalingFactor();
// Account for shorter maps having a higher ratio of 0 combo/100 combo flashlight radius.
flashlightValue *= 0.7 + 0.1 * Math.Min(1.0, totalHits / 200.0) +
@@ -276,6 +262,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount));
}
+ private double getComboScalingFactor() => 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 d073d751d0..4df8ff0b12 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -11,49 +11,65 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
{
public class OsuDifficultyHitObject : DifficultyHitObject
{
- private const int normalized_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
+ private 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 = normalized_radius * 2.4f;
- private const float assumed_slider_radius = normalized_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;
///
- /// Normalized distance from the end position of the previous to the start position of this .
+ /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms.
///
- public double JumpDistance { get; private set; }
+ public readonly double StrainTime;
///
- /// Minimum distance from the end position of the previous to the start position of this .
+ /// Normalised distance from the "lazy" end position of the previous to the start position of this .
+ ///
+ /// The "lazy" end position is the position at which the cursor ends up if the previous hitobject is followed with as minimal movement as possible (i.e. on the edge of slider follow circles).
+ ///
///
- public double MovementDistance { get; private set; }
+ public double LazyJumpDistance { get; private set; }
///
- /// Normalized distance between the start and end position of the previous .
+ /// Normalised shortest distance to consider for a jump between the previous and this .
+ ///
+ ///
+ /// This is bounded from above by , and is smaller than the former if a more natural path is able to be taken through the previous .
+ ///
+ ///
+ /// Suppose a linear slider - circle pattern.
+ ///
+ /// Following the slider lazily (see: ) will result in underestimating the true end position of the slider as being closer towards the start position.
+ /// As a result, overestimates the jump distance because the player is able to take a more natural path by following through the slider to its end,
+ /// such that the jump is felt as only starting from the slider's true end position.
+ ///
+ /// Now consider a slider - circle pattern where the circle is stacked along the path inside the slider.
+ /// In this case, the lazy end position correctly estimates the true end position of the slider and provides the more natural movement path.
+ ///
+ public double MinimumJumpDistance { get; private set; }
+
+ ///
+ /// The time taken to travel through , with a minimum value of 25ms.
+ ///
+ public double MinimumJumpTime { get; private set; }
+
+ ///
+ /// Normalised distance between the start and end position of this .
///
public double TravelDistance { get; private set; }
+ ///
+ /// The time taken to travel through , with a minimum value of 25ms for a non-zero distance.
+ ///
+ public double TravelTime { get; private set; }
+
///
/// Angle the player has to take to hit this .
/// Calculated as the angle between the circles (current-2, current-1, current).
///
public double? Angle { get; private set; }
- ///
- /// Milliseconds elapsed since the end time of the previous , with a minimum of 25ms.
- ///
- public double MovementTime { get; private set; }
-
- ///
- /// Milliseconds elapsed since the start time of the previous to the end time of the same previous , with a minimum of 25ms.
- ///
- public double TravelTime { get; private set; }
-
- ///
- /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms.
- ///
- public readonly double StrainTime;
-
private readonly OsuHitObject lastLastObject;
private readonly OsuHitObject lastObject;
@@ -71,12 +87,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
private void setDistances(double clockRate)
{
+ if (BaseObject is Slider currentSlider)
+ {
+ computeSliderCursorPosition(currentSlider);
+ TravelDistance = currentSlider.LazyTravelDistance;
+ TravelTime = Math.Max(currentSlider.LazyTravelTime / clockRate, min_delta_time);
+ }
+
// We don't need to calculate either angle or distance when one of the last->curr objects is a spinner
if (BaseObject is Spinner || lastObject is Spinner)
return;
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
- float scalingFactor = normalized_radius / (float)BaseObject.Radius;
+ float scalingFactor = normalised_radius / (float)BaseObject.Radius;
if (BaseObject.Radius < 30)
{
@@ -85,29 +108,40 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
}
Vector2 lastCursorPosition = getEndCursorPosition(lastObject);
- JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
+
+ LazyJumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
+ MinimumJumpTime = StrainTime;
+ MinimumJumpDistance = LazyJumpDistance;
if (lastObject is Slider lastSlider)
{
- computeSliderCursorPosition(lastSlider);
- TravelDistance = lastSlider.LazyTravelDistance;
- TravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time);
- MovementTime = Math.Max(StrainTime - TravelTime, min_delta_time);
+ double lastTravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time);
+ MinimumJumpTime = Math.Max(StrainTime - lastTravelTime, min_delta_time);
+
+ //
+ // There are two types of slider-to-object patterns to consider in order to better approximate the real movement a player will take to jump between the hitobjects.
+ //
+ // 1. The anti-flow pattern, where players cut the slider short in order to move to the next hitobject.
+ //
+ // <======o==> ← slider
+ // | ← most natural jump path
+ // o ← a follow-up hitcircle
+ //
+ // In this case the most natural jump path is approximated by LazyJumpDistance.
+ //
+ // 2. The flow pattern, where players follow through the slider to its visual extent into the next hitobject.
+ //
+ // <======o==>---o
+ // ↑
+ // most natural jump path
+ //
+ // In this case the most natural jump path is better approximated by a new distance called "tailJumpDistance" - the distance between the slider's tail and the next hitobject.
+ //
+ // Thus, the player is assumed to jump the minimum of these two distances in all cases.
+ //
- // Jump distance from the slider tail to the next object, as opposed to the lazy position of JumpDistance.
float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor;
-
- // For hitobjects which continue in the direction of the slider, the player will normally follow through the slider,
- // such that they're not jumping from the lazy position but rather from very close to (or the end of) the slider.
- // In such cases, a leniency is applied by also considering the jump distance from the tail of the slider, and taking the minimum jump distance.
- // Additional distance is removed based on position of jump relative to slider follow circle radius.
- // JumpDistance is the leniency distance beyond the assumed_slider_radius. tailJumpDistance is maximum_slider_radius since the full distance of radial leniency is still possible.
- MovementDistance = Math.Max(0, Math.Min(JumpDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius));
- }
- else
- {
- MovementTime = StrainTime;
- MovementDistance = JumpDistance;
+ MinimumJumpDistance = Math.Max(0, Math.Min(LazyJumpDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius));
}
if (lastLastObject != null && !(lastLastObject is Spinner))
@@ -139,7 +173,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 = normalized_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++)
{
@@ -167,7 +201,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 = normalized_radius;
+ requiredMovement = normalised_radius;
}
if (currMovementLength > requiredMovement)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
index 2a8d2ce759..a6301aed6d 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
@@ -44,24 +44,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
var osuLastLastObj = (OsuDifficultyHitObject)Previous[1];
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
- double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime;
+ double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime;
// But if the last object is a slider, then we extend the travel velocity through the slider into the current object.
if (osuLastObj.BaseObject is Slider && withSliders)
{
- double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object
- double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to slider end.
+ double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; // calculate the slider velocity from slider head to slider end.
+ double movementVelocity = osuCurrObj.MinimumJumpDistance / osuCurrObj.MinimumJumpTime; // calculate the movement velocity from slider end to current object
currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity.
}
// As above, do the same for the previous hitobject.
- double prevVelocity = osuLastObj.JumpDistance / osuLastObj.StrainTime;
+ double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime;
if (osuLastLastObj.BaseObject is Slider && withSliders)
{
- double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime;
- double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime;
+ double travelVelocity = osuLastLastObj.TravelDistance / osuLastLastObj.TravelTime;
+ double movementVelocity = osuLastObj.MinimumJumpDistance / osuLastObj.MinimumJumpTime;
prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity);
}
@@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
acuteAngleBonus *= calcAcuteAngleBonus(lastAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern.
* Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4
- * Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.JumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter).
+ * Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.LazyJumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter).
}
// Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute.
@@ -107,8 +107,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
if (Math.Max(prevVelocity, currVelocity) != 0)
{
// We want to use the average velocity over the whole object when awarding differences, not the individual jump and slider path velocities.
- prevVelocity = (osuLastObj.JumpDistance + osuLastObj.TravelDistance) / osuLastObj.StrainTime;
- currVelocity = (osuCurrObj.JumpDistance + osuCurrObj.TravelDistance) / osuCurrObj.StrainTime;
+ prevVelocity = (osuLastObj.LazyJumpDistance + osuLastLastObj.TravelDistance) / osuLastObj.StrainTime;
+ currVelocity = (osuCurrObj.LazyJumpDistance + osuLastObj.TravelDistance) / osuCurrObj.StrainTime;
// Scale with ratio of difference compared to 0.5 * max dist.
double distRatio = Math.Pow(Math.Sin(Math.PI / 2 * Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity)), 2);
@@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
// 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.JumpDistance, osuLastObj.JumpDistance) / 100)), 2);
+ * 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;
@@ -128,10 +128,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2);
}
- if (osuCurrObj.TravelTime != 0)
+ if (osuLastObj.TravelTime != 0)
{
// Reward sliders based on velocity.
- sliderBonus = osuCurrObj.TravelDistance / osuCurrObj.TravelTime;
+ sliderBonus = osuLastObj.TravelDistance / osuLastObj.TravelTime;
}
// Add in acute angle bonus or wide angle bonus + velocity change bonus, whichever is larger.
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs
index 466f0556ab..03abba29ce 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
}
- private double skillMultiplier => 0.15;
+ private double skillMultiplier => 0.07;
private double strainDecayBase => 0.15;
protected override double DecayWeight => 1.0;
protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations.
@@ -40,26 +40,31 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
double result = 0.0;
+ OsuDifficultyHitObject lastObj = osuCurrent;
+
+ // This is iterating backwards in time from the current object.
for (int i = 0; i < Previous.Count; i++)
{
- var osuPrevious = (OsuDifficultyHitObject)Previous[i];
- var osuPreviousHitObject = (OsuHitObject)(osuPrevious.BaseObject);
+ var currentObj = (OsuDifficultyHitObject)Previous[i];
+ var currentHitObject = (OsuHitObject)(currentObj.BaseObject);
- if (!(osuPrevious.BaseObject is Spinner))
+ if (!(currentObj.BaseObject is Spinner))
{
- double jumpDistance = (osuHitObject.StackedPosition - osuPreviousHitObject.EndPosition).Length;
+ double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.EndPosition).Length;
- cumulativeStrainTime += osuPrevious.StrainTime;
+ cumulativeStrainTime += lastObj.StrainTime;
// We want to nerf objects that can be easily seen within the Flashlight circle radius.
if (i == 0)
smallDistNerf = Math.Min(1.0, jumpDistance / 75.0);
// We also want to nerf stacks so that only the first object of the stack is accounted for.
- double stackNerf = Math.Min(1.0, (osuPrevious.JumpDistance / scalingFactor) / 25.0);
+ double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0);
- result += Math.Pow(0.8, i) * stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime;
+ result += stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime;
}
+
+ lastObj = currentObj;
}
return Math.Pow(smallDistNerf * result, 2.0);
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
index 24881d9c47..06d1ef7346 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
@@ -55,73 +55,75 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
bool firstDeltaSwitch = false;
- for (int i = Previous.Count - 2; i > 0; i--)
+ int rhythmStart = 0;
+
+ while (rhythmStart < Previous.Count - 2 && current.StartTime - Previous[rhythmStart].StartTime < history_time_max)
+ rhythmStart++;
+
+ for (int i = rhythmStart; i > 0; i--)
{
OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)Previous[i - 1];
OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)Previous[i];
OsuDifficultyHitObject lastObj = (OsuDifficultyHitObject)Previous[i + 1];
- double currHistoricalDecay = Math.Max(0, (history_time_max - (current.StartTime - currObj.StartTime))) / history_time_max; // scales note 0 to 1 from history to now
+ double currHistoricalDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; // scales note 0 to 1 from history to now
- if (currHistoricalDecay != 0)
+ currHistoricalDecay = Math.Min((double)(Previous.Count - i) / Previous.Count, currHistoricalDecay); // either we're limited by time or limited by object count.
+
+ double currDelta = currObj.StrainTime;
+ double prevDelta = prevObj.StrainTime;
+ double lastDelta = lastObj.StrainTime;
+ double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
+
+ double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - greatWindow * 0.6) / (greatWindow * 0.6));
+
+ windowPenalty = Math.Min(1, windowPenalty);
+
+ double effectiveRatio = windowPenalty * currRatio;
+
+ if (firstDeltaSwitch)
{
- currHistoricalDecay = Math.Min((double)(Previous.Count - i) / Previous.Count, currHistoricalDecay); // either we're limited by time or limited by object count.
-
- double currDelta = currObj.StrainTime;
- double prevDelta = prevObj.StrainTime;
- double lastDelta = lastObj.StrainTime;
- double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
-
- double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - greatWindow * 0.6) / (greatWindow * 0.6));
-
- windowPenalty = Math.Min(1, windowPenalty);
-
- double effectiveRatio = windowPenalty * currRatio;
-
- if (firstDeltaSwitch)
+ if (!(prevDelta > 1.25 * currDelta || prevDelta * 1.25 < currDelta))
{
- if (!(prevDelta > 1.25 * currDelta || prevDelta * 1.25 < currDelta))
- {
- if (islandSize < 7)
- islandSize++; // island is still progressing, count size.
- }
- else
- {
- if (Previous[i - 1].BaseObject is Slider) // bpm change is into slider, this is easy acc window
- effectiveRatio *= 0.125;
-
- if (Previous[i].BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle
- effectiveRatio *= 0.25;
-
- if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet)
- effectiveRatio *= 0.25;
-
- if (previousIslandSize % 2 == islandSize % 2) // repeated island polartiy (2 -> 4, 3 -> 5)
- effectiveRatio *= 0.50;
-
- if (lastDelta > prevDelta + 10 && prevDelta > currDelta + 10) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this.
- effectiveRatio *= 0.125;
-
- rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay * Math.Sqrt(4 + islandSize) / 2 * Math.Sqrt(4 + previousIslandSize) / 2;
-
- startRatio = effectiveRatio;
-
- previousIslandSize = islandSize; // log the last island size.
-
- if (prevDelta * 1.25 < currDelta) // we're slowing down, stop counting
- firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size.
-
- islandSize = 1;
- }
+ if (islandSize < 7)
+ islandSize++; // island is still progressing, count size.
}
- else if (prevDelta > 1.25 * currDelta) // we want to be speeding up.
+ else
{
- // Begin counting island until we change speed again.
- firstDeltaSwitch = true;
+ if (Previous[i - 1].BaseObject is Slider) // bpm change is into slider, this is easy acc window
+ effectiveRatio *= 0.125;
+
+ if (Previous[i].BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle
+ effectiveRatio *= 0.25;
+
+ if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet)
+ effectiveRatio *= 0.25;
+
+ if (previousIslandSize % 2 == islandSize % 2) // repeated island polartiy (2 -> 4, 3 -> 5)
+ effectiveRatio *= 0.50;
+
+ if (lastDelta > prevDelta + 10 && prevDelta > currDelta + 10) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this.
+ effectiveRatio *= 0.125;
+
+ rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay * Math.Sqrt(4 + islandSize) / 2 * Math.Sqrt(4 + previousIslandSize) / 2;
+
startRatio = effectiveRatio;
+
+ previousIslandSize = islandSize; // log the last island size.
+
+ if (prevDelta * 1.25 < currDelta) // we're slowing down, stop counting
+ firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size.
+
islandSize = 1;
}
}
+ else if (prevDelta > 1.25 * currDelta) // we want to be speeding up.
+ {
+ // Begin counting island until we change speed again.
+ firstDeltaSwitch = true;
+ startRatio = effectiveRatio;
+ islandSize = 1;
+ }
}
return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2; //produces multiplier that can be applied to strain. range [1, infinity) (not really though)
@@ -154,7 +156,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
if (strainTime < min_speed_bonus)
speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2);
- double distance = Math.Min(single_spacing_threshold, osuCurrObj.TravelDistance + osuCurrObj.JumpDistance);
+ double travelDistance = osuPrevObj?.TravelDistance ?? 0;
+ double distance = Math.Min(single_spacing_threshold, travelDistance + osuCurrObj.MinimumJumpDistance);
return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime;
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
index b3e0566a98..09759aa118 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
@@ -16,11 +16,9 @@ using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Graphics;
-using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Screens.Edit;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
@@ -33,6 +31,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
public class PathControlPointPiece : BlueprintPiece, IHasTooltip
{
public Action RequestSelection;
+
+ public Action DragStarted;
+ public Action DragInProgress;
+ public Action DragEnded;
+
public List PointsInSegment;
public readonly BindableBool IsSelected = new BindableBool();
@@ -42,12 +45,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private readonly Container marker;
private readonly Drawable markerRing;
- [Resolved(CanBeNull = true)]
- private IEditorChangeHandler changeHandler { get; set; }
-
- [Resolved(CanBeNull = true)]
- private IPositionSnapProvider snapProvider { get; set; }
-
[Resolved]
private OsuColour colours { get; set; }
@@ -138,6 +135,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
updateMarkerDisplay();
}
+ // Used to pair up mouse down/drag events with their corresponding mouse up events,
+ // to avoid deselecting the piece by accident when the mouse up corresponding to the mouse down/drag fires.
+ private bool keepSelection;
+
protected override bool OnMouseDown(MouseDownEvent e)
{
if (RequestSelection == null)
@@ -146,22 +147,41 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
switch (e.Button)
{
case MouseButton.Left:
+ // if control is pressed, do not do anything as the user may be adding to current selection
+ // or dragging all currently selected control points.
+ // if it isn't and the user's intent is to deselect, deselection will happen on mouse up.
+ if (e.ControlPressed && IsSelected.Value)
+ return true;
+
RequestSelection.Invoke(this, e);
+ keepSelection = true;
+
return true;
case MouseButton.Right:
if (!IsSelected.Value)
RequestSelection.Invoke(this, e);
+
+ keepSelection = true;
return false; // Allow context menu to show
}
return false;
}
- protected override bool OnClick(ClickEvent e) => RequestSelection != null;
+ protected override void OnMouseUp(MouseUpEvent e)
+ {
+ base.OnMouseUp(e);
- private Vector2 dragStartPosition;
- private PathType? dragPathType;
+ // ctrl+click deselects this piece, but only if this event
+ // wasn't immediately preceded by a matching mouse down or drag.
+ if (IsSelected.Value && e.ControlPressed && !keepSelection)
+ IsSelected.Value = false;
+
+ keepSelection = false;
+ }
+
+ protected override bool OnClick(ClickEvent e) => RequestSelection != null;
protected override bool OnDragStart(DragStartEvent e)
{
@@ -170,54 +190,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
if (e.Button == MouseButton.Left)
{
- dragStartPosition = ControlPoint.Position;
- dragPathType = PointsInSegment[0].Type;
-
- changeHandler?.BeginChange();
+ DragStarted?.Invoke(ControlPoint);
+ keepSelection = true;
return true;
}
return false;
}
- protected override void OnDrag(DragEvent e)
- {
- Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position).ToArray();
- var oldPosition = slider.Position;
- double oldStartTime = slider.StartTime;
+ protected override void OnDrag(DragEvent e) => DragInProgress?.Invoke(e);
- if (ControlPoint == slider.Path.ControlPoints[0])
- {
- // Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
- var result = snapProvider?.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition);
-
- Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? e.ScreenSpaceMousePosition) - slider.Position;
-
- slider.Position += movementDelta;
- slider.StartTime = result?.Time ?? slider.StartTime;
-
- // Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
- for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
- slider.Path.ControlPoints[i].Position -= movementDelta;
- }
- else
- ControlPoint.Position = dragStartPosition + (e.MousePosition - e.MouseDownPosition);
-
- if (!slider.Path.HasValidLength)
- {
- for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
- slider.Path.ControlPoints[i].Position = oldControlPoints[i];
-
- slider.Position = oldPosition;
- slider.StartTime = oldStartTime;
- return;
- }
-
- // Maintain the path type in case it got defaulted to bezier at some point during the drag.
- PointsInSegment[0].Type = dragPathType;
- }
-
- protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange();
+ protected override void OnDragEnd(DragEndEvent e) => DragEnded?.Invoke();
private void cachePoints(Slider slider) => PointsInSegment = slider.Path.PointsInSegment(ControlPoint);
@@ -267,7 +250,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
switch (pathType)
{
case PathType.Catmull:
- return colours.Seafoam;
+ return colours.SeaFoam;
case PathType.Bezier:
return colours.Pink;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
index 1be9b5bf2e..ae4141073e 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
+using System.Diagnostics;
using System.Linq;
using Humanizer;
using osu.Framework.Allocation;
@@ -16,6 +17,7 @@ using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
@@ -40,6 +42,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
public Action> RemoveControlPointsRequested;
+ [Resolved(CanBeNull = true)]
+ private IPositionSnapProvider snapProvider { get; set; }
+
public PathControlPointVisualiser(Slider slider, bool allowSelection)
{
this.slider = slider;
@@ -64,6 +69,39 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
controlPoints.BindTo(slider.Path.ControlPoints);
}
+ ///
+ /// Selects the corresponding to the given ,
+ /// and deselects all other s.
+ ///
+ public void SetSelectionTo(PathControlPoint pathControlPoint)
+ {
+ foreach (var p in Pieces)
+ p.IsSelected.Value = p.ControlPoint == pathControlPoint;
+ }
+
+ ///
+ /// Delete all visually selected s.
+ ///
+ ///
+ public bool DeleteSelected()
+ {
+ List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.ControlPoint).ToList();
+
+ // Ensure that there are any points to be deleted
+ if (toRemove.Count == 0)
+ return false;
+
+ changeHandler?.BeginChange();
+ RemoveControlPointsRequested?.Invoke(toRemove);
+ changeHandler?.EndChange();
+
+ // Since pieces are re-used, they will not point to the deleted control points while remaining selected
+ foreach (var piece in Pieces)
+ piece.IsSelected.Value = false;
+
+ return true;
+ }
+
private void onControlPointsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
@@ -87,7 +125,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
Pieces.Add(new PathControlPointPiece(slider, point).With(d =>
{
if (allowSelection)
- d.RequestSelection = selectPiece;
+ d.RequestSelection = selectionRequested;
+
+ d.DragStarted = dragStarted;
+ d.DragInProgress = dragInProgress;
+ d.DragEnded = dragEnded;
}));
Connections.Add(new PathControlPointConnectionPiece(slider, e.NewStartingIndex + i));
@@ -119,6 +161,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override bool OnClick(ClickEvent e)
{
+ if (Pieces.Any(piece => piece.IsHovered))
+ return false;
+
foreach (var piece in Pieces)
{
piece.IsSelected.Value = false;
@@ -142,15 +187,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
}
- private void selectPiece(PathControlPointPiece piece, MouseButtonEvent e)
+ private void selectionRequested(PathControlPointPiece piece, MouseButtonEvent e)
{
if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
piece.IsSelected.Toggle();
else
- {
- foreach (var p in Pieces)
- p.IsSelected.Value = p == piece;
- }
+ SetSelectionTo(piece.ControlPoint);
}
///
@@ -184,25 +226,87 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
[Resolved(CanBeNull = true)]
private IEditorChangeHandler changeHandler { get; set; }
- public bool DeleteSelected()
- {
- List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.ControlPoint).ToList();
+ #region Drag handling
- // Ensure that there are any points to be deleted
- if (toRemove.Count == 0)
- return false;
+ private Vector2[] dragStartPositions;
+ private PathType?[] dragPathTypes;
+ private int draggedControlPointIndex;
+ private HashSet selectedControlPoints;
+
+ private void dragStarted(PathControlPoint controlPoint)
+ {
+ dragStartPositions = slider.Path.ControlPoints.Select(point => point.Position).ToArray();
+ dragPathTypes = slider.Path.ControlPoints.Select(point => point.Type).ToArray();
+ draggedControlPointIndex = slider.Path.ControlPoints.IndexOf(controlPoint);
+ selectedControlPoints = new HashSet(Pieces.Where(piece => piece.IsSelected.Value).Select(piece => piece.ControlPoint));
+
+ Debug.Assert(draggedControlPointIndex >= 0);
changeHandler?.BeginChange();
- RemoveControlPointsRequested?.Invoke(toRemove);
- changeHandler?.EndChange();
-
- // Since pieces are re-used, they will not point to the deleted control points while remaining selected
- foreach (var piece in Pieces)
- piece.IsSelected.Value = false;
-
- return true;
}
+ private void dragInProgress(DragEvent e)
+ {
+ Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position).ToArray();
+ var oldPosition = slider.Position;
+ double oldStartTime = slider.StartTime;
+
+ if (selectedControlPoints.Contains(slider.Path.ControlPoints[0]))
+ {
+ // Special handling for selections containing head control point - the position of the slider changes which means the snapped position and time have to be taken into account
+ Vector2 newHeadPosition = Parent.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex]));
+ var result = snapProvider?.SnapScreenSpacePositionToValidTime(newHeadPosition);
+
+ Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - slider.Position;
+
+ slider.Position += movementDelta;
+ slider.StartTime = result?.Time ?? slider.StartTime;
+
+ for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
+ {
+ var controlPoint = slider.Path.ControlPoints[i];
+ // Since control points are relative to the position of the slider, all points that are _not_ selected
+ // need to be offset _back_ by the delta corresponding to the movement of the head point.
+ // All other selected control points (if any) will move together with the head point
+ // (and so they will not move at all, relative to each other).
+ if (!selectedControlPoints.Contains(controlPoint))
+ controlPoint.Position -= movementDelta;
+ }
+ }
+ else
+ {
+ for (int i = 0; i < controlPoints.Count; ++i)
+ {
+ var controlPoint = controlPoints[i];
+ if (selectedControlPoints.Contains(controlPoint))
+ controlPoint.Position = dragStartPositions[i] + (e.MousePosition - e.MouseDownPosition);
+ }
+ }
+
+ // Snap the path to the current beat divisor before checking length validity.
+ slider.SnapTo(snapProvider);
+
+ if (!slider.Path.HasValidLength)
+ {
+ for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
+ slider.Path.ControlPoints[i].Position = oldControlPoints[i];
+
+ slider.Position = oldPosition;
+ slider.StartTime = oldStartTime;
+ // Snap the path length again to undo the invalid length.
+ slider.SnapTo(snapProvider);
+ return;
+ }
+
+ // Maintain the path types in case they got defaulted to bezier at some point during the drag.
+ for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
+ slider.Path.ControlPoints[i].Type = dragPathTypes[i];
+ }
+
+ private void dragEnded() => changeHandler?.EndChange();
+
+ #endregion
+
public MenuItem[] ContextMenuItems
{
get
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
index 07b6a1bdc2..b868c9a7ee 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
@@ -9,7 +9,6 @@ using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -50,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load()
{
InternalChildren = new Drawable[]
{
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index 17a62fc61c..6cf2a493a9 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
-using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
@@ -81,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
controlPoints.BindTo(HitObject.Path.ControlPoints);
pathVersion.BindTo(HitObject.Path.Version);
- pathVersion.BindValueChanged(_ => updatePath());
+ pathVersion.BindValueChanged(_ => editorBeatmap?.Update(HitObject));
BodyPiece.UpdateFrom(HitObject);
}
@@ -140,7 +139,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
case MouseButton.Left:
if (e.ControlPressed && IsSelected)
{
- placementControlPointIndex = addControlPoint(e.MousePosition);
+ changeHandler?.BeginChange();
+ placementControlPoint = addControlPoint(e.MousePosition);
+ ControlPointVisualiser?.SetSelectionTo(placementControlPoint);
return true; // Stop input from being handled and modifying the selection
}
@@ -150,31 +151,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
return false;
}
- private int? placementControlPointIndex;
+ [CanBeNull]
+ private PathControlPoint placementControlPoint;
- protected override bool OnDragStart(DragStartEvent e)
- {
- if (placementControlPointIndex != null)
- {
- changeHandler?.BeginChange();
- return true;
- }
-
- return false;
- }
+ protected override bool OnDragStart(DragStartEvent e) => placementControlPoint != null;
protected override void OnDrag(DragEvent e)
{
- Debug.Assert(placementControlPointIndex != null);
-
- HitObject.Path.ControlPoints[placementControlPointIndex.Value].Position = e.MousePosition - HitObject.Position;
+ if (placementControlPoint != null)
+ placementControlPoint.Position = e.MousePosition - HitObject.Position;
}
- protected override void OnDragEnd(DragEndEvent e)
+ protected override void OnMouseUp(MouseUpEvent e)
{
- if (placementControlPointIndex != null)
+ if (placementControlPoint != null)
{
- placementControlPointIndex = null;
+ placementControlPoint = null;
changeHandler?.EndChange();
}
}
@@ -193,7 +185,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
return false;
}
- private int addControlPoint(Vector2 position)
+ private PathControlPoint addControlPoint(Vector2 position)
{
position -= HitObject.Position;
@@ -211,10 +203,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
}
}
- // Move the control points from the insertion index onwards to make room for the insertion
- controlPoints.Insert(insertionIndex, new PathControlPoint { Position = position });
+ var pathControlPoint = new PathControlPoint { Position = position };
- return insertionIndex;
+ // Move the control points from the insertion index onwards to make room for the insertion
+ controlPoints.Insert(insertionIndex, pathControlPoint);
+
+ HitObject.SnapTo(composer);
+
+ return pathControlPoint;
}
private void removeControlPoints(List toRemove)
@@ -233,7 +229,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
controlPoints.Remove(c);
}
- // If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted
+ // Snap the slider to the current beat divisor before checking length validity.
+ HitObject.SnapTo(composer);
+
+ // If there are 0 or 1 remaining control points, or the slider has an invalid length, it is in a degenerate form and should be deleted
if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLength)
{
placementHandler?.Delete(HitObject);
@@ -248,12 +247,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
HitObject.Position += first;
}
- private void updatePath()
- {
- HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
- editorBeatmap?.Update(HitObject);
- }
-
private void convertToStream()
{
if (editorBeatmap == null || changeHandler == null || beatDivisor == null)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
index 4a57d36eb4..efbac5439c 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
@@ -1,15 +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 enable
+
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Utils;
using osu.Game.Extensions;
+using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
@@ -17,6 +22,9 @@ namespace osu.Game.Rulesets.Osu.Edit
{
public class OsuSelectionHandler : EditorSelectionHandler
{
+ [Resolved(CanBeNull = true)]
+ private IPositionSnapProvider? positionSnapProvider { get; set; }
+
///
/// During a transform, the initial origin is stored so it can be used throughout the operation.
///
@@ -26,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit
/// During a transform, the initial path types of a single selected slider are stored so they
/// can be maintained throughout the operation.
///
- private List referencePathTypes;
+ private List? referencePathTypes;
protected override void OnSelectionChanged()
{
@@ -84,18 +92,28 @@ namespace osu.Game.Rulesets.Osu.Edit
return true;
}
- public override bool HandleFlip(Direction direction)
+ public override bool HandleFlip(Direction direction, bool flipOverOrigin)
{
var hitObjects = selectedMovableObjects;
- var selectedObjectsQuad = getSurroundingQuad(hitObjects);
+ var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : getSurroundingQuad(hitObjects);
+
+ bool didFlip = false;
foreach (var h in hitObjects)
{
- h.Position = GetFlippedPosition(direction, selectedObjectsQuad, h.Position);
+ var flippedPosition = GetFlippedPosition(direction, flipQuad, h.Position);
+
+ if (!Precision.AlmostEquals(flippedPosition, h.Position))
+ {
+ h.Position = flippedPosition;
+ didFlip = true;
+ }
if (h is Slider slider)
{
+ didFlip = true;
+
foreach (var point in slider.Path.ControlPoints)
{
point.Position = new Vector2(
@@ -106,7 +124,7 @@ namespace osu.Game.Rulesets.Osu.Edit
}
}
- return true;
+ return didFlip;
}
public override bool HandleScale(Vector2 scale, Anchor reference)
@@ -186,6 +204,10 @@ namespace osu.Game.Rulesets.Osu.Edit
for (int i = 0; i < slider.Path.ControlPoints.Count; ++i)
slider.Path.ControlPoints[i].Type = referencePathTypes[i];
+ // Snap the slider's length to the current beat divisor
+ // to calculate the final resulting duration / bounding box before the final checks.
+ slider.SnapTo(positionSnapProvider);
+
//if sliderhead or sliderend end up outside playfield, revert scaling.
Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider });
(bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad);
@@ -195,6 +217,9 @@ namespace osu.Game.Rulesets.Osu.Edit
foreach (var point in slider.Path.ControlPoints)
point.Position = oldControlPoints.Dequeue();
+
+ // Snap the slider's length again to undo the potentially-invalid length applied by the previous snap.
+ slider.SnapTo(positionSnapProvider);
}
private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale)
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
index 31474e8fbb..098c639949 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Spun Out";
public override string Acronym => "SO";
- public override IconUsage? Icon => OsuIcon.ModSpunout;
+ public override IconUsage? Icon => OsuIcon.ModSpunOut;
public override ModType Type => ModType.Automation;
public override string Description => @"Spinners will be automatically completed.";
public override double ScoreMultiplier => 0.9;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index ec87d3bfdf..c6db02ee02 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -10,7 +10,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
-using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
@@ -69,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load()
{
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 0ad8e4ea68..1eddfb7fef 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -65,8 +65,8 @@ namespace osu.Game.Rulesets.Osu.Objects
double startTime = StartTime + (float)(i + 1) / totalSpins * Duration;
AddNested(i < SpinsRequired
- ? new SpinnerTick { StartTime = startTime }
- : new SpinnerBonusTick { StartTime = startTime });
+ ? new SpinnerTick { StartTime = startTime, Position = Position }
+ : new SpinnerBonusTick { StartTime = startTime, Position = Position });
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs
index 7314021a14..5c6b907e42 100644
--- a/osu.Game.Rulesets.Osu/OsuInputManager.cs
+++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs
@@ -3,8 +3,10 @@
using System.Collections.Generic;
using System.ComponentModel;
+using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
+using osu.Framework.Input.StateChanges.Events;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu
@@ -39,6 +41,19 @@ namespace osu.Game.Rulesets.Osu
return base.Handle(e);
}
+ protected override bool HandleMouseTouchStateChange(TouchStateChangeEvent e)
+ {
+ if (!AllowUserCursorMovement)
+ {
+ // Still allow for forwarding of the "touch" part, but replace the positional data with that of the mouse.
+ // Primarily relied upon by the "autopilot" osu! mod.
+ var touch = new Touch(e.Touch.Source, CurrentState.Mouse.Position);
+ e = new TouchStateChangeEvent(e.State, e.Input, touch, e.IsActive, null);
+ }
+
+ return base.HandleMouseTouchStateChange(e);
+ }
+
private class OsuKeyBindingContainer : RulesetKeyBindingContainer
{
public bool AllowUserPresses = true;
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBackgroundLayer.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBackgroundLayer.cs
index f8a6e1d3c9..a1184a15cd 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBackgroundLayer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBackgroundLayer.cs
@@ -3,15 +3,13 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Game.Graphics;
-using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Skinning.Default
{
public class SpinnerBackgroundLayer : SpinnerFill
{
[BackgroundDependencyLoader]
- private void load(OsuColour colours, DrawableHitObject drawableHitObject)
+ private void load()
{
Disc.Alpha = 0;
Anchor = Anchor.Centre;
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.cs
new file mode 100644
index 0000000000..4ee28d05b5
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.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 osu.Framework.Audio.Track;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Graphics.Containers;
+
+#nullable enable
+
+namespace osu.Game.Rulesets.Osu.Skinning.Legacy
+{
+ internal class KiaiFlashingDrawable : BeatSyncedContainer
+ {
+ private readonly Drawable flashingDrawable;
+
+ private const float flash_opacity = 0.3f;
+
+ public KiaiFlashingDrawable(Func creationFunc)
+ {
+ AutoSizeAxes = Axes.Both;
+
+ Children = new[]
+ {
+ (creationFunc.Invoke() ?? Empty()).With(d =>
+ {
+ d.Anchor = Anchor.Centre;
+ d.Origin = Anchor.Centre;
+ }),
+ flashingDrawable = (creationFunc.Invoke() ?? Empty()).With(d =>
+ {
+ d.Anchor = Anchor.Centre;
+ d.Origin = Anchor.Centre;
+ d.Alpha = 0;
+ d.Blending = BlendingParameters.Additive;
+ })
+ };
+ }
+
+ protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
+ {
+ if (!effectPoint.KiaiMode)
+ return;
+
+ flashingDrawable
+ .FadeTo(flash_opacity)
+ .Then()
+ .FadeOut(timingPoint.BeatLength * 0.75f);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs
deleted file mode 100644
index 4a1d69ad41..0000000000
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Audio.Track;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.Textures;
-using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Graphics.Containers;
-
-namespace osu.Game.Rulesets.Osu.Skinning.Legacy
-{
- internal class KiaiFlashingSprite : BeatSyncedContainer
- {
- private readonly Sprite mainSprite;
- private readonly Sprite flashingSprite;
-
- public Texture Texture
- {
- set
- {
- mainSprite.Texture = value;
- flashingSprite.Texture = value;
- }
- }
-
- private const float flash_opacity = 0.3f;
-
- public KiaiFlashingSprite()
- {
- AutoSizeAxes = Axes.Both;
-
- Children = new Drawable[]
- {
- mainSprite = new Sprite
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- },
- flashingSprite = new Sprite
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Alpha = 0,
- Blending = BlendingParameters.Additive,
- }
- };
- }
-
- protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
- {
- if (!effectPoint.KiaiMode)
- return;
-
- flashingSprite
- .FadeTo(flash_opacity)
- .Then()
- .FadeOut(timingPoint.BeatLength * 0.75f);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs
index 611ddd08eb..b511444c44 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private GameplayState gameplayState { get; set; }
[BackgroundDependencyLoader]
- private void load(ISkinSource skin, OsuColour colours)
+ private void load(ISkinSource skin)
{
var texture = skin.GetTexture("star2");
var starBreakAdditive = skin.GetConfig(OsuSkinColour.StarBreakAdditive)?.Value ?? new Color4(255, 182, 193, 255);
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
index d2f84dcf84..c6007885be 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
@@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@@ -68,13 +69,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
// at this point, any further texture fetches should be correctly using the priority source if the base texture was retrieved using it.
// the flow above handles the case where a sliderendcircle.png is retrieved from the skin, but sliderendcircleoverlay.png doesn't exist.
// expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png (potentially from the default/fall-through skin).
- Texture overlayTexture = getTextureWithFallback("overlay");
InternalChildren = new[]
{
- hitCircleSprite = new KiaiFlashingSprite
+ hitCircleSprite = new KiaiFlashingDrawable(() => new Sprite { Texture = baseTexture })
{
- Texture = baseTexture,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
@@ -82,9 +81,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Child = hitCircleOverlay = new KiaiFlashingSprite
+ Child = hitCircleOverlay = new KiaiFlashingDrawable(() => getAnimationWithFallback(@"overlay", 1000 / 2d))
{
- Texture = overlayTexture,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
@@ -126,6 +124,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return tex ?? skin.GetTexture($"hitcircle{name}");
}
+
+ Drawable getAnimationWithFallback(string name, double frameLength)
+ {
+ Drawable animation = null;
+
+ if (!string.IsNullOrEmpty(priorityLookup))
+ {
+ animation = skin.GetAnimation($"{priorityLookup}{name}", true, true, frameLength: frameLength);
+
+ if (!allowFallback)
+ return animation;
+ }
+
+ return animation ?? skin.GetAnimation($"hitcircle{name}", true, true, frameLength: frameLength);
+ }
}
protected override void LoadComplete()
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
index 6953e66b5c..7b9cf8e1d1 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
@@ -12,6 +12,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
CursorExpand,
CursorRotate,
HitCircleOverlayAboveNumber,
+
+ // ReSharper disable once IdentifierTypo
HitCircleOverlayAboveNumer, // Some old skins will have this typo
SpinnerFrequencyModulate,
SpinnerNoBlink
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
index d1d9ee9f4d..b60ea5da21 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
@@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private OsuConfigManager config { get; set; }
[BackgroundDependencyLoader(true)]
- private void load(OsuConfigManager config, OsuRulesetConfigManager rulesetConfig)
+ private void load(OsuRulesetConfigManager rulesetConfig)
{
rulesetConfig?.BindWith(OsuRulesetSetting.ShowCursorTrail, showTrail);
}
diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/MainActivity.cs b/osu.Game.Rulesets.Taiko.Tests.Android/MainActivity.cs
index 1128a0d37f..e4f4bbfd53 100644
--- a/osu.Game.Rulesets.Taiko.Tests.Android/MainActivity.cs
+++ b/osu.Game.Rulesets.Taiko.Tests.Android/MainActivity.cs
@@ -2,13 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using Android.App;
-using Android.Content.PM;
using osu.Framework.Android;
using osu.Game.Tests;
namespace osu.Game.Rulesets.Taiko.Tests.Android
{
- [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
+ [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)]
public class MainActivity : AndroidGameActivity
{
protected override Framework.Game CreateGame() => new OsuTestBrowser();
diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist
index 5fe822946a..ac658cd14e 100644
--- a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist
+++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist
@@ -32,5 +32,7 @@
XSAppIconAssets
Assets.xcassets/AppIcon.appiconset
+ CADisableMinimumFrameDurationOnPhone
+
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs
index 269a855219..16c4148d15 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs
@@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
private BarLine createBarLineAtCurrentTime(bool major = false)
{
- var barline = new BarLine
+ var barLine = new BarLine
{
Major = major,
StartTime = Time.Current + 2000,
@@ -92,9 +92,9 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
var cpi = new ControlPointInfo();
cpi.Add(0, new TimingControlPoint { BeatLength = 500 });
- barline.ApplyDefaults(cpi, new BeatmapDifficulty());
+ barLine.ApplyDefaults(cpi, new BeatmapDifficulty());
- return barline;
+ return barLine;
}
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index b6db333dc9..b3f6a733d3 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -12,7 +12,6 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Taiko.Tests
{
[TestFixture]
- [Timeout(10000)]
public class TaikoBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs
index 16a0726c8c..41fe63a553 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
public override IEnumerable GetStatistics()
{
int hits = HitObjects.Count(s => s is Hit);
- int drumrolls = HitObjects.Count(s => s is DrumRoll);
+ int drumRolls = HitObjects.Count(s => s is DrumRoll);
int swells = HitObjects.Count(s => s is Swell);
return new[]
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{
Name = @"Drumroll Count",
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
- Content = drumrolls.ToString(),
+ Content = drumRolls.ToString(),
},
new BeatmapStatistic
{
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
index b2b5d056c3..31f5a6f570 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
@@ -9,14 +9,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
public class TaikoDifficultyAttributes : DifficultyAttributes
{
- [JsonProperty("stamina_strain")]
- public double StaminaStrain { get; set; }
+ [JsonProperty("stamina_difficulty")]
+ public double StaminaDifficulty { get; set; }
- [JsonProperty("rhythm_strain")]
- public double RhythmStrain { get; set; }
+ [JsonProperty("rhythm_difficulty")]
+ public double RhythmDifficulty { get; set; }
- [JsonProperty("colour_strain")]
- public double ColourStrain { get; set; }
+ [JsonProperty("colour_difficulty")]
+ public double ColourDifficulty { get; set; }
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
yield return v;
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
- yield return (ATTRIB_ID_STRAIN, StarRating);
+ yield return (ATTRIB_ID_DIFFICULTY, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
}
@@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
base.FromDatabaseAttributes(values);
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
- StarRating = values[ATTRIB_ID_STRAIN];
+ StarRating = values[ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
}
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index e84bee3d28..6afdef3f3c 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -91,9 +91,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
StarRating = starRating,
Mods = mods,
- StaminaStrain = staminaRating,
- RhythmStrain = rhythmRating,
- ColourStrain = colourRating,
+ StaminaDifficulty = staminaRating,
+ RhythmDifficulty = rhythmRating,
+ ColourDifficulty = colourRating,
GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate,
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
};
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
new file mode 100644
index 0000000000..80552880ea
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
@@ -0,0 +1,17 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Newtonsoft.Json;
+using osu.Game.Rulesets.Difficulty;
+
+namespace osu.Game.Rulesets.Taiko.Difficulty
+{
+ public class TaikoPerformanceAttributes : PerformanceAttributes
+ {
+ [JsonProperty("difficulty")]
+ public double Difficulty { get; set; }
+
+ [JsonProperty("accuracy")]
+ public double Accuracy { get; set; }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 90dd733dfd..bcd55f8fae 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
}
- public override double Calculate(Dictionary categoryDifficulty = null)
+ public override PerformanceAttributes Calculate()
{
mods = Score.Mods;
countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great);
@@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss);
- // Custom multipliers for NoFail and SpunOut.
double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
if (mods.Any(m => m is ModNoFail))
@@ -44,43 +43,38 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (mods.Any(m => m is ModHidden))
multiplier *= 1.10;
- double strainValue = computeStrainValue();
+ double difficultyValue = computeDifficultyValue();
double accuracyValue = computeAccuracyValue();
double totalValue =
Math.Pow(
- Math.Pow(strainValue, 1.1) +
+ Math.Pow(difficultyValue, 1.1) +
Math.Pow(accuracyValue, 1.1), 1.0 / 1.1
) * multiplier;
- if (categoryDifficulty != null)
+ return new TaikoPerformanceAttributes
{
- categoryDifficulty["Strain"] = strainValue;
- categoryDifficulty["Accuracy"] = accuracyValue;
- }
-
- return totalValue;
+ Difficulty = difficultyValue,
+ Accuracy = accuracyValue,
+ Total = totalValue
+ };
}
- private double computeStrainValue()
+ private double computeDifficultyValue()
{
- double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0;
+ double difficultyValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0;
- // Longer maps are worth more
double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
- strainValue *= lengthBonus;
+ difficultyValue *= lengthBonus;
- // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
- strainValue *= Math.Pow(0.985, countMiss);
+ difficultyValue *= Math.Pow(0.985, countMiss);
if (mods.Any(m => m is ModHidden))
- strainValue *= 1.025;
+ difficultyValue *= 1.025;
if (mods.Any(m => m is ModFlashlight))
- // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
- strainValue *= 1.05 * lengthBonus;
+ difficultyValue *= 1.05 * lengthBonus;
- // Scale the speed value with accuracy _slightly_
- return strainValue * Score.Accuracy;
+ return difficultyValue * Score.Accuracy;
}
private double computeAccuracyValue()
@@ -88,11 +82,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (Attributes.GreatHitWindow <= 0)
return 0;
- // Lots of arbitrary values from testing.
- // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
double accValue = Math.Pow(150.0 / Attributes.GreatHitWindow, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0;
- // Bonus for many hitcircles - it's harder to keep good accuracy up for longer
+ // Bonus for many objects - it's harder to keep good accuracy up for longer
return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Default/CentreHitCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Default/CentreHitCirclePiece.cs
index 455b2fc596..25f895708f 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Default/CentreHitCirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Default/CentreHitCirclePiece.cs
@@ -5,7 +5,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects;
using osuTK;
@@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load()
{
AccentColour = Hit.COLOUR_CENTRE;
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Default/RimHitCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Default/RimHitCirclePiece.cs
index bd21d511b1..c6165495d8 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Default/RimHitCirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Default/RimHitCirclePiece.cs
@@ -5,7 +5,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects;
using osuTK;
using osuTK.Graphics;
@@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load()
{
AccentColour = Hit.COLOUR_RIM;
}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs
index e1063e1071..7ba2618a63 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs
@@ -7,7 +7,6 @@ using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Judgements;
@@ -39,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.UI
}
[BackgroundDependencyLoader(true)]
- private void load(TextureStore textures, GameplayState gameplayState)
+ private void load(GameplayState gameplayState)
{
InternalChildren = new[]
{
diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
index 861b800038..16be20f7f3 100644
--- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
+++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
@@ -12,7 +12,6 @@ using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.UI;
-using osu.Game.Screens.Play;
using osu.Game.Skinning;
using osuTK;
@@ -149,9 +148,6 @@ namespace osu.Game.Rulesets.Taiko.UI
centreHit.Colour = colours.Pink;
}
- [Resolved(canBeNull: true)]
- private GameplayClock gameplayClock { get; set; }
-
public bool OnPressed(KeyBindingPressEvent e)
{
Drawable target = null;
diff --git a/osu.Game.Tests.Android/MainActivity.cs b/osu.Game.Tests.Android/MainActivity.cs
index 0695c8e37b..dbe74a10da 100644
--- a/osu.Game.Tests.Android/MainActivity.cs
+++ b/osu.Game.Tests.Android/MainActivity.cs
@@ -2,12 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using Android.App;
-using Android.Content.PM;
using osu.Framework.Android;
namespace osu.Game.Tests.Android
{
- [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = true)]
+ [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)]
public class MainActivity : AndroidGameActivity
{
protected override Framework.Game CreateGame() => new OsuTestBrowser();
diff --git a/osu.Game.Tests.iOS/Info.plist b/osu.Game.Tests.iOS/Info.plist
index 98a4223116..1a89345bc5 100644
--- a/osu.Game.Tests.iOS/Info.plist
+++ b/osu.Game.Tests.iOS/Info.plist
@@ -32,5 +32,7 @@
XSAppIconAssets
Assets.xcassets/AppIcon.appiconset
+ CADisableMinimumFrameDurationOnPhone
+
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
index 6e5a546e87..9ac7838821 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
@@ -2,14 +2,20 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
+using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Replays;
+using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko;
using osu.Game.Scoring;
@@ -21,6 +27,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
[TestFixture]
public class LegacyScoreDecoderTest
{
+ private CultureInfo originalCulture;
+
+ [SetUp]
+ public void SetUp()
+ {
+ originalCulture = CultureInfo.CurrentCulture;
+ }
+
[Test]
public void TestDecodeManiaReplay()
{
@@ -44,6 +58,58 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
+ [Test]
+ public void TestCultureInvariance()
+ {
+ var ruleset = new OsuRuleset().RulesetInfo;
+ var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
+ var beatmap = new TestBeatmap(ruleset);
+ var score = new Score
+ {
+ ScoreInfo = scoreInfo,
+ Replay = new Replay
+ {
+ Frames = new List
+ {
+ new OsuReplayFrame(2000, OsuPlayfield.BASE_SIZE / 2, OsuAction.LeftButton)
+ }
+ }
+ };
+
+ // the "se" culture is used here, as it encodes the negative number sign as U+2212 MINUS SIGN,
+ // rather than the classic ASCII U+002D HYPHEN-MINUS.
+ CultureInfo.CurrentCulture = new CultureInfo("se");
+
+ var encodeStream = new MemoryStream();
+
+ var encoder = new LegacyScoreEncoder(score, beatmap);
+ encoder.Encode(encodeStream);
+
+ var decodeStream = new MemoryStream(encodeStream.GetBuffer());
+
+ var decoder = new TestLegacyScoreDecoder();
+ var decodedAfterEncode = decoder.Parse(decodeStream);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(decodedAfterEncode, Is.Not.Null);
+
+ Assert.That(decodedAfterEncode.ScoreInfo.User.Username, Is.EqualTo(scoreInfo.User.Username));
+ Assert.That(decodedAfterEncode.ScoreInfo.Ruleset, Is.EqualTo(scoreInfo.Ruleset));
+ Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(scoreInfo.TotalScore));
+ Assert.That(decodedAfterEncode.ScoreInfo.MaxCombo, Is.EqualTo(scoreInfo.MaxCombo));
+ Assert.That(decodedAfterEncode.ScoreInfo.Date, Is.EqualTo(scoreInfo.Date));
+
+ Assert.That(decodedAfterEncode.Replay.Frames.Count, Is.EqualTo(1));
+ });
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ CultureInfo.CurrentCulture = originalCulture;
+ }
+
private class TestLegacyScoreDecoder : LegacyScoreDecoder
{
private static readonly Dictionary rulesets = new Ruleset[]
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index 6e2b9d20a8..c02141bf9f 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportWhenClosed()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -57,7 +57,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDelete()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -78,7 +78,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDeleteFromStream()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -93,7 +93,7 @@ namespace osu.Game.Tests.Beatmaps.IO
using (var stream = File.OpenRead(tempPath))
{
importedSet = await manager.Import(new ImportTask(stream, Path.GetFileName(tempPath)));
- ensureLoaded(osu);
+ await ensureLoaded(osu);
}
Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing");
@@ -114,7 +114,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenImport()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -140,7 +140,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportThenImportWithReZip()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -171,7 +171,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp));
- ensureLoaded(osu);
+ await ensureLoaded(osu);
// but contents doesn't, so existing should still be used.
Assert.IsTrue(imported.ID == importedSecondTime.Value.ID);
@@ -192,7 +192,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportThenImportWithChangedHashedFile()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -225,7 +225,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp));
- ensureLoaded(osu);
+ await ensureLoaded(osu);
// check the newly "imported" beatmap is not the original.
Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
@@ -247,7 +247,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Ignore("intentionally broken by import optimisations")]
public async Task TestImportThenImportWithChangedFile()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -277,7 +277,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp));
- ensureLoaded(osu);
+ await ensureLoaded(osu);
// check the newly "imported" beatmap is not the original.
Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
@@ -298,7 +298,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportThenImportWithDifferentFilename()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -328,7 +328,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp));
- ensureLoaded(osu);
+ await ensureLoaded(osu);
// check the newly "imported" beatmap is not the original.
Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
@@ -351,7 +351,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportCorruptThenImport()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -392,7 +392,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestModelCreationFailureDoesntReturn()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -428,7 +428,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestRollbackOnFailure()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -507,7 +507,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDeleteThenImport()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -534,7 +534,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDeleteThenImportWithOnlineIDsMissing()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}"))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -566,7 +566,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportWithDuplicateBeatmapIDs()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -621,8 +621,8 @@ namespace osu.Game.Tests.Beatmaps.IO
[NonParallelizable]
public void TestImportOverIPC()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-host", true))
- using (HeadlessGameHost client = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-client", true))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(true))
+ using (HeadlessGameHost client = new CleanRunHeadlessGameHost(true))
{
try
{
@@ -637,7 +637,7 @@ namespace osu.Game.Tests.Beatmaps.IO
if (!importer.ImportAsync(temp).Wait(10000))
Assert.Fail(@"IPC took too long to send");
- ensureLoaded(osu);
+ ensureLoaded(osu).WaitSafely();
waitForOrAssert(() => !File.Exists(temp), "Temporary still exists after IPC import", 5000);
}
@@ -651,7 +651,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportWhenFileOpen()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -659,7 +659,7 @@ namespace osu.Game.Tests.Beatmaps.IO
string temp = TestResources.GetTestBeatmapForImport();
using (File.OpenRead(temp))
await osu.Dependencies.Get().Import(temp);
- ensureLoaded(osu);
+ await ensureLoaded(osu);
File.Delete(temp);
Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
}
@@ -673,7 +673,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportWithDuplicateHashes()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -698,7 +698,7 @@ namespace osu.Game.Tests.Beatmaps.IO
await osu.Dependencies.Get().Import(temp);
- ensureLoaded(osu);
+ await ensureLoaded(osu);
}
finally
{
@@ -715,7 +715,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportNestedStructure()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -741,7 +741,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var imported = await osu.Dependencies.Get().Import(new ImportTask(temp));
- ensureLoaded(osu);
+ await ensureLoaded(osu);
Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder");
}
@@ -760,7 +760,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportWithIgnoredDirectoryInArchive()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -794,7 +794,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var imported = await osu.Dependencies.Get().Import(new ImportTask(temp));
- ensureLoaded(osu);
+ await ensureLoaded(osu);
Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored");
Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder");
@@ -812,9 +812,9 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
- public async Task TestUpdateBeatmapInfo()
+ public void TestUpdateBeatmapInfo()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -822,7 +822,8 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get();
string temp = TestResources.GetTestBeatmapForImport();
- await osu.Dependencies.Get().Import(temp);
+
+ osu.Dependencies.Get().Import(temp).WaitSafely();
// Update via the beatmap, not the beatmap info, to ensure correct linking
BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0];
@@ -842,9 +843,9 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
- public async Task TestUpdateBeatmapFile()
+ public void TestUpdateBeatmapFile()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -852,7 +853,8 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get();
string temp = TestResources.GetTestBeatmapForImport();
- await osu.Dependencies.Get().Import(temp);
+
+ osu.Dependencies.Get().Import(temp).WaitSafely();
BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0];
@@ -888,7 +890,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public void TestSaveRemovesInvalidCharactersFromPath()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -922,7 +924,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public void TestCreateNewEmptyBeatmap()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -949,7 +951,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public void TestCreateNewBeatmapWithObject()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -976,35 +978,35 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
- public static async Task LoadQuickOszIntoOsu(OsuGameBase osu)
+ public static Task LoadQuickOszIntoOsu(OsuGameBase osu) => Task.Factory.StartNew(() =>
{
string temp = TestResources.GetQuickTestBeatmapForImport();
var manager = osu.Dependencies.Get();
- var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false);
+ var importedSet = manager.Import(new ImportTask(temp)).GetResultSafely();
- ensureLoaded(osu);
+ ensureLoaded(osu).WaitSafely();
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID);
- }
+ }, TaskCreationOptions.LongRunning);
- public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
+ public static Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) => Task.Factory.StartNew(() =>
{
string temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
var manager = osu.Dependencies.Get();
- var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false);
+ var importedSet = manager.Import(new ImportTask(temp)).GetResultSafely();
- ensureLoaded(osu);
+ ensureLoaded(osu).WaitSafely();
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID);
- }
+ }, TaskCreationOptions.LongRunning);
private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu)
{
@@ -1022,7 +1024,7 @@ namespace osu.Game.Tests.Beatmaps.IO
{
return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
{
- OnlineScoreID = 2,
+ OnlineID = 2,
BeatmapInfo = beatmapInfo,
BeatmapInfoID = beatmapInfo.ID
}, new ImportScoreTest.TestArchiveReader());
@@ -1053,7 +1055,7 @@ namespace osu.Game.Tests.Beatmaps.IO
Assert.AreEqual(expected, osu.Dependencies.Get().Get().FileInfo.Count(f => f.ReferenceCount == 1));
}
- private static void ensureLoaded(OsuGameBase osu, int timeout = 60000)
+ private static Task ensureLoaded(OsuGameBase osu, int timeout = 60000) => Task.Factory.StartNew(() =>
{
IEnumerable resultSets = null;
var store = osu.Dependencies.Get();
@@ -1089,14 +1091,14 @@ namespace osu.Game.Tests.Beatmaps.IO
Assert.IsTrue(beatmap?.HitObjects.Any() == true);
beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap;
Assert.IsTrue(beatmap?.HitObjects.Any() == true);
- }
+ }, TaskCreationOptions.LongRunning);
private static void waitForOrAssert(Func result, string failureMessage, int timeout = 60000)
{
- Task task = Task.Run(() =>
+ Task task = Task.Factory.StartNew(() =>
{
while (!result()) Thread.Sleep(200);
- });
+ }, TaskCreationOptions.LongRunning);
Assert.IsTrue(task.Wait(timeout), failureMessage);
}
diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs
index 2a60a7b96d..26ab8808b9 100644
--- a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs
+++ b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs
@@ -8,6 +8,7 @@ using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Extensions;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
@@ -25,9 +26,6 @@ namespace osu.Game.Tests.Beatmaps
private BeatmapSetInfo importedSet;
- [Resolved]
- private BeatmapManager beatmaps { get; set; }
-
private TestBeatmapDifficultyCache difficultyCache;
private IBindable starDifficultyBindable;
@@ -35,7 +33,7 @@ namespace osu.Game.Tests.Beatmaps
[BackgroundDependencyLoader]
private void load(OsuGameBase osu)
{
- importedSet = ImportBeatmapTest.LoadQuickOszIntoOsu(osu).Result;
+ importedSet = ImportBeatmapTest.LoadQuickOszIntoOsu(osu).GetResultSafely();
}
[SetUpSteps]
diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs
index af87fc17ad..8def8005f1 100644
--- a/osu.Game.Tests/Chat/MessageFormatterTests.cs
+++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs
@@ -9,6 +9,21 @@ namespace osu.Game.Tests.Chat
[TestFixture]
public class MessageFormatterTests
{
+ private string originalWebsiteRootUrl;
+
+ [OneTimeSetUp]
+ public void OneTimeSetUp()
+ {
+ originalWebsiteRootUrl = MessageFormatter.WebsiteRootUrl;
+ MessageFormatter.WebsiteRootUrl = "dev.ppy.sh";
+ }
+
+ [OneTimeTearDown]
+ public void OneTimeTearDown()
+ {
+ MessageFormatter.WebsiteRootUrl = originalWebsiteRootUrl;
+ }
+
[Test]
public void TestBareLink()
{
@@ -32,8 +47,6 @@ namespace osu.Game.Tests.Chat
[TestCase(LinkAction.External, "https://dev.ppy.sh/beatmapsets/discussions/123", "https://dev.ppy.sh/beatmapsets/discussions/123")]
public void TestBeatmapLinks(LinkAction expectedAction, string expectedArg, string link)
{
- MessageFormatter.WebsiteRootUrl = "dev.ppy.sh";
-
Message result = MessageFormatter.FormatMessage(new Message { Content = link });
Assert.AreEqual(result.Content, result.DisplayContent);
@@ -47,7 +60,10 @@ namespace osu.Game.Tests.Chat
[Test]
public void TestMultipleComplexLinks()
{
- Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a http://test.io/link#fragment. (see https://twitter.com). Also, This string should not be altered. http://example.com/" });
+ Message result = MessageFormatter.FormatMessage(new Message
+ {
+ Content = "This is a http://test.io/link#fragment. (see https://twitter.com). Also, This string should not be altered. http://example.com/"
+ });
Assert.AreEqual(result.Content, result.DisplayContent);
Assert.AreEqual(3, result.Links.Count);
@@ -104,7 +120,7 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual("This is a Wiki Link.", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
- Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki Link", result.Links[0].Url);
+ Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki Link", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(9, result.Links[0].Length);
}
@@ -117,15 +133,15 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual("This is a Wiki Link Wiki:LinkWiki.Link.", result.DisplayContent);
Assert.AreEqual(3, result.Links.Count);
- Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki Link", result.Links[0].Url);
+ Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki Link", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(9, result.Links[0].Length);
- Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki:Link", result.Links[1].Url);
+ Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki:Link", result.Links[1].Url);
Assert.AreEqual(20, result.Links[1].Index);
Assert.AreEqual(9, result.Links[1].Length);
- Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki.Link", result.Links[2].Url);
+ Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki.Link", result.Links[2].Url);
Assert.AreEqual(29, result.Links[2].Index);
Assert.AreEqual(9, result.Links[2].Length);
}
@@ -445,12 +461,15 @@ namespace osu.Game.Tests.Chat
[Test]
public void TestLinkComplex()
{
- Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [http://www.simple-test.com simple test] with some [traps] and [[wiki links]]. Don't forget to visit https://osu.ppy.sh (now!)[http://google.com]\uD83D\uDE12" });
+ Message result = MessageFormatter.FormatMessage(new Message
+ {
+ Content = "This is a [http://www.simple-test.com simple test] with some [traps] and [[wiki links]]. Don't forget to visit https://osu.ppy.sh (now!)[http://google.com]\uD83D\uDE12"
+ });
Assert.AreEqual("This is a simple test with some [traps] and wiki links. Don't forget to visit https://osu.ppy.sh now!\0\0\0", result.DisplayContent);
Assert.AreEqual(5, result.Links.Count);
- Link f = result.Links.Find(l => l.Url == "https://osu.ppy.sh/wiki/wiki links");
+ Link f = result.Links.Find(l => l.Url == "https://dev.ppy.sh/wiki/wiki links");
Assert.That(f, Is.Not.Null);
Assert.AreEqual(44, f.Index);
Assert.AreEqual(10, f.Length);
@@ -514,8 +533,6 @@ namespace osu.Game.Tests.Chat
[TestCase("https://dev.ppy.sh/home/changelog/lazer/2021.1012", "lazer/2021.1012")]
public void TestChangelogLinks(string link, string expectedArg)
{
- MessageFormatter.WebsiteRootUrl = "dev.ppy.sh";
-
LinkDetails result = MessageFormatter.GetLinkDetails(link);
Assert.AreEqual(LinkAction.OpenChangelog, result.Action);
diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
index d87ac29d75..53e4ef07e7 100644
--- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
+++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
@@ -6,6 +6,7 @@ using System.IO;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
+using osu.Framework.Extensions;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Tests.Resources;
@@ -128,8 +129,12 @@ namespace osu.Game.Tests.Collections.IO
[Test]
public async Task TestSaveAndReload()
{
- using (HeadlessGameHost host = new TestRunHeadlessGameHost("TestSaveAndReload", bypassCleanup: true))
+ string firstRunName;
+
+ using (var host = new CleanRunHeadlessGameHost(bypassCleanup: true))
{
+ firstRunName = host.Name;
+
try
{
var osu = LoadOsuIntoHost(host, true);
@@ -149,7 +154,8 @@ namespace osu.Game.Tests.Collections.IO
}
}
- using (HeadlessGameHost host = new TestRunHeadlessGameHost("TestSaveAndReload"))
+ // Name matches the automatically chosen name from `CleanRunHeadlessGameHost` above, so we end up using the same storage location.
+ using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName))
{
try
{
@@ -174,7 +180,7 @@ namespace osu.Game.Tests.Collections.IO
{
// intentionally spin this up on a separate task to avoid disposal deadlocks.
// see https://github.com/EventStore/EventStore/issues/1179
- await Task.Run(() => osu.CollectionManager.Import(stream).Wait());
+ await Task.Factory.StartNew(() => osu.CollectionManager.Import(stream).WaitSafely(), TaskCreationOptions.LongRunning);
}
}
}
diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs
index a6edd6cb5f..e47e24021f 100644
--- a/osu.Game.Tests/Database/BeatmapImporterTests.cs
+++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs
@@ -809,7 +809,7 @@ namespace osu.Game.Tests.Database
// TODO: reimplement when we have score support in realm.
// return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
// {
- // OnlineScoreID = 2,
+ // OnlineID = 2,
// Beatmap = beatmap,
// BeatmapInfoID = beatmap.ID
// }, new ImportScoreTest.TestArchiveReader());
diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs
index 9b6769b788..9432a56741 100644
--- a/osu.Game.Tests/Database/RealmLiveTests.cs
+++ b/osu.Game.Tests/Database/RealmLiveTests.cs
@@ -6,6 +6,8 @@ using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
+using osu.Framework.Extensions;
+using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Models;
using Realms;
@@ -21,14 +23,41 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
- ILive beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive();
+ ILive beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive(realmFactory);
- ILive beatmap2 = realmFactory.CreateContext().All().First().ToLive();
+ ILive beatmap2 = realmFactory.CreateContext().All().First().ToLive(realmFactory);
Assert.AreEqual(beatmap, beatmap2);
});
}
+ [Test]
+ public void TestAccessAfterStorageMigrate()
+ {
+ RunTestWithRealm((realmFactory, storage) =>
+ {
+ var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
+
+ ILive liveBeatmap;
+
+ using (var context = realmFactory.CreateContext())
+ {
+ context.Write(r => r.Add(beatmap));
+
+ liveBeatmap = beatmap.ToLive(realmFactory);
+ }
+
+ using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
+ {
+ migratedStorage.DeleteDirectory(string.Empty);
+
+ storage.Migrate(migratedStorage);
+
+ Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
+ }
+ });
+ }
+
[Test]
public void TestAccessAfterAttach()
{
@@ -36,7 +65,7 @@ namespace osu.Game.Tests.Database
{
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
- var liveBeatmap = beatmap.ToLive();
+ var liveBeatmap = beatmap.ToLive(realmFactory);
using (var context = realmFactory.CreateContext())
context.Write(r => r.Add(beatmap));
@@ -49,7 +78,7 @@ namespace osu.Game.Tests.Database
public void TestAccessNonManaged()
{
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
- var liveBeatmap = beatmap.ToLive();
+ var liveBeatmap = beatmap.ToLiveUnmanaged();
Assert.IsFalse(beatmap.Hidden);
Assert.IsFalse(liveBeatmap.Value.Hidden);
@@ -74,9 +103,9 @@ namespace osu.Game.Tests.Database
{
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
- liveBeatmap = beatmap.ToLive();
+ liveBeatmap = beatmap.ToLive(realmFactory);
}
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
@@ -87,7 +116,7 @@ namespace osu.Game.Tests.Database
Assert.IsTrue(beatmap.IsValid);
Assert.IsFalse(beatmap.Hidden);
});
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
});
}
@@ -103,9 +132,9 @@ namespace osu.Game.Tests.Database
{
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
- liveBeatmap = beatmap.ToLive();
+ liveBeatmap = beatmap.ToLive(realmFactory);
}
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
@@ -113,7 +142,7 @@ namespace osu.Game.Tests.Database
{
liveBeatmap.PerformWrite(beatmap => { beatmap.Hidden = true; });
liveBeatmap.PerformRead(beatmap => { Assert.IsTrue(beatmap.Hidden); });
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
});
}
@@ -123,7 +152,7 @@ namespace osu.Game.Tests.Database
RunTestWithRealm((realmFactory, _) =>
{
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
- var liveBeatmap = beatmap.ToLive();
+ var liveBeatmap = beatmap.ToLive(realmFactory);
Assert.DoesNotThrow(() =>
{
@@ -145,9 +174,9 @@ namespace osu.Game.Tests.Database
{
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
- liveBeatmap = beatmap.ToLive();
+ liveBeatmap = beatmap.ToLive(realmFactory);
}
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
@@ -167,7 +196,7 @@ namespace osu.Game.Tests.Database
var __ = liveBeatmap.Value;
});
}
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
});
}
@@ -183,9 +212,9 @@ namespace osu.Game.Tests.Database
{
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
- liveBeatmap = beatmap.ToLive();
+ liveBeatmap = beatmap.ToLive(realmFactory);
}
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
@@ -195,7 +224,7 @@ namespace osu.Game.Tests.Database
{
var unused = liveBeatmap.Value;
});
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
});
}
@@ -222,9 +251,9 @@ namespace osu.Game.Tests.Database
// not just a refresh from the resolved Live.
threadContext.Write(r => r.Add(new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
- liveBeatmap = beatmap.ToLive();
+ liveBeatmap = beatmap.ToLive(realmFactory);
}
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs
index 04c9f2577a..4e67f09dca 100644
--- a/osu.Game.Tests/Database/RealmTest.cs
+++ b/osu.Game.Tests/Database/RealmTest.cs
@@ -10,6 +10,7 @@ using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Database;
+using osu.Game.IO;
using osu.Game.Models;
#nullable enable
@@ -27,15 +28,16 @@ namespace osu.Game.Tests.Database
storage.DeleteDirectory(string.Empty);
}
- protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "")
+ protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "")
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(caller))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
{
host.Run(new RealmTestGame(() =>
{
- var testStorage = storage.GetStorageForDirectory(caller);
+ // ReSharper disable once AccessToDisposedClosure
+ var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller));
- using (var realmFactory = new RealmContextFactory(testStorage, caller))
+ using (var realmFactory = new RealmContextFactory(testStorage, "client"))
{
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
testAction(realmFactory, testStorage);
@@ -52,13 +54,13 @@ namespace osu.Game.Tests.Database
protected void RunTestWithRealmAsync(Func testAction, [CallerMemberName] string caller = "")
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(caller))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
{
host.Run(new RealmTestGame(async () =>
{
var testStorage = storage.GetStorageForDirectory(caller);
- using (var realmFactory = new RealmContextFactory(testStorage, caller))
+ using (var realmFactory = new RealmContextFactory(testStorage, "client"))
{
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
await testAction(realmFactory, testStorage);
diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs
index f4e0838be1..cc7e8a0c97 100644
--- a/osu.Game.Tests/Database/RulesetStoreTests.cs
+++ b/osu.Game.Tests/Database/RulesetStoreTests.cs
@@ -45,9 +45,9 @@ namespace osu.Game.Tests.Database
{
var rulesets = new RealmRulesetStore(realmFactory, storage);
- Assert.IsTrue((rulesets.AvailableRulesets.First() as RealmRuleset)?.IsManaged == false);
- Assert.IsTrue((rulesets.GetRuleset(0) as RealmRuleset)?.IsManaged == false);
- Assert.IsTrue((rulesets.GetRuleset("mania") as RealmRuleset)?.IsManaged == false);
+ Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged);
+ Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged);
+ Assert.IsFalse(rulesets.GetRuleset("mania")?.IsManaged);
});
}
}
diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs
index 860828ae81..f05d9ab3dc 100644
--- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs
+++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs
@@ -52,6 +52,45 @@ namespace osu.Game.Tests.Database
Assert.That(queryCount(GlobalAction.Select), Is.EqualTo(2));
}
+ [Test]
+ public void TestDefaultsPopulationRemovesExcess()
+ {
+ Assert.That(queryCount(), Is.EqualTo(0));
+
+ KeyBindingContainer testContainer = new TestKeyBindingContainer();
+
+ // Add some excess bindings for an action which only supports 1.
+ using (var realm = realmContextFactory.CreateContext())
+ using (var transaction = realm.BeginWrite())
+ {
+ realm.Add(new RealmKeyBinding
+ {
+ Action = GlobalAction.Back,
+ KeyCombination = new KeyCombination(InputKey.A)
+ });
+
+ realm.Add(new RealmKeyBinding
+ {
+ Action = GlobalAction.Back,
+ KeyCombination = new KeyCombination(InputKey.S)
+ });
+
+ realm.Add(new RealmKeyBinding
+ {
+ Action = GlobalAction.Back,
+ KeyCombination = new KeyCombination(InputKey.D)
+ });
+
+ transaction.Commit();
+ }
+
+ Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3));
+
+ keyBindingStore.Register(testContainer, Enumerable.Empty());
+
+ Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(1));
+ }
+
private int queryCount(GlobalAction? match = null)
{
using (var realm = realmContextFactory.CreateContext())
diff --git a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs
index 4ab6e5cef6..91d9a8753c 100644
--- a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs
+++ b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs
@@ -40,80 +40,80 @@ namespace osu.Game.Tests.Editing.Checks
[Test]
public void TestNormalControlPointVolume()
{
- var hitcircle = new HitCircle
+ var hitCircle = new HitCircle
{
StartTime = 0,
Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
};
- hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
+ hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
- assertOk(new List { hitcircle });
+ assertOk(new List { hitCircle });
}
[Test]
public void TestLowControlPointVolume()
{
- var hitcircle = new HitCircle
+ var hitCircle = new HitCircle
{
StartTime = 1000,
Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
};
- hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
+ hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
- assertLowVolume(new List { hitcircle });
+ assertLowVolume(new List { hitCircle });
}
[Test]
public void TestMutedControlPointVolume()
{
- var hitcircle = new HitCircle
+ var hitCircle = new HitCircle
{
StartTime = 2000,
Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
};
- hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
+ hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
- assertMuted(new List { hitcircle });
+ assertMuted(new List { hitCircle });
}
[Test]
public void TestNormalSampleVolume()
{
// The sample volume should take precedence over the control point volume.
- var hitcircle = new HitCircle
+ var hitCircle = new HitCircle
{
StartTime = 2000,
Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) }
};
- hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
+ hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
- assertOk(new List { hitcircle });
+ assertOk(new List { hitCircle });
}
[Test]
public void TestLowSampleVolume()
{
- var hitcircle = new HitCircle
+ var hitCircle = new HitCircle
{
StartTime = 2000,
Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_low) }
};
- hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
+ hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
- assertLowVolume(new List { hitcircle });
+ assertLowVolume(new List { hitCircle });
}
[Test]
public void TestMutedSampleVolume()
{
- var hitcircle = new HitCircle
+ var hitCircle = new HitCircle
{
StartTime = 0,
Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) }
};
- hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
+ hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
- assertMuted(new List { hitcircle });
+ assertMuted(new List { hitCircle });
}
[Test]
diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
index 3bf6aaac7a..f0ebd7a8cc 100644
--- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
@@ -5,8 +5,10 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics.Audio;
@@ -15,6 +17,8 @@ using osu.Framework.IO.Stores;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Audio;
+using osu.Game.Configuration;
+using osu.Game.Database;
using osu.Game.IO;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@@ -33,6 +37,9 @@ namespace osu.Game.Tests.Gameplay
[HeadlessTest]
public class TestSceneStoryboardSamples : OsuTestScene, IStorageResourceProvider
{
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
[Test]
public void TestRetrieveTopLevelSample()
{
@@ -164,10 +171,43 @@ namespace osu.Game.Tests.Gameplay
((IApplicableToRate)testedMod).ApplyToRate(sampleInfo.StartTime)));
}
+ [Test]
+ public void TestSamplePlaybackWithBeatmapHitsoundsOff()
+ {
+ GameplayClockContainer gameplayContainer = null;
+ TestDrawableStoryboardSample sample = null;
+
+ AddStep("disable beatmap hitsounds", () => config.SetValue(OsuSetting.BeatmapHitsounds, false));
+
+ AddStep("setup storyboard sample", () =>
+ {
+ Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, this);
+
+ var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
+
+ Add(gameplayContainer = new MasterGameplayClockContainer(Beatmap.Value, 0)
+ {
+ Child = beatmapSkinSourceContainer
+ });
+
+ beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
+ {
+ Clock = gameplayContainer.GameplayClock
+ });
+ });
+
+ AddStep("start", () => gameplayContainer.Start());
+
+ AddUntilStep("sample played", () => sample.IsPlayed);
+ AddUntilStep("sample has lifetime end", () => sample.LifetimeEnd < double.MaxValue);
+
+ AddStep("restore default", () => config.GetBindable(OsuSetting.BeatmapHitsounds).SetDefault());
+ }
+
private class TestSkin : LegacySkin
{
public TestSkin(string resourceName, IStorageResourceProvider resources)
- : base(DefaultLegacySkin.Info, new TestResourceStore(resourceName), resources, "skin.ini")
+ : base(DefaultLegacySkin.CreateInfo(), new TestResourceStore(resourceName), resources, "skin.ini")
{
}
}
@@ -183,7 +223,8 @@ namespace osu.Game.Tests.Gameplay
public byte[] Get(string name) => name == resourceName ? TestResources.GetStore().Get("Resources/Samples/test-sample.mp3") : null;
- public Task GetAsync(string name) => name == resourceName ? TestResources.GetStore().GetAsync("Resources/Samples/test-sample.mp3") : null;
+ public Task GetAsync(string name, CancellationToken cancellationToken = default)
+ => name == resourceName ? TestResources.GetStore().GetAsync("Resources/Samples/test-sample.mp3", cancellationToken) : null;
public Stream GetStream(string name) => name == resourceName ? TestResources.GetStore().GetStream("Resources/Samples/test-sample.mp3") : null;
@@ -220,6 +261,7 @@ namespace osu.Game.Tests.Gameplay
public AudioManager AudioManager => Audio;
public IResourceStore Files => null;
public new IResourceStore Resources => base.Resources;
+ public RealmContextFactory RealmContextFactory => null;
public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => null;
#endregion
diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs
index dbeb453d4d..a658a0eaeb 100644
--- a/osu.Game.Tests/ImportTest.cs
+++ b/osu.Game.Tests/ImportTest.cs
@@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions;
using osu.Framework.Platform;
using osu.Game.Collections;
using osu.Game.Tests.Resources;
@@ -58,7 +59,7 @@ namespace osu.Game.Tests
{
// Beatmap must be imported before the collection manager is loaded.
if (withBeatmap)
- BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait();
+ BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely();
AddInternal(CollectionManager = new CollectionManager(Storage));
}
diff --git a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
index b612899d79..28937b2120 100644
--- a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
+++ b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
@@ -18,9 +18,6 @@ namespace osu.Game.Tests.Input
[Resolved]
private FrameworkConfigManager frameworkConfigManager { get; set; }
- [Resolved]
- private OsuConfigManager osuConfigManager { get; set; }
-
[TestCase(WindowMode.Windowed)]
[TestCase(WindowMode.Borderless)]
public void TestDisableConfining(WindowMode windowMode)
diff --git a/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs b/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs
index ce6b3a68a5..cd6879cf01 100644
--- a/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs
+++ b/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs
@@ -34,20 +34,20 @@ namespace osu.Game.Tests.Mods
var mod3 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } };
var doubleConvertedMod1 = new APIMod(mod1).ToMod(ruleset);
- var doulbeConvertedMod2 = new APIMod(mod2).ToMod(ruleset);
- var doulbeConvertedMod3 = new APIMod(mod3).ToMod(ruleset);
+ var doubleConvertedMod2 = new APIMod(mod2).ToMod(ruleset);
+ var doubleConvertedMod3 = new APIMod(mod3).ToMod(ruleset);
Assert.That(mod1, Is.Not.EqualTo(mod2));
- Assert.That(doubleConvertedMod1, Is.Not.EqualTo(doulbeConvertedMod2));
+ Assert.That(doubleConvertedMod1, Is.Not.EqualTo(doubleConvertedMod2));
Assert.That(mod2, Is.EqualTo(mod2));
- Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod2));
+ Assert.That(doubleConvertedMod2, Is.EqualTo(doubleConvertedMod2));
Assert.That(mod2, Is.EqualTo(mod3));
- Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod3));
+ Assert.That(doubleConvertedMod2, Is.EqualTo(doubleConvertedMod3));
Assert.That(mod3, Is.EqualTo(mod2));
- Assert.That(doulbeConvertedMod3, Is.EqualTo(doulbeConvertedMod2));
+ Assert.That(doubleConvertedMod3, Is.EqualTo(doubleConvertedMod2));
}
}
}
diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
index 8a063b3c6e..4bb54f1625 100644
--- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
+++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Tests.NonVisual
{
try
{
- string defaultStorageLocation = getDefaultLocationFor(nameof(TestDefaultDirectory));
+ string defaultStorageLocation = getDefaultLocationFor(host);
var osu = LoadOsuIntoHost(host);
var storage = osu.Dependencies.Get();
@@ -61,6 +61,7 @@ namespace osu.Game.Tests.NonVisual
finally
{
host.Exit();
+ cleanupPath(customPath);
}
}
}
@@ -94,6 +95,7 @@ namespace osu.Game.Tests.NonVisual
finally
{
host.Exit();
+ cleanupPath(customPath);
}
}
}
@@ -107,7 +109,7 @@ namespace osu.Game.Tests.NonVisual
{
try
{
- string defaultStorageLocation = getDefaultLocationFor(nameof(TestMigration));
+ string defaultStorageLocation = getDefaultLocationFor(host);
var osu = LoadOsuIntoHost(host);
var storage = osu.Dependencies.Get();
@@ -160,6 +162,7 @@ namespace osu.Game.Tests.NonVisual
finally
{
host.Exit();
+ cleanupPath(customPath);
}
}
}
@@ -168,7 +171,7 @@ namespace osu.Game.Tests.NonVisual
public void TestMigrationBetweenTwoTargets()
{
string customPath = prepareCustomPath();
- string customPath2 = prepareCustomPath("-2");
+ string customPath2 = prepareCustomPath();
using (var host = new CustomTestHeadlessGameHost())
{
@@ -185,7 +188,7 @@ namespace osu.Game.Tests.NonVisual
Assert.That(File.Exists(Path.Combine(customPath2, database_filename)));
// some files may have been left behind for whatever reason, but that's not what we're testing here.
- customPath = prepareCustomPath();
+ cleanupPath(customPath);
Assert.DoesNotThrow(() => osu.Migrate(customPath));
Assert.That(File.Exists(Path.Combine(customPath, database_filename)));
@@ -193,6 +196,8 @@ namespace osu.Game.Tests.NonVisual
finally
{
host.Exit();
+ cleanupPath(customPath);
+ cleanupPath(customPath2);
}
}
}
@@ -214,6 +219,7 @@ namespace osu.Game.Tests.NonVisual
finally
{
host.Exit();
+ cleanupPath(customPath);
}
}
}
@@ -243,6 +249,7 @@ namespace osu.Game.Tests.NonVisual
finally
{
host.Exit();
+ cleanupPath(customPath);
}
}
}
@@ -272,13 +279,14 @@ namespace osu.Game.Tests.NonVisual
finally
{
host.Exit();
+ cleanupPath(customPath);
}
}
}
- private static string getDefaultLocationFor(string testTypeName)
+ private static string getDefaultLocationFor(CustomTestHeadlessGameHost host)
{
- string path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, testTypeName);
+ string path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, host.Name);
if (Directory.Exists(path))
Directory.Delete(path, true);
@@ -286,14 +294,18 @@ namespace osu.Game.Tests.NonVisual
return path;
}
- private string prepareCustomPath(string suffix = "")
+ private static string prepareCustomPath() => Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, $"custom-path-{Guid.NewGuid()}");
+
+ private static void cleanupPath(string path)
{
- string path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, $"custom-path{suffix}");
-
- if (Directory.Exists(path))
- Directory.Delete(path, true);
-
- return path;
+ try
+ {
+ if (Directory.Exists(path))
+ Directory.Delete(path, true);
+ }
+ catch
+ {
+ }
}
public class CustomTestHeadlessGameHost : CleanRunHeadlessGameHost
@@ -303,7 +315,7 @@ namespace osu.Game.Tests.NonVisual
public CustomTestHeadlessGameHost([CallerMemberName] string callingMethodName = @"")
: base(callingMethodName: callingMethodName)
{
- string defaultStorageLocation = getDefaultLocationFor(callingMethodName);
+ string defaultStorageLocation = getDefaultLocationFor(this);
InitialStorage = new DesktopStorage(defaultStorageLocation, this);
InitialStorage.DeleteDirectory(string.Empty);
diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
index 55378043e6..8ba3d1a6c7 100644
--- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
+++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
@@ -16,7 +16,8 @@ namespace osu.Game.Tests.NonVisual.Filtering
{
private BeatmapInfo getExampleBeatmap() => new BeatmapInfo
{
- Ruleset = new RulesetInfo { OnlineID = 5 },
+ Ruleset = new RulesetInfo { OnlineID = 0 },
+ RulesetID = 0,
StarRating = 4.0d,
BaseDifficulty = new BeatmapDifficulty
{
diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
index 42305ccd81..0c49a18c8f 100644
--- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
+++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
@@ -45,8 +45,6 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
AddRepeatStep("add some users", () => Client.AddUser(new APIUser { Id = id++ }), 5);
checkPlayingUserCount(0);
- AddAssert("playlist item is available", () => Client.CurrentMatchPlayingItem.Value != null);
-
changeState(3, MultiplayerUserState.WaitingForLoad);
checkPlayingUserCount(3);
@@ -64,15 +62,13 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
AddStep("leave room", () => Client.LeaveRoom());
checkPlayingUserCount(0);
-
- AddAssert("playlist item is null", () => Client.CurrentMatchPlayingItem.Value == null);
}
[Test]
public void TestPlayingUsersUpdatedOnJoin()
{
AddStep("leave room", () => Client.LeaveRoom());
- AddUntilStep("wait for room part", () => Client.Room == null);
+ AddUntilStep("wait for room part", () => !RoomJoined);
AddStep("create room initially in gameplay", () =>
{
diff --git a/osu.Game.Tests/NonVisual/SessionStaticsTest.cs b/osu.Game.Tests/NonVisual/SessionStaticsTest.cs
index d5fd803986..cd02f15adf 100644
--- a/osu.Game.Tests/NonVisual/SessionStaticsTest.cs
+++ b/osu.Game.Tests/NonVisual/SessionStaticsTest.cs
@@ -1,9 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using NUnit.Framework;
using osu.Game.Configuration;
-using osu.Game.Input;
+using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Tests.NonVisual
{
@@ -11,37 +12,32 @@ namespace osu.Game.Tests.NonVisual
public class SessionStaticsTest
{
private SessionStatics sessionStatics;
- private IdleTracker sessionIdleTracker;
- [SetUp]
- public void SetUp()
+ [Test]
+ public void TestSessionStaticsReset()
{
sessionStatics = new SessionStatics();
- sessionIdleTracker = new GameIdleTracker(1000);
sessionStatics.SetValue(Static.LoginOverlayDisplayed, true);
sessionStatics.SetValue(Static.MutedAudioNotificationShownOnce, true);
sessionStatics.SetValue(Static.LowBatteryNotificationShownOnce, true);
sessionStatics.SetValue(Static.LastHoverSoundPlaybackTime, (double?)1d);
+ sessionStatics.SetValue(Static.SeasonalBackgrounds, new APISeasonalBackgrounds { EndDate = new DateTimeOffset(2022, 1, 1, 0, 0, 0, TimeSpan.Zero) });
- sessionIdleTracker.IsIdle.BindValueChanged(e =>
- {
- if (e.NewValue)
- sessionStatics.ResetValues();
- });
- }
+ Assert.IsFalse(sessionStatics.GetBindable(Static.LoginOverlayDisplayed).IsDefault);
+ Assert.IsFalse(sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce).IsDefault);
+ Assert.IsFalse(sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce).IsDefault);
+ Assert.IsFalse(sessionStatics.GetBindable(Static.LastHoverSoundPlaybackTime).IsDefault);
+ Assert.IsFalse(sessionStatics.GetBindable(Static.SeasonalBackgrounds).IsDefault);
- [Test]
- [Timeout(2000)]
- public void TestSessionStaticsReset()
- {
- sessionIdleTracker.IsIdle.BindValueChanged(e =>
- {
- Assert.IsTrue(sessionStatics.GetBindable(Static.LoginOverlayDisplayed).IsDefault);
- Assert.IsTrue(sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce).IsDefault);
- Assert.IsTrue(sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce).IsDefault);
- Assert.IsTrue(sessionStatics.GetBindable(Static.LastHoverSoundPlaybackTime).IsDefault);
- });
+ sessionStatics.ResetAfterInactivity();
+
+ Assert.IsTrue(sessionStatics.GetBindable(Static.LoginOverlayDisplayed).IsDefault);
+ Assert.IsTrue(sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce).IsDefault);
+ Assert.IsTrue(sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce).IsDefault);
+ // some statics should not reset despite inactivity.
+ Assert.IsFalse(sessionStatics.GetBindable(Static.LastHoverSoundPlaybackTime).IsDefault);
+ Assert.IsFalse(sessionStatics.GetBindable(Static.SeasonalBackgrounds).IsDefault);
}
}
}
diff --git a/osu.Game.Tests/NonVisual/TaskChainTest.cs b/osu.Game.Tests/NonVisual/TaskChainTest.cs
index d83eaafe20..3678279035 100644
--- a/osu.Game.Tests/NonVisual/TaskChainTest.cs
+++ b/osu.Game.Tests/NonVisual/TaskChainTest.cs
@@ -4,6 +4,7 @@
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
+using osu.Framework.Extensions;
using osu.Game.Utils;
namespace osu.Game.Tests.NonVisual
@@ -42,9 +43,9 @@ namespace osu.Game.Tests.NonVisual
await Task.WhenAll(task1.task, task2.task, task3.task);
- Assert.That(task1.task.Result, Is.EqualTo(1));
- Assert.That(task2.task.Result, Is.EqualTo(2));
- Assert.That(task3.task.Result, Is.EqualTo(3));
+ Assert.That(task1.task.GetResultSafely(), Is.EqualTo(1));
+ Assert.That(task2.task.GetResultSafely(), Is.EqualTo(2));
+ Assert.That(task3.task.GetResultSafely(), Is.EqualTo(3));
}
[Test]
@@ -68,9 +69,9 @@ namespace osu.Game.Tests.NonVisual
// Wait on both tasks.
await Task.WhenAll(task1.task, task3.task);
- Assert.That(task1.task.Result, Is.EqualTo(1));
+ Assert.That(task1.task.GetResultSafely(), Is.EqualTo(1));
Assert.That(task2.task.IsCompleted, Is.False);
- Assert.That(task3.task.Result, Is.EqualTo(2));
+ Assert.That(task3.task.GetResultSafely(), Is.EqualTo(2));
}
[Test]
diff --git a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs
new file mode 100644
index 0000000000..855de9b656
--- /dev/null
+++ b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs
@@ -0,0 +1,90 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Online.Chat;
+
+namespace osu.Game.Tests.Online.Chat
+{
+ [TestFixture]
+ public class MessageNotifierTest
+ {
+ [Test]
+ public void TestContainsUsernameMidlinePositive()
+ {
+ Assert.IsTrue(MessageNotifier.CheckContainsUsername("This is a test message", "Test"));
+ }
+
+ [Test]
+ public void TestContainsUsernameStartOfLinePositive()
+ {
+ Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test message", "Test"));
+ }
+
+ [Test]
+ public void TestContainsUsernameEndOfLinePositive()
+ {
+ Assert.IsTrue(MessageNotifier.CheckContainsUsername("This is a test", "Test"));
+ }
+
+ [Test]
+ public void TestContainsUsernameMidlineNegative()
+ {
+ Assert.IsFalse(MessageNotifier.CheckContainsUsername("This is a testmessage for notifications", "Test"));
+ }
+
+ [Test]
+ public void TestContainsUsernameStartOfLineNegative()
+ {
+ Assert.IsFalse(MessageNotifier.CheckContainsUsername("Testmessage", "Test"));
+ }
+
+ [Test]
+ public void TestContainsUsernameEndOfLineNegative()
+ {
+ Assert.IsFalse(MessageNotifier.CheckContainsUsername("This is a notificationtest", "Test"));
+ }
+
+ [Test]
+ public void TestContainsUsernameBetweenPunctuation()
+ {
+ Assert.IsTrue(MessageNotifier.CheckContainsUsername("Hello 'test'-message", "Test"));
+ }
+
+ [Test]
+ public void TestContainsUsernameUnicode()
+ {
+ Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test \u0460\u0460 message", "\u0460\u0460"));
+ }
+
+ [Test]
+ public void TestContainsUsernameUnicodeNegative()
+ {
+ Assert.IsFalse(MessageNotifier.CheckContainsUsername("Test ha\u0460\u0460o message", "\u0460\u0460"));
+ }
+
+ [Test]
+ public void TestContainsUsernameSpecialCharactersPositive()
+ {
+ Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test [#^-^#] message", "[#^-^#]"));
+ }
+
+ [Test]
+ public void TestContainsUsernameSpecialCharactersNegative()
+ {
+ Assert.IsFalse(MessageNotifier.CheckContainsUsername("Test pad[#^-^#]oru message", "[#^-^#]"));
+ }
+
+ [Test]
+ public void TestContainsUsernameAtSign()
+ {
+ Assert.IsTrue(MessageNotifier.CheckContainsUsername("@username hi", "username"));
+ }
+
+ [Test]
+ public void TestContainsUsernameColon()
+ {
+ Assert.IsTrue(MessageNotifier.CheckContainsUsername("username: hi", "username"));
+ }
+ }
+}
diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs
index 8378b33b3d..4b160e1d67 100644
--- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs
+++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs
@@ -13,7 +13,6 @@ using osu.Game.Online.Solo;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
@@ -94,7 +93,7 @@ namespace osu.Game.Tests.Online
[Test]
public void TestDeserialiseSubmittableScoreWithEmptyMods()
{
- var score = new SubmittableScore(new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo });
+ var score = new SubmittableScore(new ScoreInfo());
var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score));
@@ -106,7 +105,6 @@ namespace osu.Game.Tests.Online
{
var score = new SubmittableScore(new ScoreInfo
{
- Ruleset = new OsuRuleset().RulesetInfo,
Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } }
});
diff --git a/osu.Game.Tests/Online/TestMultiplayerMessagePackSerialization.cs b/osu.Game.Tests/Online/TestMultiplayerMessagePackSerialization.cs
index 1027b722d1..81475f2fbe 100644
--- a/osu.Game.Tests/Online/TestMultiplayerMessagePackSerialization.cs
+++ b/osu.Game.Tests/Online/TestMultiplayerMessagePackSerialization.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Tests.Online
}
[Test]
- public void TestSerialiseUnionFailsWithSingalR()
+ public void TestSerialiseUnionFailsWithSignalR()
{
var state = new TeamVersusUserState();
diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
index 24824b1e23..a7b431fb6e 100644
--- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
+++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
@@ -100,7 +100,7 @@ namespace osu.Game.Tests.Online
public void TestTrackerRespectsSoftDeleting()
{
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
- AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait());
+ AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID)));
@@ -114,18 +114,23 @@ namespace osu.Game.Tests.Online
public void TestTrackerRespectsChecksum()
{
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
+ AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
+ addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable);
AddStep("import altered beatmap", () =>
{
- beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait();
+ beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).WaitSafely();
});
- addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded);
+ addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
AddStep("recreate tracker", () => Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
{
SelectedItem = { BindTarget = selectedItem }
});
addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded);
+
+ AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely());
+ addAvailabilityCheckStep("locally available after re-import", BeatmapAvailability.LocallyAvailable);
}
private void addAvailabilityCheckStep(string description, Func expected)
diff --git a/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs b/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs
new file mode 100644
index 0000000000..d33081662d
--- /dev/null
+++ b/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs
@@ -0,0 +1,100 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Game.Online.Rooms;
+
+namespace osu.Game.Tests.OnlinePlay
+{
+ [TestFixture]
+ public class PlaylistExtensionsTest
+ {
+ [Test]
+ public void TestEmpty()
+ {
+ // mostly an extreme edge case, i.e. during room creation.
+ var items = Array.Empty();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(items.GetHistoricalItems(), Is.Empty);
+ Assert.That(items.GetCurrentItem(), Is.Null);
+ Assert.That(items.GetUpcomingItems(), Is.Empty);
+ });
+ }
+
+ [Test]
+ public void TestPlaylistItemsInOrder()
+ {
+ var items = new[]
+ {
+ new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1 },
+ new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2 },
+ new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 },
+ };
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(items.GetHistoricalItems(), Is.Empty);
+ Assert.That(items.GetCurrentItem(), Is.EqualTo(items[0]));
+ Assert.That(items.GetUpcomingItems(), Is.EquivalentTo(items));
+ });
+ }
+
+ [Test]
+ public void TestPlaylistItemsOutOfOrder()
+ {
+ var items = new[]
+ {
+ new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2 },
+ new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1 },
+ new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 },
+ };
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(items.GetHistoricalItems(), Is.Empty);
+ Assert.That(items.GetCurrentItem(), Is.EqualTo(items[1]));
+ Assert.That(items.GetUpcomingItems(), Is.EquivalentTo(new[] { items[1], items[0], items[2] }));
+ });
+ }
+
+ [Test]
+ public void TestExpiredPlaylistItemsSkipped()
+ {
+ var items = new[]
+ {
+ new PlaylistItem { ID = 1, BeatmapID = 1001, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) },
+ new PlaylistItem { ID = 2, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) },
+ new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 },
+ };
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(items.GetHistoricalItems(), Is.EquivalentTo(new[] { items[1], items[0] }));
+ Assert.That(items.GetCurrentItem(), Is.EqualTo(items[2]));
+ Assert.That(items.GetUpcomingItems(), Is.EquivalentTo(new[] { items[2] }));
+ });
+ }
+
+ [Test]
+ public void TestAllItemsExpired()
+ {
+ var items = new[]
+ {
+ new PlaylistItem { ID = 1, BeatmapID = 1001, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) },
+ new PlaylistItem { ID = 2, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) },
+ new PlaylistItem { ID = 3, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 57, 0, TimeSpan.Zero) },
+ };
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(items.GetHistoricalItems(), Is.EquivalentTo(new[] { items[1], items[0], items[2] }));
+ // if all items are expired, the last-played item is expected to be returned.
+ Assert.That(items.GetCurrentItem(), Is.EqualTo(items[2]));
+ Assert.That(items.GetUpcomingItems(), Is.Empty);
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs
index 440d5e701f..445394fc77 100644
--- a/osu.Game.Tests/Resources/TestResources.cs
+++ b/osu.Game.Tests/Resources/TestResources.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Text;
using System.Threading;
using NUnit.Framework;
@@ -12,8 +13,12 @@ using osu.Framework.IO.Stores;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
+using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
namespace osu.Game.Tests.Resources
{
@@ -137,5 +142,63 @@ namespace osu.Game.Tests.Resources
}
}
}
+
+ ///
+ /// Create a test score model.
+ ///
+ /// The ruleset for which the score was set against.
+ ///
+ public static ScoreInfo CreateTestScoreInfo(RulesetInfo ruleset = null) =>
+ CreateTestScoreInfo(CreateTestBeatmapSetInfo(1, new[] { ruleset ?? new OsuRuleset().RulesetInfo }).Beatmaps.First());
+
+ ///
+ /// Create a test score model.
+ ///
+ /// The beatmap for which the score was set against.
+ ///
+ public static ScoreInfo CreateTestScoreInfo(BeatmapInfo beatmap) => new ScoreInfo
+ {
+ User = new APIUser
+ {
+ Id = 2,
+ Username = "peppy",
+ CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
+ },
+ BeatmapInfo = beatmap,
+ Ruleset = beatmap.Ruleset,
+ RulesetID = beatmap.Ruleset.ID ?? 0,
+ Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() },
+ TotalScore = 2845370,
+ Accuracy = 0.95,
+ MaxCombo = 999,
+ Position = 1,
+ Rank = ScoreRank.S,
+ Date = DateTimeOffset.Now,
+ Statistics = new Dictionary
+ {
+ [HitResult.Miss] = 1,
+ [HitResult.Meh] = 50,
+ [HitResult.Ok] = 100,
+ [HitResult.Good] = 200,
+ [HitResult.Great] = 300,
+ [HitResult.Perfect] = 320,
+ [HitResult.SmallTickHit] = 50,
+ [HitResult.SmallTickMiss] = 25,
+ [HitResult.LargeTickHit] = 100,
+ [HitResult.LargeTickMiss] = 50,
+ [HitResult.SmallBonus] = 10,
+ [HitResult.SmallBonus] = 50
+ },
+ };
+
+ private class TestModHardRock : ModHardRock
+ {
+ public override double ScoreMultiplier => 1;
+ }
+
+ private class TestModDoubleTime : ModDoubleTime
+ {
+ public override double ScoreMultiplier => 1;
+ }
}
}
diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs
index c357fccd27..20439ac969 100644
--- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs
+++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -114,7 +115,7 @@ namespace osu.Game.Tests.Rulesets
public Sample Get(string name) => null;
- public Task GetAsync(string name) => null;
+ public Task GetAsync(string name, CancellationToken cancellationToken = default) => null;
public Stream GetStream(string name) => null;
diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
index 0dee0f89ea..bbc92b7817 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Tests.Scores.IO
Combo = 250,
User = new APIUser { Username = "Test user" },
Date = DateTimeOffset.Now,
- OnlineScoreID = 12345,
+ OnlineID = 12345,
};
var imported = await LoadScoreIntoOsu(osu, toImport);
@@ -52,7 +52,7 @@ namespace osu.Game.Tests.Scores.IO
Assert.AreEqual(toImport.Combo, imported.Combo);
Assert.AreEqual(toImport.User.Username, imported.User.Username);
Assert.AreEqual(toImport.Date, imported.Date);
- Assert.AreEqual(toImport.OnlineScoreID, imported.OnlineScoreID);
+ Assert.AreEqual(toImport.OnlineID, imported.OnlineID);
}
finally
{
@@ -163,12 +163,12 @@ namespace osu.Game.Tests.Scores.IO
{
var osu = LoadOsuIntoHost(host, true);
- await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader());
+ await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineID = 2 }, new TestArchiveReader());
var scoreManager = osu.Dependencies.Get();
// Note: A new score reference is used here since the import process mutates the original object to set an ID
- Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineScoreID = 2 }));
+ Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineID = 2 }));
}
finally
{
diff --git a/osu.Game.Tests/Scores/IO/TestScoreEquality.cs b/osu.Game.Tests/Scores/IO/TestScoreEquality.cs
index d1374eb6e5..42fcb3acab 100644
--- a/osu.Game.Tests/Scores/IO/TestScoreEquality.cs
+++ b/osu.Game.Tests/Scores/IO/TestScoreEquality.cs
@@ -44,24 +44,6 @@ namespace osu.Game.Tests.Scores.IO
Assert.That(score1, Is.EqualTo(score2));
}
- [Test]
- public void TestNonMatchingByHash()
- {
- ScoreInfo score1 = new ScoreInfo { Hash = "a" };
- ScoreInfo score2 = new ScoreInfo { Hash = "b" };
-
- Assert.That(score1, Is.Not.EqualTo(score2));
- }
-
- [Test]
- public void TestMatchingByHash()
- {
- ScoreInfo score1 = new ScoreInfo { Hash = "a" };
- ScoreInfo score2 = new ScoreInfo { Hash = "a" };
-
- Assert.That(score1, Is.EqualTo(score2));
- }
-
[Test]
public void TestNonMatchingByNull()
{
diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
index ecc9c92025..3f063264e0 100644
--- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
+++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
@@ -8,7 +8,9 @@ using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions;
using osu.Framework.Platform;
+using osu.Game.Database;
using osu.Game.IO;
using osu.Game.IO.Archives;
using osu.Game.Skinning;
@@ -163,32 +165,109 @@ namespace osu.Game.Tests.Skins.IO
assertCorrectMetadata(import2, "name 1 [my custom skin 2]", "author 1", osu);
});
+ [Test]
+ public Task TestExportThenImportDefaultSkin() => runSkinTest(osu =>
+ {
+ var skinManager = osu.Dependencies.Get();
+
+ skinManager.EnsureMutableSkin();
+
+ MemoryStream exportStream = new MemoryStream();
+
+ Guid originalSkinId = skinManager.CurrentSkinInfo.Value.ID;
+
+ skinManager.CurrentSkinInfo.Value.PerformRead(s =>
+ {
+ Assert.IsFalse(s.Protected);
+ Assert.AreEqual(typeof(DefaultSkin), s.CreateInstance(skinManager).GetType());
+
+ new LegacySkinExporter(osu.Dependencies.Get()).ExportModelTo(s, exportStream);
+
+ Assert.Greater(exportStream.Length, 0);
+ });
+
+ var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk"));
+
+ imported.GetResultSafely().PerformRead(s =>
+ {
+ Assert.IsFalse(s.Protected);
+ Assert.AreNotEqual(originalSkinId, s.ID);
+ Assert.AreEqual(typeof(DefaultSkin), s.CreateInstance(skinManager).GetType());
+ });
+
+ return Task.CompletedTask;
+ });
+
+ [Test]
+ public Task TestExportThenImportClassicSkin() => runSkinTest(osu =>
+ {
+ var skinManager = osu.Dependencies.Get();
+
+ skinManager.CurrentSkinInfo.Value = skinManager.DefaultLegacySkin.SkinInfo;
+
+ skinManager.EnsureMutableSkin();
+
+ MemoryStream exportStream = new MemoryStream();
+
+ Guid originalSkinId = skinManager.CurrentSkinInfo.Value.ID;
+
+ skinManager.CurrentSkinInfo.Value.PerformRead(s =>
+ {
+ Assert.IsFalse(s.Protected);
+ Assert.AreEqual(typeof(DefaultLegacySkin), s.CreateInstance(skinManager).GetType());
+
+ new LegacySkinExporter(osu.Dependencies.Get()).ExportModelTo(s, exportStream);
+
+ Assert.Greater(exportStream.Length, 0);
+ });
+
+ var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk"));
+
+ imported.GetResultSafely().PerformRead(s =>
+ {
+ Assert.IsFalse(s.Protected);
+ Assert.AreNotEqual(originalSkinId, s.ID);
+ Assert.AreEqual(typeof(DefaultLegacySkin), s.CreateInstance(skinManager).GetType());
+ });
+
+ return Task.CompletedTask;
+ });
+
#endregion
- private void assertCorrectMetadata(SkinInfo import1, string name, string creator, OsuGameBase osu)
+ private void assertCorrectMetadata(ILive import1, string name, string creator, OsuGameBase osu)
{
- Assert.That(import1.Name, Is.EqualTo(name));
- Assert.That(import1.Creator, Is.EqualTo(creator));
+ import1.PerformRead(i =>
+ {
+ Assert.That(i.Name, Is.EqualTo(name));
+ Assert.That(i.Creator, Is.EqualTo(creator));
- // for extra safety let's reconstruct the skin, reading from the skin.ini.
- var instance = import1.CreateInstance((IStorageResourceProvider)osu.Dependencies.Get(typeof(SkinManager)));
+ // for extra safety let's reconstruct the skin, reading from the skin.ini.
+ var instance = i.CreateInstance((IStorageResourceProvider)osu.Dependencies.Get(typeof(SkinManager)));
- Assert.That(instance.Configuration.SkinInfo.Name, Is.EqualTo(name));
- Assert.That(instance.Configuration.SkinInfo.Creator, Is.EqualTo(creator));
+ Assert.That(instance.Configuration.SkinInfo.Name, Is.EqualTo(name));
+ Assert.That(instance.Configuration.SkinInfo.Creator, Is.EqualTo(creator));
+ });
}
- private void assertImportedBoth(SkinInfo import1, SkinInfo import2)
+ private void assertImportedBoth(ILive import1, ILive import2)
{
- Assert.That(import2.ID, Is.Not.EqualTo(import1.ID));
- Assert.That(import2.Hash, Is.Not.EqualTo(import1.Hash));
- Assert.That(import2.Files.Select(f => f.FileInfoID), Is.Not.EquivalentTo(import1.Files.Select(f => f.FileInfoID)));
+ import1.PerformRead(i1 => import2.PerformRead(i2 =>
+ {
+ Assert.That(i2.ID, Is.Not.EqualTo(i1.ID));
+ Assert.That(i2.Hash, Is.Not.EqualTo(i1.Hash));
+ Assert.That(i2.Files.First(), Is.Not.EqualTo(i1.Files.First()));
+ }));
}
- private void assertImportedOnce(SkinInfo import1, SkinInfo import2)
+ private void assertImportedOnce(ILive import1, ILive import2)
{
- Assert.That(import2.ID, Is.EqualTo(import1.ID));
- Assert.That(import2.Hash, Is.EqualTo(import1.Hash));
- Assert.That(import2.Files.Select(f => f.FileInfoID), Is.EquivalentTo(import1.Files.Select(f => f.FileInfoID)));
+ import1.PerformRead(i1 => import2.PerformRead(i2 =>
+ {
+ Assert.That(i2.ID, Is.EqualTo(i1.ID));
+ Assert.That(i2.Hash, Is.EqualTo(i1.Hash));
+ Assert.That(i2.Files.First(), Is.EqualTo(i1.Files.First()));
+ }));
}
private MemoryStream createEmptyOsk()
@@ -241,7 +320,7 @@ namespace osu.Game.Tests.Skins.IO
private async Task runSkinTest(Func action, [CallerMemberName] string callingMethodName = @"")
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: callingMethodName))
{
try
{
@@ -255,10 +334,10 @@ namespace osu.Game.Tests.Skins.IO
}
}
- private async Task loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null)
+ private async Task> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null)
{
var skinManager = osu.Dependencies.Get();
- return (await skinManager.Import(archive)).Value;
+ return await skinManager.Import(archive);
}
}
}
diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
index 1d8b754837..c20ab84a68 100644
--- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
+++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
@@ -4,6 +4,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
+using osu.Framework.Extensions;
using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Beatmaps;
@@ -24,7 +25,7 @@ namespace osu.Game.Tests.Skins
[BackgroundDependencyLoader]
private void load()
{
- var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).Result;
+ var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).GetResultSafely();
beatmap = beatmaps.GetWorkingBeatmap(imported.Value.Beatmaps[0]);
beatmap.LoadTrack();
}
diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs
index 10f1ab31df..0271198049 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs
@@ -3,6 +3,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions;
using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.IO.Archives;
@@ -23,8 +24,8 @@ namespace osu.Game.Tests.Skins
[BackgroundDependencyLoader]
private void load()
{
- var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).Result;
- skin = skins.GetSkin(imported.Value);
+ var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).GetResultSafely();
+ skin = imported.PerformRead(skinInfo => skins.GetSkin(skinInfo));
}
[Test]
diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs
index ec16578b71..884e74346b 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs
@@ -5,15 +5,26 @@ using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Track;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Textures;
using osu.Framework.Screens;
using osu.Framework.Testing;
+using osu.Game.Beatmaps;
using osu.Game.Configuration;
+using osu.Game.Database;
+using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
+using osu.Game.Graphics.Sprites;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Screens;
using osu.Game.Screens.Backgrounds;
using osu.Game.Skinning;
+using osu.Game.Storyboards;
+using osu.Game.Storyboards.Drawables;
+using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual.Background
{
@@ -21,8 +32,7 @@ namespace osu.Game.Tests.Visual.Background
public class TestSceneBackgroundScreenDefault : OsuTestScene
{
private BackgroundScreenStack stack;
- private BackgroundScreenDefault screen;
-
+ private TestBackgroundScreenDefault screen;
private Graphics.Backgrounds.Background getCurrentBackground() => screen.ChildrenOfType().FirstOrDefault();
[Resolved]
@@ -35,10 +45,136 @@ namespace osu.Game.Tests.Visual.Background
public void SetUpSteps()
{
AddStep("create background stack", () => Child = stack = new BackgroundScreenStack());
- AddStep("push default screen", () => stack.Push(screen = new BackgroundScreenDefault(false)));
+ AddStep("push default screen", () => stack.Push(screen = new TestBackgroundScreenDefault()));
AddUntilStep("wait for screen to load", () => screen.IsCurrentScreen());
}
+ [Test]
+ public void TestBeatmapBackgroundTracksBeatmap()
+ {
+ setSupporter(true);
+ setSourceMode(BackgroundSource.Beatmap);
+
+ AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
+ AddAssert("background changed", () => screen.CheckLastLoadChange() == true);
+
+ Graphics.Backgrounds.Background last = null;
+
+ AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground));
+ AddStep("store background", () => last = getCurrentBackground());
+
+ AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
+
+ AddUntilStep("wait for beatmap background to change", () => screen.CheckLastLoadChange() == true);
+
+ AddUntilStep("background is new beatmap background", () => last != getCurrentBackground());
+ AddStep("store background", () => last = getCurrentBackground());
+
+ AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
+
+ AddUntilStep("wait for beatmap background to change", () => screen.CheckLastLoadChange() == true);
+ AddUntilStep("background is new beatmap background", () => last != getCurrentBackground());
+ }
+
+ [Test]
+ public void TestBeatmapBackgroundTracksBeatmapWhenSuspended()
+ {
+ setSupporter(true);
+ setSourceMode(BackgroundSource.Beatmap);
+
+ AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
+ AddAssert("background changed", () => screen.CheckLastLoadChange() == true);
+ AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground));
+
+ BackgroundScreenBeatmap nestedScreen = null;
+
+ // of note, this needs to be a type that doesn't match BackgroundScreenDefault else it is silently not pushed by the background stack.
+ AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value)));
+ AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen());
+ AddUntilStep("previous background hidden", () => !screen.IsAlive);
+
+ AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null);
+
+ AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
+
+ AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null);
+
+ AddStep("pop screen back to top level", () => screen.MakeCurrent());
+
+ AddAssert("top level background changed", () => screen.CheckLastLoadChange() == true);
+ }
+
+ [Test]
+ public void TestBeatmapBackgroundIgnoresNoChangeWhenSuspended()
+ {
+ BackgroundScreenBeatmap nestedScreen = null;
+ WorkingBeatmap originalWorking = null;
+
+ setSupporter(true);
+ setSourceMode(BackgroundSource.Beatmap);
+
+ AddStep("change beatmap", () => originalWorking = Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
+ AddAssert("background changed", () => screen.CheckLastLoadChange() == true);
+ AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground));
+
+ // of note, this needs to be a type that doesn't match BackgroundScreenDefault else it is silently not pushed by the background stack.
+ AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value)));
+ AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen());
+
+ // we're testing a case where scheduling may be used to avoid issues, so ensure the scheduler is no longer running.
+ AddUntilStep("wait for top level not alive", () => !screen.IsAlive);
+
+ AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
+ AddStep("change beatmap back", () => Beatmap.Value = originalWorking);
+
+ AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null);
+
+ AddStep("pop screen back to top level", () => screen.MakeCurrent());
+
+ AddStep("top level screen is current", () => screen.IsCurrentScreen());
+ AddAssert("top level background reused existing", () => screen.CheckLastLoadChange() == false);
+ }
+
+ [Test]
+ public void TestBeatmapBackgroundWithStoryboardClockAlwaysUsesCurrentTrack()
+ {
+ BackgroundScreenBeatmap nestedScreen = null;
+ WorkingBeatmap originalWorking = null;
+
+ setSupporter(true);
+ setSourceMode(BackgroundSource.BeatmapWithStoryboard);
+
+ AddStep("change beatmap", () => originalWorking = Beatmap.Value = createTestWorkingBeatmapWithStoryboard());
+ AddAssert("background changed", () => screen.CheckLastLoadChange() == true);
+ AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackgroundWithStoryboard));
+
+ AddStep("start music", () => MusicController.Play());
+ AddUntilStep("storyboard clock running", () => screen.ChildrenOfType().SingleOrDefault()?.Clock.IsRunning == true);
+
+ // of note, this needs to be a type that doesn't match BackgroundScreenDefault else it is silently not pushed by the background stack.
+ AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value)));
+ AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen());
+
+ // we're testing a case where scheduling may be used to avoid issues, so ensure the scheduler is no longer running.
+ AddUntilStep("wait for top level not alive", () => !screen.IsAlive);
+
+ AddStep("stop music", () => MusicController.Stop());
+ AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithStoryboard());
+ AddStep("change beatmap back", () => Beatmap.Value = originalWorking);
+ AddStep("restart music", () => MusicController.Play());
+
+ AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null);
+
+ AddStep("pop screen back to top level", () => screen.MakeCurrent());
+
+ AddStep("top level screen is current", () => screen.IsCurrentScreen());
+ AddAssert("top level background reused existing", () => screen.CheckLastLoadChange() == false);
+ AddUntilStep("storyboard clock running", () => screen.ChildrenOfType().Single().Clock.IsRunning);
+
+ AddStep("stop music", () => MusicController.Stop());
+ AddStep("restore default beatmap", () => Beatmap.SetDefault());
+ }
+
[Test]
public void TestBackgroundTypeSwitch()
{
@@ -77,36 +213,24 @@ namespace osu.Game.Tests.Visual.Background
[TestCase(BackgroundSource.Skin, typeof(SkinBackground))]
public void TestBackgroundDoesntReloadOnNoChange(BackgroundSource source, Type backgroundType)
{
- Graphics.Backgrounds.Background last = null;
-
setSourceMode(source);
setSupporter(true);
if (source == BackgroundSource.Skin)
setCustomSkin();
- AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground())?.GetType() == backgroundType);
+ AddUntilStep("wait for beatmap background to be loaded", () => (getCurrentBackground())?.GetType() == backgroundType);
AddAssert("next doesn't load new background", () => screen.Next() == false);
-
- // doesn't really need to be checked but might as well.
- AddWaitStep("wait a bit", 5);
- AddUntilStep("ensure same background instance", () => last == getCurrentBackground());
}
[Test]
public void TestBackgroundCyclingOnDefaultSkin([Values] bool supporter)
{
- Graphics.Backgrounds.Background last = null;
-
setSourceMode(BackgroundSource.Skin);
setSupporter(supporter);
setDefaultSkin();
- AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground())?.GetType() == typeof(Graphics.Backgrounds.Background));
+ AddUntilStep("wait for beatmap background to be loaded", () => (getCurrentBackground())?.GetType() == typeof(Graphics.Backgrounds.Background));
AddAssert("next cycles background", () => screen.Next());
-
- // doesn't really need to be checked but might as well.
- AddWaitStep("wait a bit", 5);
- AddUntilStep("ensure different background instance", () => last != getCurrentBackground());
}
private void setSourceMode(BackgroundSource source) =>
@@ -119,10 +243,92 @@ namespace osu.Game.Tests.Visual.Background
Id = API.LocalUser.Value.Id + 1,
});
+ private WorkingBeatmap createTestWorkingBeatmapWithUniqueBackground() => new UniqueBackgroundTestWorkingBeatmap(Audio);
+ private WorkingBeatmap createTestWorkingBeatmapWithStoryboard() => new TestWorkingBeatmapWithStoryboard(Audio);
+
+ private class TestBackgroundScreenDefault : BackgroundScreenDefault
+ {
+ private bool? lastLoadTriggerCausedChange;
+
+ public TestBackgroundScreenDefault()
+ : base(false)
+ {
+ }
+
+ public override bool Next()
+ {
+ bool didChange = base.Next();
+ lastLoadTriggerCausedChange = didChange;
+ return didChange;
+ }
+
+ public bool? CheckLastLoadChange()
+ {
+ bool? lastChange = lastLoadTriggerCausedChange;
+ lastLoadTriggerCausedChange = null;
+ return lastChange;
+ }
+ }
+
+ private class UniqueBackgroundTestWorkingBeatmap : TestWorkingBeatmap
+ {
+ public UniqueBackgroundTestWorkingBeatmap(AudioManager audioManager)
+ : base(new Beatmap(), null, audioManager)
+ {
+ }
+
+ protected override Texture GetBackground() => new Texture(1, 1);
+ }
+
+ private class TestWorkingBeatmapWithStoryboard : TestWorkingBeatmap
+ {
+ public TestWorkingBeatmapWithStoryboard(AudioManager audioManager)
+ : base(new Beatmap(), createStoryboard(), audioManager)
+ {
+ }
+
+ protected override Track GetBeatmapTrack() => new TrackVirtual(100000);
+
+ private static Storyboard createStoryboard()
+ {
+ var storyboard = new Storyboard();
+ storyboard.Layers.Last().Add(new TestStoryboardElement());
+ return storyboard;
+ }
+
+ private class TestStoryboardElement : IStoryboardElementWithDuration
+ {
+ public string Path => string.Empty;
+ public bool IsDrawable => true;
+ public double StartTime => double.MinValue;
+ public double EndTime => double.MaxValue;
+
+ public Drawable CreateDrawable() => new DrawableTestStoryboardElement();
+ }
+
+ private class DrawableTestStoryboardElement : OsuSpriteText
+ {
+ public override bool RemoveWhenNotAlive => false;
+
+ public DrawableTestStoryboardElement()
+ {
+ Anchor = Origin = Anchor.Centre;
+ Font = OsuFont.Default.With(size: 32);
+ Text = "(not started)";
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+ Text = Time.Current.ToString("N2");
+ }
+ }
+ }
+
private void setCustomSkin()
{
// feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin.
- AddStep("set custom skin", () => skins.CurrentSkinInfo.Value = new SkinInfo { ID = 5 });
+ AddStep("set custom skin", () => skins.CurrentSkinInfo.Value = new SkinInfo().ToLiveUnmanaged());
}
private void setDefaultSkin() => AddStep("set default skin", () => skins.CurrentSkinInfo.SetDefault());
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
index 194341d1ab..5b2cf877ba 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
@@ -7,18 +7,19 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
+using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Input.States;
using osu.Framework.Platform;
using osu.Framework.Screens;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
-using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
@@ -28,7 +29,6 @@ using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
-using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources;
using osuTK;
using osuTK.Graphics;
@@ -36,7 +36,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Background
{
[TestFixture]
- public class TestSceneUserDimBackgrounds : OsuManualInputManagerTestScene
+ public class TestSceneUserDimBackgrounds : ScreenTestScene
{
private DummySongSelect songSelect;
private TestPlayerLoader playerLoader;
@@ -51,19 +51,17 @@ namespace osu.Game.Tests.Visual.Background
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new OsuConfigManager(LocalStorage));
- manager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
+ manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
Beatmap.SetDefault();
}
- [SetUp]
- public virtual void SetUp() => Schedule(() =>
+ public override void SetUpSteps()
{
- var stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both };
- Child = stack;
+ base.SetUpSteps();
- stack.Push(songSelect = new DummySongSelect());
- });
+ AddStep("push song select", () => Stack.Push(songSelect = new DummySongSelect()));
+ }
///
/// User settings should always be ignored on song select screen.
@@ -229,12 +227,7 @@ namespace osu.Game.Tests.Visual.Background
FadeAccessibleResults results = null;
- AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo
- {
- User = new APIUser { Username = "osu!" },
- BeatmapInfo = new TestBeatmap(Ruleset.Value).BeatmapInfo,
- Ruleset = Ruleset.Value,
- })));
+ AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(TestResources.CreateTestScoreInfo())));
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
@@ -329,7 +322,7 @@ namespace osu.Game.Tests.Visual.Background
public bool IsBackgroundUndimmed() => background.CurrentColour == Color4.White;
- public bool IsUserBlurApplied() => background.CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR);
+ public bool IsUserBlurApplied() => Precision.AlmostEquals(background.CurrentBlur, new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR), 0.1f);
public bool IsUserBlurDisabled() => background.CurrentBlur == new Vector2(0);
@@ -337,9 +330,9 @@ namespace osu.Game.Tests.Visual.Background
public bool IsBackgroundVisible() => background.CurrentAlpha == 1;
- public bool IsBackgroundBlur() => background.CurrentBlur == new Vector2(BACKGROUND_BLUR);
+ public bool IsBackgroundBlur() => Precision.AlmostEquals(background.CurrentBlur, new Vector2(BACKGROUND_BLUR), 0.1f);
- public bool CheckBackgroundBlur(Vector2 expected) => background.CurrentBlur == expected;
+ public bool CheckBackgroundBlur(Vector2 expected) => Precision.AlmostEquals(background.CurrentBlur, expected, 0.1f);
///
/// Make sure every time a screen gets pushed, the background doesn't get replaced
diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs
index 5effc1f215..7b5e1f4ec7 100644
--- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs
+++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs
@@ -11,17 +11,18 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Drawables;
using osu.Game.Beatmaps.Drawables.Cards;
+using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osuTK;
-using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
namespace osu.Game.Tests.Visual.Beatmaps
{
- public class TestSceneBeatmapCard : OsuTestScene
+ public class TestSceneBeatmapCard : OsuManualInputManagerTestScene
{
///
/// All cards on this scene use a common online ID to ensure that map download, preview tracks, etc. can be tested manually with online sources.
@@ -95,6 +96,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
var longName = CreateAPIBeatmapSet(Ruleset.Value);
longName.Title = longName.TitleUnicode = "this track has an incredibly and implausibly long title";
longName.Artist = longName.ArtistUnicode = "and this artist! who would have thunk it. it's really such a long name.";
+ longName.Source = "wow. even the source field has an impossibly long string in it. this really takes the cake, doesn't it?";
longName.HasExplicitContent = true;
longName.TrackId = 444;
@@ -227,7 +229,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
- Child = new FillFlowContainer
+ Child = new ReverseChildIDFillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
@@ -248,6 +250,41 @@ namespace osu.Game.Tests.Visual.Beatmaps
}
[Test]
- public void TestNormal() => createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo));
+ public void TestNormal()
+ {
+ createTestCase(beatmapSetInfo => new BeatmapCardNormal(beatmapSetInfo));
+ }
+
+ [Test]
+ public void TestExtra()
+ {
+ createTestCase(beatmapSetInfo => new BeatmapCardExtra(beatmapSetInfo));
+ }
+
+ [Test]
+ public void TestHoverState()
+ {
+ AddStep("create cards", () => Child = createContent(OverlayColourScheme.Blue, s => new BeatmapCardNormal(s)));
+
+ AddStep("Hover card", () => InputManager.MoveMouseTo(firstCard()));
+ AddWaitStep("wait for potential state change", 5);
+ AddAssert("card is not expanded", () => !firstCard().Expanded.Value);
+
+ AddStep("Hover spectrum display", () => InputManager.MoveMouseTo(firstCard().ChildrenOfType().Single()));
+ AddUntilStep("card is expanded", () => firstCard().Expanded.Value);
+
+ AddStep("Hover difficulty content", () => InputManager.MoveMouseTo(firstCard().ChildrenOfType().Single()));
+ AddWaitStep("wait for potential state change", 5);
+ AddAssert("card is still expanded", () => firstCard().Expanded.Value);
+
+ AddStep("Hover main content again", () => InputManager.MoveMouseTo(firstCard()));
+ AddWaitStep("wait for potential state change", 5);
+ AddAssert("card is still expanded", () => firstCard().Expanded.Value);
+
+ AddStep("Hover away", () => InputManager.MoveMouseTo(this.ChildrenOfType().Last()));
+ AddUntilStep("card is not expanded", () => !firstCard().Expanded.Value);
+
+ BeatmapCardNormal firstCard() => this.ChildrenOfType().First();
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDifficultyList.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDifficultyList.cs
index aec75884d6..e6fb4372ff 100644
--- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDifficultyList.cs
+++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDifficultyList.cs
@@ -6,7 +6,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.Drawables.Cards;
-using osu.Game.Graphics;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
@@ -18,7 +17,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load()
{
var beatmapSet = new APIBeatmapSet
{
diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs
index 28218ea220..d2b0f7324b 100644
--- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs
+++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs
@@ -4,6 +4,7 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
@@ -38,7 +39,7 @@ namespace osu.Game.Tests.Visual.Collections
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default));
- beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
+ beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
base.Content.AddRange(new Drawable[]
{
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs
index c81a1abfbc..516305079b 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs
@@ -5,11 +5,13 @@ using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Overlays.Dialog;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit;
using osu.Game.Tests.Beatmaps.IO;
@@ -31,7 +33,7 @@ namespace osu.Game.Tests.Visual.Editing
public override void SetUpSteps()
{
- AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result);
+ AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely());
base.SetUpSteps();
}
@@ -89,6 +91,7 @@ namespace osu.Game.Tests.Visual.Editing
confirmEditingBeatmap(() => targetDifficulty);
AddAssert("no objects selected", () => !EditorBeatmap.SelectedHitObjects.Any());
+ AddUntilStep("wait for drawable ruleset", () => Editor.ChildrenOfType().SingleOrDefault()?.IsLoaded == true);
AddStep("paste object", () => Editor.Paste());
if (sameRuleset)
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
index 92c8131568..db20d3c7ba 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Editing
public void TestCreateNewBeatmap()
{
AddStep("save beatmap", () => Editor.Save());
- AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0);
+ AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.IsManaged);
AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == false);
}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs
index 3aff74a0a8..e41f8372b4 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs
@@ -4,6 +4,7 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Edit;
@@ -85,11 +86,17 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1);
+ Slider slider = null;
+ AddStep("retrieve slider", () => slider = (Slider)EditorBeatmap.HitObjects.Single());
AddAssert("path matches", () =>
{
- var path = ((Slider)EditorBeatmap.HitObjects.Single()).Path;
+ var path = slider.Path;
return path.ControlPoints.Count == 2 && path.ControlPoints.SequenceEqual(addedObject.Path.ControlPoints);
});
+
+ // see `HitObject.control_point_leniency`.
+ AddAssert("sample control point has correct time", () => Precision.AlmostEquals(slider.SampleControlPoint.Time, slider.GetEndTime(), 1));
+ AddAssert("difficulty control point has correct time", () => slider.DifficultyControlPoint.Time == slider.StartTime);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs
index 160af47a6d..6d48ef3ba7 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs
@@ -5,10 +5,12 @@ using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Edit;
@@ -37,13 +39,14 @@ namespace osu.Game.Tests.Visual.Editing
public override void SetUpSteps()
{
- AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game).Result);
+ AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game).GetResultSafely());
base.SetUpSteps();
}
protected override void LoadEditor()
{
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0));
+ SelectedMods.Value = new[] { new ModCinema() };
base.LoadEditor();
}
@@ -67,6 +70,7 @@ namespace osu.Game.Tests.Visual.Editing
var background = this.ChildrenOfType().Single();
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
});
+ AddAssert("no mods selected", () => SelectedMods.Value.Count == 0);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs
index c5ab3974a4..e10ef57a25 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected OsuConfigManager Config { get; private set; }
[BackgroundDependencyLoader]
- private void load(RulesetStore rulesets)
+ private void load()
{
Dependencies.Cache(Config = new OsuConfigManager(LocalStorage));
Config.GetBindable(OsuSetting.DimLevel).Value = 1.0;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
index 7398527f57..c5f56cae9e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
@@ -12,6 +12,7 @@ using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
+using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
@@ -41,7 +42,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestEmptyLegacyBeatmapSkinFallsBack()
{
- CreateSkinTest(SkinInfo.Default, () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null));
+ CreateSkinTest(DefaultSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null));
AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded));
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value));
}
@@ -52,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("setup skins", () =>
{
- skinManager.CurrentSkinInfo.Value = gameCurrentSkin;
+ skinManager.CurrentSkinInfo.Value = gameCurrentSkin.ToLiveUnmanaged();
currentBeatmapSkin = getBeatmapSkin();
});
});
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
index 745932315c..fa27e1abdd 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
@@ -26,6 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("total number of results == 1", () =>
{
var score = new ScoreInfo();
+
((FailPlayer)Player).ScoreProcessor.PopulateScore(score);
return score.Statistics.Values.Sum() == 1;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs
index f5f17a0bc1..e03c8d7561 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs
@@ -85,11 +85,12 @@ namespace osu.Game.Tests.Visual.Gameplay
loopGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1);
var target = addEventToLoop ? loopGroup : sprite.TimelineGroup;
- target.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
+ double targetTime = addEventToLoop ? 20000 : 0;
+ target.Alpha.Add(Easing.None, targetTime + firstStoryboardEvent, targetTime + firstStoryboardEvent + 500, 0, 1);
// these should be ignored due to being in the future.
sprite.TimelineGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1);
- loopGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1);
+ loopGroup.Alpha.Add(Easing.None, 38000, 40000, 0, 1);
storyboard.GetLayer("Background").Add(sprite);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
index 06eaa726c9..958d617d63 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
@@ -251,7 +251,12 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestMutedNotificationMuteButton()
{
- addVolumeSteps("mute button", () => volumeOverlay.IsMuted.Value = true, () => !volumeOverlay.IsMuted.Value);
+ addVolumeSteps("mute button", () =>
+ {
+ // Importantly, in the case the volume is muted but the user has a volume level set, it should be retained.
+ audioManager.VolumeTrack.Value = 0.5f;
+ volumeOverlay.IsMuted.Value = true;
+ }, () => !volumeOverlay.IsMuted.Value && audioManager.VolumeTrack.Value == 0.5f);
}
///
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
index 324a132120..cf5aadde6d 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
@@ -3,6 +3,7 @@
using System;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Screens;
@@ -36,7 +37,9 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override bool HasCustomSteps => true;
- protected override TestPlayer CreatePlayer(Ruleset ruleset) => new NonImportingPlayer(false);
+ protected override TestPlayer CreatePlayer(Ruleset ruleset) => new FakeImportingPlayer(false);
+
+ protected new FakeImportingPlayer Player => (FakeImportingPlayer)base.Player;
protected override Ruleset CreatePlayerRuleset() => createCustomRuleset?.Invoke() ?? new OsuRuleset();
@@ -54,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestNoSubmissionOnResultsWithNoToken()
{
- prepareTokenResponse(false);
+ prepareTestAPI(false);
createPlayerTest();
@@ -74,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestSubmissionOnResults()
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest();
@@ -93,7 +96,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestSubmissionForDifferentRuleset()
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest(createRuleset: () => new TaikoRuleset());
@@ -113,7 +116,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestSubmissionForConvertedBeatmap()
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest(createRuleset: () => new ManiaRuleset(), createBeatmap: _ => createTestBeatmap(new OsuRuleset().RulesetInfo));
@@ -133,7 +136,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestNoSubmissionOnExitWithNoToken()
{
- prepareTokenResponse(false);
+ prepareTestAPI(false);
createPlayerTest();
@@ -150,7 +153,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestNoSubmissionOnEmptyFail()
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest(true);
@@ -165,7 +168,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestSubmissionOnFail()
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest(true);
@@ -182,7 +185,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestNoSubmissionOnEmptyExit()
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest();
@@ -195,7 +198,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestSubmissionOnExit()
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest();
@@ -207,10 +210,29 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false);
}
+ [Test]
+ public void TestSubmissionOnExitDuringImport()
+ {
+ prepareTestAPI(true);
+
+ createPlayerTest();
+ AddStep("block imports", () => Player.AllowImportCompletion.Wait());
+
+ AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
+
+ addFakeHit();
+
+ AddUntilStep("wait for import to start", () => Player.ScoreImportStarted);
+
+ AddStep("exit", () => Player.Exit());
+ AddStep("allow import to proceed", () => Player.AllowImportCompletion.Release(1));
+ AddUntilStep("ensure submission", () => Player.SubmittedScore != null && Player.ImportedScore != null);
+ }
+
[Test]
public void TestNoSubmissionOnLocalBeatmap()
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest(false, r =>
{
@@ -231,7 +253,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestCase(10)]
public void TestNoSubmissionOnCustomRuleset(int? rulesetId)
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest(false, createRuleset: () => new OsuRuleset { RulesetInfo = { OnlineID = rulesetId ?? -1 } });
@@ -253,7 +275,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}));
}
- private void prepareTokenResponse(bool validToken)
+ private void prepareTestAPI(bool validToken)
{
AddStep("Prepare test API", () =>
{
@@ -267,6 +289,31 @@ namespace osu.Game.Tests.Visual.Gameplay
else
tokenRequest.TriggerFailure(new APIException("something went wrong!", null));
return true;
+
+ case SubmitSoloScoreRequest submissionRequest:
+ if (validToken)
+ {
+ var requestScore = submissionRequest.Score;
+
+ submissionRequest.TriggerSuccess(new MultiplayerScore
+ {
+ ID = 1234,
+ User = dummyAPI.LocalUser.Value,
+ Rank = requestScore.Rank,
+ TotalScore = requestScore.TotalScore,
+ Accuracy = requestScore.Accuracy,
+ MaxCombo = requestScore.MaxCombo,
+ Mods = requestScore.Mods,
+ Statistics = requestScore.Statistics,
+ Passed = requestScore.Passed,
+ EndedAt = DateTimeOffset.Now,
+ Position = 1
+ });
+
+ return true;
+ }
+
+ break;
}
return false;
@@ -288,15 +335,26 @@ namespace osu.Game.Tests.Visual.Gameplay
});
}
- private class NonImportingPlayer : TestPlayer
+ protected class FakeImportingPlayer : TestPlayer
{
- public NonImportingPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false)
+ public bool ScoreImportStarted { get; set; }
+ public SemaphoreSlim AllowImportCompletion { get; }
+ public Score ImportedScore { get; private set; }
+
+ public FakeImportingPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false)
: base(allowPause, showResults, pauseOnFocusLost)
{
+ AllowImportCompletion = new SemaphoreSlim(1);
}
- protected override Task ImportScore(Score score)
+ protected override async Task ImportScore(Score score)
{
+ ScoreImportStarted = true;
+
+ await AllowImportCompletion.WaitAsync().ConfigureAwait(false);
+
+ 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.
@@ -311,8 +369,7 @@ namespace osu.Game.Tests.Visual.Gameplay
// 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, importing is disabled in this test.
- return Task.CompletedTask;
+ // For the above reasons, actual importing is disabled in this test.
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
index f47fae33ca..3168c4b94e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
@@ -9,6 +9,7 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Scoring;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Extensions;
using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
@@ -135,7 +136,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded);
- AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)).Result);
+ AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)).GetResultSafely());
AddUntilStep("state is available", () => downloadButton.State.Value == DownloadState.LocallyAvailable);
@@ -164,7 +165,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private ScoreInfo getScoreInfo(bool replayAvailable)
{
- return new APIScoreInfo
+ return new APIScore
{
OnlineID = 2553163309,
RulesetID = 0,
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
index dcc193669b..e6361a15d7 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
@@ -43,83 +43,88 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached]
private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty());
- [SetUp]
- public void SetUp() => Schedule(() =>
+ [SetUpSteps]
+ public void SetUpSteps()
{
- replay = new Replay();
+ AddStep("Reset recorder state", cleanUpState);
- Add(new GridContainer
+ AddStep("Setup containers", () =>
{
- RelativeSizeAxes = Axes.Both,
- Content = new[]
+ replay = new Replay();
+
+ Add(new GridContainer
{
- new Drawable[]
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
{
- recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
+ new Drawable[]
{
- Recorder = recorder = new TestReplayRecorder(new Score
+ recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
- Replay = replay,
- ScoreInfo = { BeatmapInfo = gameplayState.Beatmap.BeatmapInfo }
- })
- {
- ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
- },
- Child = new Container
- {
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
+ Recorder = recorder = new TestReplayRecorder(new Score
{
- new Box
+ Replay = replay,
+ ScoreInfo = { BeatmapInfo = gameplayState.Beatmap.BeatmapInfo }
+ })
+ {
+ ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
+ },
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
{
- 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(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
+ 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[]
{
- ReplayInputHandler = new TestFramedReplayInputHandler(replay)
+ playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
- GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
- },
- Child = new Container
- {
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
+ ReplayInputHandler = new TestFramedReplayInputHandler(replay)
{
- new Box
+ GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
+ },
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
{
- Colour = Color4.DarkBlue,
- RelativeSizeAxes = Axes.Both,
- },
- new OsuSpriteText
- {
- Text = "Playback",
- Scale = new Vector2(3),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- },
- new TestInputConsumer()
- }
- },
+ new Box
+ {
+ Colour = Color4.DarkBlue,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Text = "Playback",
+ Scale = new Vector2(3),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new TestInputConsumer()
+ }
+ },
+ }
}
}
- }
+ });
});
- });
+ }
[Test]
public void TestBasic()
@@ -184,7 +189,14 @@ namespace osu.Game.Tests.Visual.Gameplay
[TearDownSteps]
public void TearDown()
{
- AddStep("stop recorder", () => recorder.Expire());
+ AddStep("stop recorder", cleanUpState);
+ }
+
+ private void cleanUpState()
+ {
+ // Ensure previous recorder is disposed else it may affect the global playing state of `SpectatorClient`.
+ recorder?.RemoveAndDisposeImmediately();
+ recorder = null;
}
public class TestFramedReplayInputHandler : FramedReplayInputHandler
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
deleted file mode 100644
index 3f7155f1e2..0000000000
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
+++ /dev/null
@@ -1,228 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using System.Collections.Generic;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Input.Bindings;
-using osu.Framework.Input.Events;
-using osu.Framework.Input.StateChanges;
-using osu.Game.Beatmaps;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Replays;
-using osu.Game.Rulesets;
-using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Osu;
-using osu.Game.Rulesets.Replays;
-using osu.Game.Rulesets.UI;
-using osu.Game.Scoring;
-using osu.Game.Screens.Play;
-using osu.Game.Tests.Visual.UserInterface;
-using osuTK;
-using osuTK.Graphics;
-
-namespace osu.Game.Tests.Visual.Gameplay
-{
- public class TestSceneReplayRecording : OsuTestScene
- {
- private readonly TestRulesetInputManager playbackManager;
-
- private readonly TestRulesetInputManager recordingManager;
-
- [Cached]
- private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty());
-
- public TestSceneReplayRecording()
- {
- Replay replay = new Replay();
-
- Add(new GridContainer
- {
- RelativeSizeAxes = Axes.Both,
- Content = new[]
- {
- new Drawable[]
- {
- recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
- {
- Recorder = new TestReplayRecorder(new Score
- {
- Replay = replay,
- ScoreInfo = { BeatmapInfo = gameplayState.Beatmap.BeatmapInfo }
- })
- {
- ScreenSpaceToGamefield = pos => recordingManager?.ToLocalSpace(pos) ?? Vector2.Zero,
- },
- 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 TestConsumer()
- }
- },
- }
- },
- new Drawable[]
- {
- playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
- {
- ReplayInputHandler = new TestFramedReplayInputHandler(replay)
- {
- GamefieldToScreenSpace = pos => playbackManager?.ToScreenSpace(pos) ?? Vector2.Zero,
- },
- 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 TestConsumer()
- }
- },
- }
- }
- }
- });
- }
-
- protected override void Update()
- {
- base.Update();
-
- playbackManager.ReplayInputHandler.SetFrameFromTime(Time.Current - 500);
- }
- }
-
- public class TestFramedReplayInputHandler : FramedReplayInputHandler
- {
- public TestFramedReplayInputHandler(Replay replay)
- : base(replay)
- {
- }
-
- public override void CollectPendingInputs(List inputs)
- {
- inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) });
- inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() });
- }
- }
-
- public class TestConsumer : CompositeDrawable, IKeyBindingHandler
- {
- public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos);
-
- private readonly Box box;
-
- public TestConsumer()
- {
- Size = new Vector2(30);
-
- Origin = Anchor.Centre;
-
- InternalChildren = new Drawable[]
- {
- box = new Box
- {
- Colour = Color4.Black,
- RelativeSizeAxes = Axes.Both,
- },
- };
- }
-
- protected override bool OnMouseMove(MouseMoveEvent e)
- {
- Position = e.MousePosition;
- return base.OnMouseMove(e);
- }
-
- public bool OnPressed(KeyBindingPressEvent e)
- {
- if (e.Repeat)
- return false;
-
- box.Colour = Color4.White;
- return true;
- }
-
- public void OnReleased(KeyBindingReleaseEvent e)
- {
- box.Colour = Color4.Black;
- }
- }
-
- public class TestRulesetInputManager : RulesetInputManager
- {
- public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
- : base(ruleset, variant, unique)
- {
- }
-
- protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
- => new TestKeyBindingContainer();
-
- internal class TestKeyBindingContainer : KeyBindingContainer
- {
- public override IEnumerable DefaultKeyBindings => new[]
- {
- new KeyBinding(InputKey.MouseLeft, TestAction.Down),
- };
- }
- }
-
- public class TestReplayFrame : ReplayFrame
- {
- public Vector2 Position;
-
- public List Actions = new List();
-
- public TestReplayFrame(double time, Vector2 position, params TestAction[] actions)
- : base(time)
- {
- Position = position;
- Actions.AddRange(actions);
- }
- }
-
- public enum TestAction
- {
- Down,
- }
-
- internal class TestReplayRecorder : ReplayRecorder
- {
- public TestReplayRecorder(Score target)
- : base(target)
- {
- }
-
- protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) =>
- new TestReplayFrame(Time.Current, mousePosition, actions.ToArray());
- }
-}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
index a0b27755b7..a0602e21b9 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
@@ -2,12 +2,10 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
-using osu.Game.Skinning;
using osu.Game.Skinning.Editor;
namespace osu.Game.Tests.Visual.Gameplay
@@ -16,9 +14,6 @@ namespace osu.Game.Tests.Visual.Gameplay
{
private SkinEditor skinEditor;
- [Resolved]
- private SkinManager skinManager { get; set; }
-
protected override bool Autoplay => true;
[SetUpSteps]
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
index 1c5a05dd1d..bd1fe050af 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
@@ -4,6 +4,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Testing;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD;
@@ -24,5 +25,17 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("reset combo", () => scoreProcessor.Combo.Value = 0);
}
+
+ [Test]
+ public void TestLegacyComboCounterHiddenByRulesetImplementation()
+ {
+ AddToggleStep("toggle legacy hidden by ruleset", visible =>
+ {
+ foreach (var legacyCounter in this.ChildrenOfType())
+ legacyCounter.HiddenByRulesetImplementation = visible;
+ });
+
+ AddRepeatStep("increase combo", () => scoreProcessor.Combo.Value++, 10);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs
index 723e35ed55..3074a91dc6 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs
@@ -10,7 +10,6 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
-using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
@@ -36,9 +35,6 @@ namespace osu.Game.Tests.Visual.Gameplay
private Drawable hideTarget => hudOverlay.KeyCounter;
private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First();
- [Resolved]
- private OsuConfigManager config { get; set; }
-
[Test]
public void TestComboCounterIncrementing()
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs
index 9fadbe02bd..242eca0bbc 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs
@@ -4,6 +4,7 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Framework.Testing;
@@ -60,7 +61,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("import beatmap", () =>
{
- importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result;
+ importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely();
importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineID ?? -1;
});
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
index 5fbccd54c8..f7e9a1fe16 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private TestReplayRecorder recorder;
- private readonly ManualClock manualClock = new ManualClock();
+ private ManualClock manualClock;
private OsuSpriteText latencyDisplay;
@@ -66,113 +66,121 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached]
private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty());
- [SetUp]
- public void SetUp() => Schedule(() =>
+ [SetUpSteps]
+ public void SetUpSteps()
{
- replay = new Replay();
+ AddStep("Reset recorder state", cleanUpState);
- users.BindTo(spectatorClient.PlayingUsers);
- users.BindCollectionChanged((obj, args) =>
+ AddStep("Setup containers", () =>
{
- switch (args.Action)
+ replay = new Replay();
+ manualClock = new ManualClock();
+
+ spectatorClient.OnNewFrames += onNewFrames;
+
+ users.BindTo(spectatorClient.PlayingUsers);
+ users.BindCollectionChanged((obj, args) =>
{
- case NotifyCollectionChangedAction.Add:
- Debug.Assert(args.NewItems != null);
-
- foreach (int user in args.NewItems)
- {
- if (user == api.LocalUser.Value.Id)
- spectatorClient.WatchUser(user);
- }
-
- break;
-
- case NotifyCollectionChangedAction.Remove:
- Debug.Assert(args.OldItems != null);
-
- foreach (int user in args.OldItems)
- {
- if (user == api.LocalUser.Value.Id)
- spectatorClient.StopWatchingUser(user);
- }
-
- break;
- }
- }, true);
-
- spectatorClient.OnNewFrames += onNewFrames;
-
- Add(new GridContainer
- {
- RelativeSizeAxes = Axes.Both,
- Content = new[]
- {
- new Drawable[]
+ switch (args.Action)
{
- recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
+ case NotifyCollectionChangedAction.Add:
+ Debug.Assert(args.NewItems != null);
+
+ foreach (int user in args.NewItems)
+ {
+ if (user == api.LocalUser.Value.Id)
+ spectatorClient.WatchUser(user);
+ }
+
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ Debug.Assert(args.OldItems != null);
+
+ foreach (int user in args.OldItems)
+ {
+ if (user == api.LocalUser.Value.Id)
+ spectatorClient.StopWatchingUser(user);
+ }
+
+ break;
+ }
+ }, true);
+
+ Children = new Drawable[]
+ {
+ new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
{
- Recorder = recorder = new TestReplayRecorder
+ new Drawable[]
{
- ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
- },
- Child = new Container
- {
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
+ recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
- new Box
+ Recorder = recorder = new TestReplayRecorder
+ {
+ ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
+ },
+ Child = new Container
{
- Colour = Color4.Brown,
RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Brown,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Text = "Sending",
+ Scale = new Vector2(3),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new TestInputConsumer()
+ }
},
- new OsuSpriteText
- {
- Text = "Sending",
- Scale = new Vector2(3),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- },
- new TestInputConsumer()
}
},
+ new Drawable[]
+ {
+ playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
+ {
+ Clock = new FramedClock(manualClock),
+ ReplayInputHandler = replayHandler = 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 = "Receiving",
+ Scale = new Vector2(3),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new TestInputConsumer()
+ }
+ },
+ }
+ }
}
},
- new Drawable[]
- {
- playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
- {
- Clock = new FramedClock(manualClock),
- ReplayInputHandler = replayHandler = 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 = "Receiving",
- Scale = new Vector2(3),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- },
- new TestInputConsumer()
- }
- },
- }
- }
- }
+ latencyDisplay = new OsuSpriteText()
+ };
});
-
- Add(latencyDisplay = new OsuSpriteText());
- });
+ }
private void onNewFrames(int userId, FrameDataBundle frames)
{
@@ -189,6 +197,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestBasic()
{
+ AddStep("Wait for user input", () => { });
}
private double latency = SpectatorClient.TIME_BETWEEN_SENDS;
@@ -232,11 +241,15 @@ namespace osu.Game.Tests.Visual.Gameplay
[TearDownSteps]
public void TearDown()
{
- AddStep("stop recorder", () =>
- {
- recorder.Expire();
- spectatorClient.OnNewFrames -= onNewFrames;
- });
+ AddStep("stop recorder", cleanUpState);
+ }
+
+ private void cleanUpState()
+ {
+ // Ensure previous recorder is disposed else it may affect the global playing state of `SpectatorClient`.
+ recorder?.RemoveAndDisposeImmediately();
+ recorder = null;
+ spectatorClient.OnNewFrames -= onNewFrames;
}
public class TestFramedReplayInputHandler : FramedReplayInputHandler
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs
index a718a98aa6..95603b5c04 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void checkForFirstSamplePlayback()
{
- AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded);
+ AddAssert("storyboard loaded", () => Player.Beatmap.Value.Storyboard != null);
AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying));
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
index 48a97d54f7..69798dcb82 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
@@ -53,8 +53,8 @@ namespace osu.Game.Tests.Visual.Gameplay
CreateTest(null);
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space));
+ AddAssert("player is no longer current screen", () => !Player.IsCurrentScreen());
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
- AddUntilStep("time less than storyboard duration", () => Player.GameplayClockContainer.GameplayClock.CurrentTime < currentStoryboardDuration);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
index 55e453c3d3..ee9363fa12 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Extensions;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Database;
@@ -33,11 +34,11 @@ namespace osu.Game.Tests.Visual.Menus
Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null;
// ensure we have at least two beatmaps available to identify the direction the music controller navigated to.
- AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()).Wait(), 5);
+ AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()).WaitSafely(), 5);
AddStep("import beatmap with track", () =>
{
- var setWithTrack = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).Result;
+ var setWithTrack = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).GetResultSafely();
Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Value.Beatmaps.First());
});
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
index 57d60cea9e..c65595d82e 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Menus
private TestToolbar toolbar;
[Resolved]
- private RulesetStore rulesets { get; set; }
+ private IRulesetStore rulesets { get; set; }
[SetUp]
public void SetUp() => Schedule(() =>
diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs
index 357db16e2c..c4d7bd7e6a 100644
--- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs
@@ -5,7 +5,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
-using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Extensions;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
@@ -20,7 +20,6 @@ using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Screens.Play;
using osu.Game.Tests.Resources;
-using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
@@ -31,17 +30,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
protected BeatmapInfo InitialBeatmap { get; private set; }
protected BeatmapInfo OtherBeatmap { get; private set; }
- protected IScreen CurrentScreen => multiplayerScreenStack.CurrentScreen;
- protected IScreen CurrentSubScreen => multiplayerScreenStack.MultiplayerScreen.CurrentSubScreen;
+ protected IScreen CurrentScreen => multiplayerComponents.CurrentScreen;
+ protected IScreen CurrentSubScreen => multiplayerComponents.MultiplayerScreen.CurrentSubScreen;
private BeatmapManager beatmaps;
private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
- private TestMultiplayerScreenStack multiplayerScreenStack;
+ private TestMultiplayerComponents multiplayerComponents;
- protected TestMultiplayerClient Client => multiplayerScreenStack.Client;
- protected TestMultiplayerRoomManager RoomManager => multiplayerScreenStack.RoomManager;
+ protected TestMultiplayerClient Client => multiplayerComponents.Client;
[Cached(typeof(UserLookupCache))]
private UserLookupCache lookupCache = new TestUserLookupCache();
@@ -59,18 +57,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("import beatmap", () =>
{
- beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
+ beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
InitialBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0);
OtherBeatmap = importedSet.Beatmaps.Last(b => b.RulesetID == 0);
});
- AddStep("load multiplayer", () => LoadScreen(multiplayerScreenStack = new TestMultiplayerScreenStack()));
- AddUntilStep("wait for multiplayer to load", () => multiplayerScreenStack.IsLoaded);
+ AddStep("load multiplayer", () => LoadScreen(multiplayerComponents = new TestMultiplayerComponents()));
+ AddUntilStep("wait for multiplayer to load", () => multiplayerComponents.IsLoaded);
AddUntilStep("wait for lounge to load", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
- AddUntilStep("wait for lounge", () => multiplayerScreenStack.ChildrenOfType().SingleOrDefault()?.IsLoaded == true);
- AddStep("open room", () => multiplayerScreenStack.ChildrenOfType().Single().Open(new Room
+ AddUntilStep("wait for lounge", () => multiplayerComponents.ChildrenOfType().SingleOrDefault()?.IsLoaded == true);
+ AddStep("open room", () => multiplayerComponents.ChildrenOfType().Single().Open(new Room
{
Name = { Value = "Test Room" },
QueueMode = { Value = Mode },
@@ -87,13 +85,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
AddWaitStep("wait for transition", 2);
- AddStep("create room", () =>
- {
- InputManager.MoveMouseTo(this.ChildrenOfType().Single());
- InputManager.Click(MouseButton.Left);
- });
+ ClickButtonWhenEnabled();
- AddUntilStep("wait for join", () => RoomManager.RoomJoined);
+ AddUntilStep("wait for join", () => Client.RoomJoined);
}
[Test]
@@ -105,24 +99,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
protected void RunGameplay()
{
AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle);
- clickReadyButton();
+ ClickButtonWhenEnabled();
AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready);
- clickReadyButton();
+ ClickButtonWhenEnabled();
- AddUntilStep("wait for player", () => multiplayerScreenStack.CurrentScreen is Player player && player.IsLoaded);
- AddStep("exit player", () => multiplayerScreenStack.MultiplayerScreen.MakeCurrent());
- }
-
- private void clickReadyButton()
- {
- AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType().Single().ChildrenOfType