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/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 f18c5e76f9..b1dfcab416 100644
--- a/README.md
+++ b/README.md
@@ -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.
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 1532d4ce23..b296c114e9 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,11 +51,11 @@
-
-
+
+
-
+
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/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index b234207848..cd3fb7eb61 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -10,14 +10,11 @@ using System.Runtime.Versioning;
using System.Threading.Tasks;
using Microsoft.Win32;
using osu.Desktop.Security;
-using osu.Desktop.Overlays;
using osu.Framework.Platform;
using osu.Game;
using osu.Desktop.Updater;
using osu.Framework;
using osu.Framework.Logging;
-using osu.Framework.Screens;
-using osu.Game.Screens.Menu;
using osu.Game.Updater;
using osu.Desktop.Windows;
using osu.Framework.Threading;
@@ -27,13 +24,9 @@ namespace osu.Desktop
{
internal class OsuGameDesktop : OsuGame
{
- private readonly bool noVersionOverlay;
- private VersionManager versionManager;
-
public OsuGameDesktop(string[] args = null)
: base(args)
{
- noVersionOverlay = args?.Any(a => a == "--no-version-overlay") ?? false;
}
public override StableStorage GetStorageForStableInstall()
@@ -114,9 +107,6 @@ namespace osu.Desktop
{
base.LoadComplete();
- if (!noVersionOverlay)
- LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add);
-
LoadComponentAsync(new DiscordRichPresence(), Add);
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
@@ -125,23 +115,6 @@ namespace osu.Desktop
LoadComponentAsync(new ElevatedPrivilegesChecker(), Add);
}
- protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen)
- {
- base.ScreenChanged(lastScreen, newScreen);
-
- switch (newScreen)
- {
- case IntroScreen _:
- case MainMenu _:
- versionManager?.Show();
- break;
-
- default:
- versionManager?.Hide();
- break;
- }
- }
-
public override void SetHost(GameHost host)
{
base.SetHost(host);
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/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/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index be1885cfa6..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";
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs
index d4c2c0f0af..e345e03c96 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs
@@ -29,7 +29,13 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
protected CatchSelectionBlueprintTestScene()
{
- EditorBeatmap = new EditorBeatmap(new CatchBeatmap()) { Difficulty = { CircleSize = 0 } };
+ EditorBeatmap = new EditorBeatmap(new CatchBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new CatchRuleset().RulesetInfo,
+ }
+ }) { Difficulty = { CircleSize = 0 } };
EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint
{
BeatLength = 100
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
index f552c3c27b..1014158fc1 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 },
+ Difficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 },
Ruleset = ruleset
}
};
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
index e89a95ae37..96ac5c4bf2 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
+ Difficulty = new BeatmapDifficulty { CircleSize = 6 },
Ruleset = ruleset
}
};
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
index 1ff31697b8..0a4ef49e19 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
+ Difficulty = new BeatmapDifficulty { CircleSize = 6 },
Ruleset = ruleset
}
};
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
index 459b8e1f6f..4b8fede369 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
@@ -35,12 +35,12 @@ namespace osu.Game.Rulesets.Catch.Tests
HitObjects = new List { new Fruit() },
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty(),
+ Difficulty = new BeatmapDifficulty(),
Metadata = new BeatmapMetadata
{
Artist = @"Unknown",
Title = @"You're breathtaking",
- AuthorString = @"Everyone",
+ Author = { Username = @"Everyone" },
},
Ruleset = new CatchRuleset().RulesetInfo
},
@@ -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/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
index 163fee49fb..a5b44dc605 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Catch.Tests
BeatmapInfo =
{
Ruleset = ruleset,
- BaseDifficulty = new BeatmapDifficulty { CircleSize = 3.6f }
+ Difficulty = new BeatmapDifficulty { CircleSize = 3.6f }
}
};
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs
index 269e783899..4601234669 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty { CircleSize = 5, SliderMultiplier = 2 },
+ Difficulty = new BeatmapDifficulty { CircleSize = 5, SliderMultiplier = 2 },
Ruleset = ruleset
},
HitObjects = new List
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/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/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/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
index 5ccb191a9b..50be13c4e0 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
@@ -29,7 +29,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
private ScrollingTestContainer.TestScrollingInfo scrollingInfo = new ScrollingTestContainer.TestScrollingInfo();
[Cached(typeof(EditorBeatmap))]
- private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition()));
+ private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition())
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new ManiaRuleset().RulesetInfo
+ }
+ });
private readonly ManiaBeatSnapGrid beatSnapGrid;
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs
index a30e09cd29..5dd7c23ab6 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs
@@ -31,10 +31,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
{
AddStep("setup compose screen", () =>
{
- var editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 }))
+ var editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })
{
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
- };
+ });
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs
index 01d80881fa..9788dfe844 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs
@@ -203,10 +203,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
{
InternalChildren = new Drawable[]
{
- EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 }))
+ EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })
{
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }
- },
+ }),
Composer = new ManiaHitObjectComposer(new ManiaRuleset())
};
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/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
index 36765d61bf..9c987efc60 100644
--- a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
+++ b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
@@ -4,11 +4,14 @@ Version: 2.5
[Mania]
Keys: 4
ColumnLineWidth: 3,1,3,1,1
-Hit0: mania/hit0
-Hit50: mania/hit50
-Hit100: mania/hit100
-Hit200: mania/hit200
-Hit300: mania/hit300
-Hit300g: mania/hit300g
+// some skins found in the wild had configuration keys where the @2x suffix was included in the values.
+// the expected compatibility behaviour is that the presence of the @2x suffix shouldn't change anything
+// if @2x assets are present.
+Hit0: mania/hit0@2x
+Hit50: mania/hit50@2x
+Hit100: mania/hit100@2x
+Hit200: mania/hit200@2x
+Hit300: mania/hit300@2x
+Hit300g: mania/hit300g@2x
StageLeft: mania/stage-left
StageRight: mania/stage-right
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
index 75a5495078..d033676ec7 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
@@ -5,8 +5,10 @@ using System;
using System.Linq;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
+using osu.Framework.Testing;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Scoring;
+using osu.Game.Rulesets.Mania.Skinning.Legacy;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
@@ -23,15 +25,24 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
if (hitWindows.IsHitResultAllowed(result))
{
- AddStep("Show " + result.GetDescription(), () => SetContents(_ =>
- new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement())
- {
- Type = result
- }, null)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- }));
+ AddStep("Show " + result.GetDescription(), () =>
+ {
+ SetContents(_ =>
+ new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement())
+ {
+ Type = result
+ }, null)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ });
+
+ // for test purposes, undo the Y adjustment related to the `ScorePosition` legacy positioning config value
+ // (see `LegacyManiaJudgementPiece.load()`).
+ // this prevents the judgements showing somewhere below or above the bounding box of the judgement.
+ foreach (var legacyPiece in this.ChildrenOfType())
+ legacyPiece.Y = 0;
+ });
}
}
}
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/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
index 4387bc6b3b..f973cb5ed3 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
@@ -264,7 +264,7 @@ namespace osu.Game.Rulesets.Mania.Tests
},
BeatmapInfo =
{
- BaseDifficulty = new BeatmapDifficulty
+ Difficulty = new BeatmapDifficulty
{
SliderTickRate = 4,
OverallDifficulty = 10,
@@ -306,7 +306,7 @@ namespace osu.Game.Rulesets.Mania.Tests
},
BeatmapInfo =
{
- BaseDifficulty = new BeatmapDifficulty { SliderTickRate = tick_rate },
+ Difficulty = new BeatmapDifficulty { SliderTickRate = tick_rate },
Ruleset = new ManiaRuleset().RulesetInfo
},
};
@@ -383,7 +383,7 @@ namespace osu.Game.Rulesets.Mania.Tests
},
BeatmapInfo =
{
- BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 },
+ Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
Ruleset = new ManiaRuleset().RulesetInfo
},
};
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/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 9d0aaec2ba..47e0e6d7b1 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
public static int GetColumnCountForNonConvert(BeatmapInfo beatmapInfo)
{
- double roundedCircleSize = Math.Round(beatmapInfo.BaseDifficulty.CircleSize);
+ double roundedCircleSize = Math.Round(beatmapInfo.Difficulty.CircleSize);
return (int)Math.Max(1, roundedCircleSize);
}
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/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/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/Editor/Checks/CheckTooShortSpinnersTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs
index 787807a8ea..1f3d4297f1 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs
@@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
var beatmap = new Beatmap
{
HitObjects = hitObjects,
- BeatmapInfo = new BeatmapInfo { BaseDifficulty = new BeatmapDifficulty(beatmapDifficulty) }
+ BeatmapInfo = new BeatmapInfo { Difficulty = new BeatmapDifficulty(beatmapDifficulty) }
};
return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
index ef43c3a696..c770e2d96f 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
@@ -40,7 +40,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
public TestSceneOsuDistanceSnapGrid()
{
- editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo
+ }
+ });
}
[SetUp]
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/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
index db8546c71b..9d06ff5801 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
{
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty
+ Difficulty = new BeatmapDifficulty
{
CircleSize = 8
}
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/TestSceneHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
index 8cf29ddfbf..4e17c4c363 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
+ Difficulty = new BeatmapDifficulty { CircleSize = 6 },
Ruleset = ruleset
}
};
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs
index ef05bcd320..5e92bac986 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 10 },
+ Difficulty = new BeatmapDifficulty { OverallDifficulty = 10 },
Ruleset = ruleset
}
};
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
index f3392724ec..2368cc7365 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
@@ -358,7 +358,7 @@ namespace osu.Game.Rulesets.Osu.Tests
},
BeatmapInfo =
{
- BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 },
+ Difficulty = new BeatmapDifficulty { SliderTickRate = 3 },
Ruleset = new OsuRuleset().RulesetInfo
},
});
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.Tests/TestSceneStartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs
index 2d43e1b95e..53fa3624b8 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs
@@ -364,7 +364,7 @@ namespace osu.Game.Rulesets.Osu.Tests
HitObjects = hitObjects,
BeatmapInfo =
{
- BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 },
+ Difficulty = new BeatmapDifficulty { SliderTickRate = 3 },
Ruleset = new OsuRuleset().RulesetInfo
},
});
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 79314344ea..52487c29a1 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 f4c451f80b..430bb36372 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -25,14 +25,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private int countMeh;
private int countMiss;
- private int effectiveMissCount;
+ private double effectiveMissCount;
public OsuPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
: base(ruleset, attributes, score)
{
}
- 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);
+ aimValue *= 0.97 * Math.Pow(1 - Math.Pow(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,20 +136,17 @@ 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;
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
- speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
+ speedValue *= 0.97 * Math.Pow(1 - Math.Pow(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,7 +216,7 @@ 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);
@@ -236,11 +225,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
- flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
+ flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow(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) +
@@ -254,7 +241,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return flashlightValue;
}
- private int calculateEffectiveMissCount()
+ private double calculateEffectiveMissCount()
{
// Guess the number of misses + slider breaks from combo
double comboBasedMissCount = 0.0;
@@ -266,12 +253,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo);
}
- // Clamp misscount since it's derived from combo and can be higher than total hits and that breaks some calculations
+ // Clamp miss count since it's derived from combo and can be higher than total hits and that breaks some calculations
comboBasedMissCount = Math.Min(comboBasedMissCount, totalHits);
- return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount));
+ return Math.Max(countMiss, 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/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/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/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs
index db4a6eb50b..6c76da7925 100644
--- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs
+++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs
@@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.Statistics
pointGrid.Content = points;
- if (score.HitEvents == null || score.HitEvents.Count == 0)
+ if (score.HitEvents.Count == 0)
return;
// Todo: This should probably not be done like this.
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/DrawableTaikoRulesetTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs
index 4bdb85ba60..f5e7304c12 100644
--- a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs
@@ -32,12 +32,12 @@ namespace osu.Game.Rulesets.Taiko.Tests
HitObjects = new List { new Hit { Type = HitType.Centre } },
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty(),
+ Difficulty = new BeatmapDifficulty(),
Metadata = new BeatmapMetadata
{
Artist = @"Unknown",
Title = @"Sample Beatmap",
- AuthorString = @"peppy",
+ Author = { Username = @"peppy" },
},
Ruleset = new TaikoRuleset().RulesetInfo
},
diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs
index 626537053a..55eb2fa66b 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs
@@ -40,10 +40,10 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor
{
InternalChildren = new Drawable[]
{
- EditorBeatmap = new EditorBeatmap(new TaikoBeatmap())
+ EditorBeatmap = new EditorBeatmap(new TaikoBeatmap
{
BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo }
- },
+ }),
new TaikoHitObjectComposer(new TaikoRuleset())
};
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/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs
index b976735223..920a7cd1a1 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs
@@ -158,12 +158,12 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
HitObjects = new List { new Hit { Type = HitType.Centre } },
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty(),
+ Difficulty = new BeatmapDifficulty(),
Metadata = new BeatmapMetadata
{
Artist = "Unknown",
Title = "Sample Beatmap",
- AuthorString = "Craftplacer",
+ Author = { Username = "Craftplacer" },
},
Ruleset = new TaikoRuleset().RulesetInfo
},
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.Tests/TestSceneDrumRollJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs
new file mode 100644
index 0000000000..060c3c9443
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs
@@ -0,0 +1,36 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ public class TestSceneDrumRollJudgements : TestSceneTaikoPlayer
+ {
+ [Test]
+ public void TestStrongDrumRollFullyJudgedOnKilled()
+ {
+ AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted.Value);
+ AddAssert("all judgements are misses", () => Player.Results.All(r => r.Type == r.Judgement.MinResult));
+ }
+
+ protected override bool Autoplay => false;
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
+ {
+ BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo },
+ HitObjects =
+ {
+ new DrumRoll
+ {
+ StartTime = 1000,
+ Duration = 1000,
+ IsStrong = true
+ }
+ }
+ };
+ }
+}
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/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index 9b2e9fedc5..613874b7d6 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -191,6 +191,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
protected override Beatmap CreateBeatmap() => new TaikoBeatmap();
+ // Important to note that this is subclassing a realm object.
+ // Realm doesn't allow this, but for now this can work since we aren't (in theory?) persisting this to the database.
+ // It is only used during beatmap conversion and processing.
internal class TaikoMultiplierAppliedDifficulty : BeatmapDifficulty
{
public TaikoMultiplierAppliedDifficulty(IBeatmapDifficultyInfo difficulty)
@@ -205,6 +208,8 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
#region Overrides of BeatmapDifficulty
+ public override BeatmapDifficulty Clone() => new TaikoMultiplierAppliedDifficulty(this);
+
public override void CopyTo(BeatmapDifficulty other)
{
base.CopyTo(other);
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/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
index 521189d36c..b84db513f7 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
@@ -197,6 +197,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
}
+ public override void OnKilled()
+ {
+ base.OnKilled();
+
+ if (Time.Current > ParentHitObject.HitObject.GetEndTime() && !Judged)
+ ApplyResult(r => r.Type = r.Judgement.MinResult);
+ }
+
public override bool OnPressed(KeyBindingPressEvent e) => false;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
index dc2ed200a1..e24923e482 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
@@ -5,6 +5,7 @@ using System;
using JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Skinning.Default;
using osu.Game.Skinning;
@@ -52,6 +53,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = r.Judgement.MaxResult);
}
+ public override void OnKilled()
+ {
+ base.OnKilled();
+
+ if (Time.Current > HitObject.GetEndTime() && !Judged)
+ ApplyResult(r => r.Type = r.Judgement.MinResult);
+ }
+
protected override void UpdateHitStateTransforms(ArmedState state)
{
switch (state)
@@ -92,6 +101,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
}
+ public override void OnKilled()
+ {
+ base.OnKilled();
+
+ if (Time.Current > ParentHitObject.HitObject.GetEndTime() && !Judged)
+ ApplyResult(r => r.Type = r.Judgement.MinResult);
+ }
+
public override bool OnPressed(KeyBindingPressEvent e) => false;
}
}
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.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/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 677aaf6f78..6ec14e6351 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -117,7 +117,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(string.Empty, metadata.Source);
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags);
Assert.AreEqual(557821, beatmapInfo.OnlineID);
- Assert.AreEqual(241526, beatmapInfo.BeatmapSet.OnlineID);
+ Assert.AreEqual(241526, beatmapInfo.BeatmapSet?.OnlineID);
}
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
index 81d89359e0..3d4b05b52b 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
@@ -95,7 +95,6 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.That(decodedAfterEncode, Is.Not.Null);
Assert.That(decodedAfterEncode.ScoreInfo.User.Username, Is.EqualTo(scoreInfo.User.Username));
- Assert.That(decodedAfterEncode.ScoreInfo.BeatmapInfoID, Is.EqualTo(scoreInfo.BeatmapInfoID));
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));
@@ -129,7 +128,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
MD5Hash = md5Hash,
Ruleset = new OsuRuleset().RulesetInfo,
- BaseDifficulty = new BeatmapDifficulty()
+ Difficulty = new BeatmapDifficulty()
}
});
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
index bfd6ff0314..06ed638e0a 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
var beatmap = decodeAsJson(normal);
var meta = beatmap.BeatmapInfo.Metadata;
- Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineID);
+ Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet?.OnlineID);
Assert.AreEqual("Soleily", meta.Artist);
Assert.AreEqual("Soleily", meta.ArtistUnicode);
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
diff --git a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs
new file mode 100644
index 0000000000..44f6943871
--- /dev/null
+++ b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs
@@ -0,0 +1,86 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Game.Beatmaps;
+using osu.Game.Database;
+using osu.Game.Tests.Database;
+using osu.Game.Tests.Resources;
+
+namespace osu.Game.Tests.Beatmaps.IO
+{
+ public static class BeatmapImportHelper
+ {
+ public static async Task LoadQuickOszIntoOsu(OsuGameBase osu)
+ {
+ string temp = TestResources.GetQuickTestBeatmapForImport();
+
+ var manager = osu.Dependencies.Get();
+
+ var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false);
+
+ Debug.Assert(importedSet != null);
+
+ ensureLoaded(osu);
+
+ waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
+
+ return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID);
+ }
+
+ public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
+ {
+ string temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
+
+ var manager = osu.Dependencies.Get();
+
+ var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false);
+
+ Debug.Assert(importedSet != null);
+
+ ensureLoaded(osu);
+
+ waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
+
+ return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID);
+ }
+
+ private static void ensureLoaded(OsuGameBase osu, int timeout = 60000)
+ {
+ var realmContextFactory = osu.Dependencies.Get();
+
+ using (var realm = realmContextFactory.CreateContext())
+ BeatmapImporterTests.EnsureLoaded(realm, timeout);
+
+ // TODO: add back some extra checks outside of the realm ones?
+ // var set = queryBeatmapSets().First();
+ // foreach (BeatmapInfo b in set.Beatmaps)
+ // Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID));
+ // Assert.IsTrue(set.Beatmaps.Count > 0);
+ // var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap;
+ // Assert.IsTrue(beatmap?.HitObjects.Any() == true);
+ // beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap;
+ // Assert.IsTrue(beatmap?.HitObjects.Any() == true);
+ // beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap;
+ // Assert.IsTrue(beatmap?.HitObjects.Any() == true);
+ // beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap;
+ // Assert.IsTrue(beatmap?.HitObjects.Any() == true);
+ }
+
+ private static void waitForOrAssert(Func result, string failureMessage, int timeout = 60000)
+ {
+ Task task = Task.Run(() =>
+ {
+ while (!result()) Thread.Sleep(200);
+ });
+
+ Assert.IsTrue(task.Wait(timeout), failureMessage);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
deleted file mode 100644
index 6d0d5702e9..0000000000
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ /dev/null
@@ -1,1104 +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.IO;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using NUnit.Framework;
-using osu.Framework.Platform;
-using osu.Game.IPC;
-using osu.Framework.Allocation;
-using osu.Framework.Extensions;
-using osu.Framework.Extensions.ObjectExtensions;
-using osu.Framework.Logging;
-using osu.Game.Beatmaps;
-using osu.Game.Database;
-using osu.Game.Extensions;
-using osu.Game.IO;
-using osu.Game.Online.API.Requests.Responses;
-using osu.Game.Overlays.Notifications;
-using osu.Game.Rulesets.Osu;
-using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Scoring;
-using osu.Game.Tests.Resources;
-using osu.Game.Tests.Scores.IO;
-using SharpCompress.Archives;
-using SharpCompress.Archives.Zip;
-using SharpCompress.Common;
-using SharpCompress.Writers.Zip;
-using FileInfo = System.IO.FileInfo;
-
-namespace osu.Game.Tests.Beatmaps.IO
-{
- [TestFixture]
- public class ImportBeatmapTest : ImportTest
- {
- [Test]
- 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)))
- {
- try
- {
- await LoadOszIntoOsu(LoadOsuIntoHost(host));
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- 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)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- var imported = await LoadOszIntoOsu(osu);
-
- deleteBeatmapSet(imported, osu);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- 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)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- string tempPath = TestResources.GetTestBeatmapForImport();
-
- var manager = osu.Dependencies.Get();
-
- ILive importedSet;
-
- using (var stream = File.OpenRead(tempPath))
- {
- importedSet = await manager.Import(new ImportTask(stream, Path.GetFileName(tempPath)));
- ensureLoaded(osu);
- }
-
- Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing");
- File.Delete(tempPath);
-
- var imported = manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID);
-
- deleteBeatmapSet(imported, osu);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- 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)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- var imported = await LoadOszIntoOsu(osu);
- var importedSecondTime = await LoadOszIntoOsu(osu);
-
- // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
- Assert.IsTrue(imported.ID == importedSecondTime.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
-
- checkBeatmapSetCount(osu, 1);
- checkSingleReferencedFileCount(osu, 18);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportThenImportWithReZip()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- string temp = TestResources.GetTestBeatmapForImport();
-
- string extractedFolder = $"{temp}_extracted";
- Directory.CreateDirectory(extractedFolder);
-
- try
- {
- var imported = await LoadOszIntoOsu(osu);
-
- string hashBefore = hashFile(temp);
-
- using (var zip = ZipArchive.Open(temp))
- zip.WriteToDirectory(extractedFolder);
-
- using (var zip = ZipArchive.Create())
- {
- zip.AddAllFromDirectory(extractedFolder);
- zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
- }
-
- // zip files differ because different compression or encoder.
- Assert.AreNotEqual(hashBefore, hashFile(temp));
-
- var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp));
-
- ensureLoaded(osu);
-
- // but contents doesn't, so existing should still be used.
- Assert.IsTrue(imported.ID == importedSecondTime.Value.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Value.Beatmaps.First().ID);
- }
- finally
- {
- Directory.Delete(extractedFolder, true);
- }
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportThenImportWithChangedHashedFile()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- string temp = TestResources.GetTestBeatmapForImport();
-
- string extractedFolder = $"{temp}_extracted";
- Directory.CreateDirectory(extractedFolder);
-
- try
- {
- var imported = await LoadOszIntoOsu(osu);
-
- await createScoreForBeatmap(osu, imported.Beatmaps.First());
-
- using (var zip = ZipArchive.Open(temp))
- zip.WriteToDirectory(extractedFolder);
-
- // arbitrary write to hashed file
- // this triggers the special BeatmapManager.PreImport deletion/replacement flow.
- using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.osu").First()).AppendText())
- await sw.WriteLineAsync("// changed");
-
- using (var zip = ZipArchive.Create())
- {
- zip.AddAllFromDirectory(extractedFolder);
- zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
- }
-
- var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp));
-
- ensureLoaded(osu);
-
- // check the newly "imported" beatmap is not the original.
- Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID);
- }
- finally
- {
- Directory.Delete(extractedFolder, true);
- }
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- [Ignore("intentionally broken by import optimisations")]
- public async Task TestImportThenImportWithChangedFile()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- string temp = TestResources.GetTestBeatmapForImport();
-
- string extractedFolder = $"{temp}_extracted";
- Directory.CreateDirectory(extractedFolder);
-
- try
- {
- var imported = await LoadOszIntoOsu(osu);
-
- using (var zip = ZipArchive.Open(temp))
- zip.WriteToDirectory(extractedFolder);
-
- // arbitrary write to non-hashed file
- using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.mp3").First()).AppendText())
- await sw.WriteLineAsync("text");
-
- using (var zip = ZipArchive.Create())
- {
- zip.AddAllFromDirectory(extractedFolder);
- zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
- }
-
- var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp));
-
- ensureLoaded(osu);
-
- // check the newly "imported" beatmap is not the original.
- Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID);
- }
- finally
- {
- Directory.Delete(extractedFolder, true);
- }
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportThenImportWithDifferentFilename()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- string temp = TestResources.GetTestBeatmapForImport();
-
- string extractedFolder = $"{temp}_extracted";
- Directory.CreateDirectory(extractedFolder);
-
- try
- {
- var imported = await LoadOszIntoOsu(osu);
-
- using (var zip = ZipArchive.Open(temp))
- zip.WriteToDirectory(extractedFolder);
-
- // change filename
- var firstFile = new FileInfo(Directory.GetFiles(extractedFolder).First());
- firstFile.MoveTo(Path.Combine(firstFile.DirectoryName.AsNonNull(), $"{firstFile.Name}-changed{firstFile.Extension}"));
-
- using (var zip = ZipArchive.Create())
- {
- zip.AddAllFromDirectory(extractedFolder);
- zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
- }
-
- var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp));
-
- ensureLoaded(osu);
-
- // check the newly "imported" beatmap is not the original.
- Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID);
- }
- finally
- {
- Directory.Delete(extractedFolder, true);
- }
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- [Ignore("intentionally broken by import optimisations")]
- public async Task TestImportCorruptThenImport()
- {
- // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- var imported = await LoadOszIntoOsu(osu);
-
- var firstFile = imported.Files.First();
-
- var files = osu.Dependencies.Get();
-
- long originalLength;
- using (var stream = files.Storage.GetStream(firstFile.FileInfo.GetStoragePath()))
- originalLength = stream.Length;
-
- using (var stream = files.Storage.GetStream(firstFile.FileInfo.GetStoragePath(), FileAccess.Write, FileMode.Create))
- stream.WriteByte(0);
-
- var importedSecondTime = await LoadOszIntoOsu(osu);
-
- using (var stream = files.Storage.GetStream(firstFile.FileInfo.GetStoragePath()))
- Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import");
-
- // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
- Assert.IsTrue(imported.ID == importedSecondTime.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
-
- checkBeatmapSetCount(osu, 1);
- checkSingleReferencedFileCount(osu, 18);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestModelCreationFailureDoesntReturn()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
- var importer = osu.Dependencies.Get();
-
- var progressNotification = new ImportProgressNotification();
-
- var zipStream = new MemoryStream();
-
- using (var zip = ZipArchive.Create())
- zip.SaveTo(zipStream, new ZipWriterOptions(CompressionType.Deflate));
-
- var imported = await importer.Import(
- progressNotification,
- new ImportTask(zipStream, string.Empty)
- );
-
- checkBeatmapSetCount(osu, 0);
- checkBeatmapCount(osu, 0);
-
- Assert.IsEmpty(imported);
- Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- 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)))
- {
- try
- {
- int itemAddRemoveFireCount = 0;
- int loggedExceptionCount = 0;
-
- Logger.NewEntry += l =>
- {
- if (l.Target == LoggingTarget.Database && l.Exception != null)
- Interlocked.Increment(ref loggedExceptionCount);
- };
-
- var osu = LoadOsuIntoHost(host);
- var manager = osu.Dependencies.Get();
-
- // ReSharper disable once AccessToModifiedClosure
- manager.ItemUpdated += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
- manager.ItemRemoved += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
-
- var imported = await LoadOszIntoOsu(osu);
-
- Assert.AreEqual(0, itemAddRemoveFireCount -= 1);
-
- imported.Hash += "-changed";
- manager.Update(imported);
-
- Assert.AreEqual(0, itemAddRemoveFireCount -= 1);
-
- checkBeatmapSetCount(osu, 1);
- checkBeatmapCount(osu, 12);
- checkSingleReferencedFileCount(osu, 18);
-
- string brokenTempFilename = TestResources.GetTestBeatmapForImport();
-
- MemoryStream brokenOsu = new MemoryStream();
- MemoryStream brokenOsz = new MemoryStream(await File.ReadAllBytesAsync(brokenTempFilename));
-
- File.Delete(brokenTempFilename);
-
- using (var outStream = File.Open(brokenTempFilename, FileMode.CreateNew))
- using (var zip = ZipArchive.Open(brokenOsz))
- {
- zip.AddEntry("broken.osu", brokenOsu, false);
- zip.SaveTo(outStream, CompressionType.Deflate);
- }
-
- // this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
- try
- {
- await manager.Import(new ImportTask(brokenTempFilename));
- }
- catch
- {
- }
-
- // no events should be fired in the case of a rollback.
- Assert.AreEqual(0, itemAddRemoveFireCount);
-
- checkBeatmapSetCount(osu, 1);
- checkBeatmapCount(osu, 12);
-
- checkSingleReferencedFileCount(osu, 18);
-
- Assert.AreEqual(1, loggedExceptionCount);
-
- File.Delete(brokenTempFilename);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- 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)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- var imported = await LoadOszIntoOsu(osu);
-
- deleteBeatmapSet(imported, osu);
-
- var importedSecondTime = await LoadOszIntoOsu(osu);
-
- // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
- Assert.IsTrue(imported.ID == importedSecondTime.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportThenDeleteThenImportWithOnlineIDsMissing()
- {
- // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}"))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- var imported = await LoadOszIntoOsu(osu);
-
- foreach (var b in imported.Beatmaps)
- b.OnlineID = null;
-
- osu.Dependencies.Get().Update(imported);
-
- deleteBeatmapSet(imported, osu);
-
- var importedSecondTime = await LoadOszIntoOsu(osu);
-
- // check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched)
- Assert.IsTrue(imported.ID != importedSecondTime.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportWithDuplicateBeatmapIDs()
- {
- // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- var metadata = new BeatmapMetadata
- {
- Artist = "SomeArtist",
- AuthorString = "SomeAuthor"
- };
-
- var difficulty = new BeatmapDifficulty();
-
- var toImport = new BeatmapSetInfo
- {
- OnlineID = 1,
- Metadata = metadata,
- Beatmaps =
- {
- new BeatmapInfo
- {
- OnlineID = 2,
- Metadata = metadata,
- BaseDifficulty = difficulty
- },
- new BeatmapInfo
- {
- OnlineID = 2,
- Metadata = metadata,
- Status = BeatmapOnlineStatus.Loved,
- BaseDifficulty = difficulty
- }
- }
- };
-
- var manager = osu.Dependencies.Get();
-
- var imported = await manager.Import(toImport);
-
- Assert.NotNull(imported);
- Assert.AreEqual(null, imported.Value.Beatmaps[0].OnlineID);
- Assert.AreEqual(null, imported.Value.Beatmaps[1].OnlineID);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- [NonParallelizable]
- public void TestImportOverIPC()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-host", true))
- using (HeadlessGameHost client = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-client", true))
- {
- try
- {
- Assert.IsTrue(host.IsPrimaryInstance);
- Assert.IsFalse(client.IsPrimaryInstance);
-
- var osu = LoadOsuIntoHost(host);
-
- string temp = TestResources.GetTestBeatmapForImport();
-
- var importer = new ArchiveImportIPCChannel(client);
- if (!importer.ImportAsync(temp).Wait(10000))
- Assert.Fail(@"IPC took too long to send");
-
- ensureLoaded(osu);
-
- waitForOrAssert(() => !File.Exists(temp), "Temporary still exists after IPC import", 5000);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportWhenFileOpen()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
- string temp = TestResources.GetTestBeatmapForImport();
- using (File.OpenRead(temp))
- await osu.Dependencies.Get().Import(temp);
- ensureLoaded(osu);
- File.Delete(temp);
- Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportWithDuplicateHashes()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- string temp = TestResources.GetTestBeatmapForImport();
-
- string extractedFolder = $"{temp}_extracted";
- Directory.CreateDirectory(extractedFolder);
-
- try
- {
- using (var zip = ZipArchive.Open(temp))
- zip.WriteToDirectory(extractedFolder);
-
- using (var zip = ZipArchive.Create())
- {
- zip.AddAllFromDirectory(extractedFolder);
- zip.AddEntry("duplicate.osu", Directory.GetFiles(extractedFolder, "*.osu").First());
- zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
- }
-
- await osu.Dependencies.Get().Import(temp);
-
- ensureLoaded(osu);
- }
- finally
- {
- Directory.Delete(extractedFolder, true);
- }
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportNestedStructure()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- string temp = TestResources.GetTestBeatmapForImport();
-
- string extractedFolder = $"{temp}_extracted";
- string subfolder = Path.Combine(extractedFolder, "subfolder");
-
- Directory.CreateDirectory(subfolder);
-
- try
- {
- using (var zip = ZipArchive.Open(temp))
- zip.WriteToDirectory(subfolder);
-
- using (var zip = ZipArchive.Create())
- {
- zip.AddAllFromDirectory(extractedFolder);
- zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
- }
-
- var imported = await osu.Dependencies.Get().Import(new ImportTask(temp));
-
- ensureLoaded(osu);
-
- Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder");
- }
- finally
- {
- Directory.Delete(extractedFolder, true);
- }
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportWithIgnoredDirectoryInArchive()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- string temp = TestResources.GetTestBeatmapForImport();
-
- string extractedFolder = $"{temp}_extracted";
- string dataFolder = Path.Combine(extractedFolder, "actual_data");
- string resourceForkFolder = Path.Combine(extractedFolder, "__MACOSX");
- string resourceForkFilePath = Path.Combine(resourceForkFolder, ".extracted");
-
- Directory.CreateDirectory(dataFolder);
- Directory.CreateDirectory(resourceForkFolder);
-
- using (var resourceForkFile = File.CreateText(resourceForkFilePath))
- {
- await resourceForkFile.WriteLineAsync("adding content so that it's not empty");
- }
-
- try
- {
- using (var zip = ZipArchive.Open(temp))
- zip.WriteToDirectory(dataFolder);
-
- using (var zip = ZipArchive.Create())
- {
- zip.AddAllFromDirectory(extractedFolder);
- zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
- }
-
- var imported = await osu.Dependencies.Get().Import(new ImportTask(temp));
-
- 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");
- }
- finally
- {
- Directory.Delete(extractedFolder, true);
- }
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestUpdateBeatmapInfo()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
- var manager = osu.Dependencies.Get();
-
- string temp = TestResources.GetTestBeatmapForImport();
- await osu.Dependencies.Get().Import(temp);
-
- // Update via the beatmap, not the beatmap info, to ensure correct linking
- BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0];
- Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap;
- beatmapToUpdate.BeatmapInfo.DifficultyName = "updated";
-
- manager.Update(setToUpdate);
-
- BeatmapInfo updatedInfo = manager.QueryBeatmap(b => b.ID == beatmapToUpdate.BeatmapInfo.ID);
- Assert.That(updatedInfo.DifficultyName, Is.EqualTo("updated"));
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestUpdateBeatmapFile()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
- var manager = osu.Dependencies.Get();
-
- string temp = TestResources.GetTestBeatmapForImport();
- await osu.Dependencies.Get().Import(temp);
-
- BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0];
-
- var beatmapInfo = setToUpdate.Beatmaps.First(b => b.RulesetID == 0);
- Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap;
- BeatmapSetFileInfo fileToUpdate = setToUpdate.Files.First(f => beatmapToUpdate.BeatmapInfo.Path.Contains(f.Filename));
-
- string oldMd5Hash = beatmapToUpdate.BeatmapInfo.MD5Hash;
-
- beatmapToUpdate.HitObjects.Clear();
- beatmapToUpdate.HitObjects.Add(new HitCircle { StartTime = 5000 });
-
- manager.Save(beatmapInfo, beatmapToUpdate);
-
- // Check that the old file reference has been removed
- Assert.That(manager.QueryBeatmapSet(s => s.ID == setToUpdate.ID).Files.All(f => f.ID != fileToUpdate.ID));
-
- // Check that the new file is referenced correctly by attempting a retrieval
- Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(manager.QueryBeatmap(b => b.ID == beatmapToUpdate.BeatmapInfo.ID)).Beatmap;
- Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(1));
- Assert.That(updatedBeatmap.HitObjects[0].StartTime, Is.EqualTo(5000));
- Assert.That(updatedBeatmap.BeatmapInfo.MD5Hash, Is.Not.EqualTo(oldMd5Hash));
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- // TODO: needs to be pulled across to realm implementation when this file is nuked.
- [Test]
- 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)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- var manager = osu.Dependencies.Get();
-
- var working = manager.CreateNew(new OsuRuleset().RulesetInfo, APIUser.SYSTEM_USER);
-
- var beatmap = working.Beatmap;
-
- beatmap.BeatmapInfo.DifficultyName = "difficulty";
- beatmap.BeatmapInfo.Metadata = new BeatmapMetadata
- {
- Artist = "Artist/With\\Slashes",
- Title = "Title",
- AuthorString = "mapper",
- };
-
- manager.Save(beatmap.BeatmapInfo, working.Beatmap);
-
- Assert.AreEqual("Artist_With_Slashes - Title (mapper) [difficulty].osu", beatmap.BeatmapInfo.Path);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public void TestCreateNewEmptyBeatmap()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
- var manager = osu.Dependencies.Get();
-
- var working = manager.CreateNew(new OsuRuleset().RulesetInfo, APIUser.SYSTEM_USER);
-
- manager.Save(working.BeatmapInfo, working.Beatmap);
-
- var retrievedSet = manager.GetAllUsableBeatmapSets()[0];
-
- // Check that the new file is referenced correctly by attempting a retrieval
- Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap;
- Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(0));
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public void TestCreateNewBeatmapWithObject()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
- var manager = osu.Dependencies.Get();
-
- var working = manager.CreateNew(new OsuRuleset().RulesetInfo, APIUser.SYSTEM_USER);
-
- ((Beatmap)working.Beatmap).HitObjects.Add(new HitCircle { StartTime = 5000 });
-
- manager.Save(working.BeatmapInfo, working.Beatmap);
-
- var retrievedSet = manager.GetAllUsableBeatmapSets()[0];
-
- // Check that the new file is referenced correctly by attempting a retrieval
- Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap;
- Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(1));
- Assert.That(updatedBeatmap.HitObjects[0].StartTime, Is.EqualTo(5000));
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- public static async Task LoadQuickOszIntoOsu(OsuGameBase osu)
- {
- string temp = TestResources.GetQuickTestBeatmapForImport();
-
- var manager = osu.Dependencies.Get();
-
- var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false);
-
- ensureLoaded(osu);
-
- waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
-
- return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID);
- }
-
- public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
- {
- string temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
-
- var manager = osu.Dependencies.Get();
-
- var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false);
-
- ensureLoaded(osu);
-
- waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
-
- return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID);
- }
-
- private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu)
- {
- var manager = osu.Dependencies.Get();
- manager.Delete(imported);
-
- checkBeatmapSetCount(osu, 0);
- checkBeatmapSetCount(osu, 1, true);
- checkSingleReferencedFileCount(osu, 0);
-
- Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending);
- }
-
- private static Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmapInfo)
- {
- return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
- {
- OnlineID = 2,
- BeatmapInfo = beatmapInfo,
- BeatmapInfoID = beatmapInfo.ID
- }, new ImportScoreTest.TestArchiveReader());
- }
-
- private static void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false)
- {
- var manager = osu.Dependencies.Get();
-
- Assert.AreEqual(expected, includeDeletePending
- ? manager.QueryBeatmapSets(_ => true).ToList().Count
- : manager.GetAllUsableBeatmapSets().Count);
- }
-
- private static string hashFile(string filename)
- {
- using (var s = File.OpenRead(filename))
- return s.ComputeMD5Hash();
- }
-
- private static void checkBeatmapCount(OsuGameBase osu, int expected)
- {
- Assert.AreEqual(expected, osu.Dependencies.Get().QueryBeatmaps(_ => true).ToList().Count);
- }
-
- private static void checkSingleReferencedFileCount(OsuGameBase osu, int expected)
- {
- Assert.AreEqual(expected, osu.Dependencies.Get().Get().FileInfo.Count(f => f.ReferenceCount == 1));
- }
-
- private static void ensureLoaded(OsuGameBase osu, int timeout = 60000)
- {
- IEnumerable resultSets = null;
- var store = osu.Dependencies.Get();
- waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineID == 241526)).Any(),
- @"BeatmapSet did not import to the database in allocated time.", timeout);
-
- // ensure we were stored to beatmap database backing...
- Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1).");
- IEnumerable queryBeatmaps() => store.QueryBeatmaps(s => s.BeatmapSet.OnlineID == 241526 && s.BaseDifficultyID > 0);
- IEnumerable queryBeatmapSets() => store.QueryBeatmapSets(s => s.OnlineID == 241526);
-
- // if we don't re-check here, the set will be inserted but the beatmaps won't be present yet.
- waitForOrAssert(() => queryBeatmaps().Count() == 12,
- @"Beatmaps did not import to the database in allocated time", timeout);
- waitForOrAssert(() => queryBeatmapSets().Count() == 1,
- @"BeatmapSet did not import to the database in allocated time", timeout);
- int countBeatmapSetBeatmaps = 0;
- int countBeatmaps = 0;
- waitForOrAssert(() =>
- (countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count) ==
- (countBeatmaps = queryBeatmaps().Count()),
- $@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).", timeout);
-
- var set = queryBeatmapSets().First();
- foreach (BeatmapInfo b in set.Beatmaps)
- Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID));
- Assert.IsTrue(set.Beatmaps.Count > 0);
- var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap;
- Assert.IsTrue(beatmap?.HitObjects.Any() == true);
- beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap;
- Assert.IsTrue(beatmap?.HitObjects.Any() == true);
- beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap;
- Assert.IsTrue(beatmap?.HitObjects.Any() == true);
- beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap;
- Assert.IsTrue(beatmap?.HitObjects.Any() == true);
- }
-
- private static void waitForOrAssert(Func result, string failureMessage, int timeout = 60000)
- {
- Task task = Task.Run(() =>
- {
- while (!result()) Thread.Sleep(200);
- });
-
- Assert.IsTrue(task.Wait(timeout), failureMessage);
- }
- }
-}
diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs
index b2ab1eeaa6..810ea5dbd0 100644
--- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var meta = beatmap.Metadata;
- Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineID);
+ Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet?.OnlineID);
Assert.AreEqual("Soleily", meta.Artist);
Assert.AreEqual("Soleily", meta.ArtistUnicode);
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs
index 3a82cbc785..f3456cf8e4 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;
@@ -23,6 +24,8 @@ namespace osu.Game.Tests.Beatmaps
{
public const double BASE_STARS = 5.55;
+ private static readonly Guid guid = Guid.NewGuid();
+
private BeatmapSetInfo importedSet;
private TestBeatmapDifficultyCache difficultyCache;
@@ -32,7 +35,7 @@ namespace osu.Game.Tests.Beatmaps
[BackgroundDependencyLoader]
private void load(OsuGameBase osu)
{
- importedSet = ImportBeatmapTest.LoadQuickOszIntoOsu(osu).Result;
+ importedSet = BeatmapImportHelper.LoadQuickOszIntoOsu(osu).GetResultSafely();
}
[SetUpSteps]
@@ -97,8 +100,8 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestKeyEqualsWithDifferentModInstances()
{
- var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
- var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
+ var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
+ var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
Assert.That(key1, Is.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
@@ -107,8 +110,8 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestKeyEqualsWithDifferentModOrder()
{
- var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
- var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHidden(), new OsuModHardRock() });
+ var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
+ var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHidden(), new OsuModHardRock() });
Assert.That(key1, Is.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
@@ -117,8 +120,8 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestKeyDoesntEqualWithDifferentModSettings()
{
- var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } });
- var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } });
+ var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } });
+ var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } });
Assert.That(key1, Is.Not.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.Not.EqualTo(key2.GetHashCode()));
@@ -127,8 +130,8 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestKeyEqualWithMatchingModSettings()
{
- var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
- var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
+ var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
+ var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
Assert.That(key1, Is.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
diff --git a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs
index bf5b517603..153788c2cf 100644
--- a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs
+++ b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs
@@ -7,6 +7,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
@@ -30,7 +31,13 @@ namespace osu.Game.Tests.Beatmaps
AddStep("add beatmap", () =>
{
- Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ });
editorBeatmap.HitObjectAdded += h => addedObject = h;
});
@@ -49,7 +56,14 @@ namespace osu.Game.Tests.Beatmaps
EditorBeatmap editorBeatmap = null;
AddStep("add beatmap", () =>
{
- Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
+ Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ HitObjects = { hitCircle }
+ });
editorBeatmap.HitObjectRemoved += h => removedObject = h;
});
AddStep("remove hitobject", () => editorBeatmap.Remove(editorBeatmap.HitObjects.First()));
@@ -71,7 +85,14 @@ namespace osu.Game.Tests.Beatmaps
{
EditorBeatmap editorBeatmap;
- Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
+ Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ HitObjects = { hitCircle }
+ });
editorBeatmap.HitObjectUpdated += h => changedObject = h;
});
@@ -91,7 +112,13 @@ namespace osu.Game.Tests.Beatmaps
AddStep("add beatmap", () =>
{
- Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ });
editorBeatmap.HitObjectUpdated += h => changedObject = h;
});
@@ -111,7 +138,14 @@ namespace osu.Game.Tests.Beatmaps
public void TestRemovedHitObjectStartTimeChangeEvent()
{
var hitCircle = new HitCircle();
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ HitObjects = { hitCircle }
+ });
HitObject changedObject = null;
editorBeatmap.HitObjectUpdated += h => changedObject = h;
@@ -131,6 +165,10 @@ namespace osu.Game.Tests.Beatmaps
{
var editorBeatmap = new EditorBeatmap(new OsuBeatmap
{
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
HitObjects =
{
new HitCircle(),
@@ -156,6 +194,10 @@ namespace osu.Game.Tests.Beatmaps
var editorBeatmap = new EditorBeatmap(new OsuBeatmap
{
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
HitObjects =
{
new HitCircle(),
@@ -185,7 +227,13 @@ namespace osu.Game.Tests.Beatmaps
{
updatedObjects.Clear();
- Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ });
for (int i = 0; i < 10; i++)
{
@@ -220,7 +268,13 @@ namespace osu.Game.Tests.Beatmaps
{
updatedObjects.Clear();
- Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ });
editorBeatmap.Add(new HitCircle());
});
diff --git a/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs b/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs
index 4a7d7505ad..10cac4ed9d 100644
--- a/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs
+++ b/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs
@@ -3,7 +3,7 @@
using NUnit.Framework;
using osu.Game.Beatmaps;
-using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Models;
namespace osu.Game.Tests.Beatmaps
{
@@ -34,7 +34,7 @@ namespace osu.Game.Tests.Beatmaps
{
Artist = "artist",
Title = "title",
- Author = new APIUser { Username = "creator" }
+ Author = new RealmUser { Username = "creator" }
}
};
@@ -50,7 +50,7 @@ namespace osu.Game.Tests.Beatmaps
{
Artist = "artist",
Title = "title",
- Author = new APIUser { Username = "creator" }
+ Author = new RealmUser { Username = "creator" }
},
DifficultyName = "difficulty"
};
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 e47e24021f..227314cffd 100644
--- a/osu.Game.Tests/Database/BeatmapImporterTests.cs
+++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs
@@ -19,6 +19,7 @@ using osu.Game.Extensions;
using osu.Game.IO.Archives;
using osu.Game.Models;
using osu.Game.Overlays.Notifications;
+using osu.Game.Rulesets;
using osu.Game.Stores;
using osu.Game.Tests.Resources;
using Realms;
@@ -34,33 +35,134 @@ namespace osu.Game.Tests.Database
[TestFixture]
public class BeatmapImporterTests : RealmTest
{
+ [Test]
+ public void TestDetachBeatmapSet()
+ {
+ RunTestWithRealmAsync(async (realmFactory, storage) =>
+ {
+ using (var importer = new BeatmapModelManager(realmFactory, storage))
+ using (new RulesetStore(realmFactory, storage))
+ {
+ ILive? beatmapSet;
+
+ using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
+ beatmapSet = await importer.Import(reader);
+
+ Assert.NotNull(beatmapSet);
+ Debug.Assert(beatmapSet != null);
+
+ BeatmapSetInfo? detachedBeatmapSet = null;
+
+ beatmapSet.PerformRead(live =>
+ {
+ detachedBeatmapSet = live.Detach();
+
+ // files are omitted
+ Assert.AreEqual(0, detachedBeatmapSet.Files.Count);
+
+ Assert.AreEqual(live.Beatmaps.Count, detachedBeatmapSet.Beatmaps.Count);
+ Assert.AreEqual(live.Beatmaps.Select(f => f.Difficulty).Count(), detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count());
+ Assert.AreEqual(live.Metadata, detachedBeatmapSet.Metadata);
+ });
+
+ Debug.Assert(detachedBeatmapSet != null);
+
+ // Check detached instances can all be accessed without throwing.
+ Assert.AreEqual(0, detachedBeatmapSet.Files.Count);
+ Assert.NotNull(detachedBeatmapSet.Beatmaps.Count);
+ Assert.NotZero(detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count());
+ Assert.NotNull(detachedBeatmapSet.Metadata);
+
+ // Check cyclic reference to beatmap set
+ Assert.AreEqual(detachedBeatmapSet, detachedBeatmapSet.Beatmaps.First().BeatmapSet);
+ }
+ });
+ }
+
+ [Test]
+ public void TestUpdateDetachedBeatmapSet()
+ {
+ RunTestWithRealmAsync(async (realmFactory, storage) =>
+ {
+ using (var importer = new BeatmapModelManager(realmFactory, storage))
+ using (new RulesetStore(realmFactory, storage))
+ {
+ ILive? beatmapSet;
+
+ using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
+ beatmapSet = await importer.Import(reader);
+
+ Assert.NotNull(beatmapSet);
+ Debug.Assert(beatmapSet != null);
+
+ // Detach at the BeatmapInfo point, similar to what GetWorkingBeatmap does.
+ BeatmapInfo? detachedBeatmap = null;
+
+ beatmapSet.PerformRead(s => detachedBeatmap = s.Beatmaps.First().Detach());
+
+ BeatmapSetInfo? detachedBeatmapSet = detachedBeatmap?.BeatmapSet;
+
+ Debug.Assert(detachedBeatmapSet != null);
+
+ var newUser = new RealmUser { Username = "peppy", OnlineID = 2 };
+
+ detachedBeatmapSet.Beatmaps.First().Metadata.Artist = "New Artist";
+ detachedBeatmapSet.Beatmaps.First().Metadata.Author = newUser;
+
+ Assert.AreNotEqual(detachedBeatmapSet.Status, BeatmapOnlineStatus.Ranked);
+ detachedBeatmapSet.Status = BeatmapOnlineStatus.Ranked;
+
+ beatmapSet.PerformWrite(s =>
+ {
+ detachedBeatmapSet.CopyChangesToRealm(s);
+ });
+
+ beatmapSet.PerformRead(s =>
+ {
+ // Check above changes explicitly.
+ Assert.AreEqual(BeatmapOnlineStatus.Ranked, s.Status);
+ Assert.AreEqual("New Artist", s.Beatmaps.First().Metadata.Artist);
+ Assert.AreEqual(newUser, s.Beatmaps.First().Metadata.Author);
+ Assert.NotZero(s.Files.Count);
+
+ // Check nothing was lost in the copy operation.
+ Assert.AreEqual(s.Files.Count, detachedBeatmapSet.Files.Count);
+ Assert.AreEqual(s.Files.Select(f => f.File).Count(), detachedBeatmapSet.Files.Select(f => f.File).Count());
+ Assert.AreEqual(s.Beatmaps.Count, detachedBeatmapSet.Beatmaps.Count);
+ Assert.AreEqual(s.Beatmaps.Select(f => f.Difficulty).Count(), detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count());
+ Assert.AreEqual(s.Metadata, detachedBeatmapSet.Metadata);
+ });
+ }
+ });
+ }
+
[Test]
public void TestImportBeatmapThenCleanup()
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using (var importer = new BeatmapImporter(realmFactory, storage))
- using (new RealmRulesetStore(realmFactory, storage))
+ using (var importer = new BeatmapModelManager(realmFactory, storage))
+ using (new RulesetStore(realmFactory, storage))
{
- ILive? imported;
+ ILive? imported;
using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
imported = await importer.Import(reader);
- Assert.AreEqual(1, realmFactory.Context.All().Count());
+ Assert.AreEqual(1, realmFactory.Context.All().Count());
Assert.NotNull(imported);
Debug.Assert(imported != null);
imported.PerformWrite(s => s.DeletePending = true);
- Assert.AreEqual(1, realmFactory.Context.All().Count(s => s.DeletePending));
+ Assert.AreEqual(1, realmFactory.Context.All().Count(s => s.DeletePending));
}
});
Logger.Log("Running with no work to purge pending deletions");
- RunTestWithRealm((realmFactory, _) => { Assert.AreEqual(0, realmFactory.Context.All().Count()); });
+ RunTestWithRealm((realmFactory, _) => { Assert.AreEqual(0, realmFactory.Context.All().Count()); });
}
[Test]
@@ -68,8 +170,8 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
await LoadOszIntoStore(importer, realmFactory.Context);
});
@@ -80,8 +182,8 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
@@ -98,8 +200,8 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
@@ -112,17 +214,17 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
string? tempPath = TestResources.GetTestBeatmapForImport();
- ILive? importedSet;
+ ILive? importedSet;
using (var stream = File.OpenRead(tempPath))
{
importedSet = await importer.Import(new ImportTask(stream, Path.GetFileName(tempPath)));
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realmFactory.Context);
}
Assert.NotNull(importedSet);
@@ -131,7 +233,7 @@ namespace osu.Game.Tests.Database
Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing");
File.Delete(tempPath);
- var imported = realmFactory.Context.All().First(beatmapSet => beatmapSet.ID == importedSet.ID);
+ var imported = realmFactory.Context.All().First(beatmapSet => beatmapSet.ID == importedSet.ID);
deleteBeatmapSet(imported, realmFactory.Context);
});
@@ -142,8 +244,8 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
@@ -162,8 +264,8 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -190,7 +292,7 @@ namespace osu.Game.Tests.Database
var importedSecondTime = await importer.Import(new ImportTask(temp));
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realmFactory.Context);
Assert.NotNull(importedSecondTime);
Debug.Assert(importedSecondTime != null);
@@ -211,8 +313,8 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -241,7 +343,7 @@ namespace osu.Game.Tests.Database
var importedSecondTime = await importer.Import(new ImportTask(temp));
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realmFactory.Context);
// check the newly "imported" beatmap is not the original.
Assert.NotNull(importedSecondTime);
@@ -263,8 +365,8 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -290,7 +392,7 @@ namespace osu.Game.Tests.Database
var importedSecondTime = await importer.Import(new ImportTask(temp));
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realmFactory.Context);
Assert.NotNull(importedSecondTime);
Debug.Assert(importedSecondTime != null);
@@ -311,8 +413,8 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -338,7 +440,7 @@ namespace osu.Game.Tests.Database
var importedSecondTime = await importer.Import(new ImportTask(temp));
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realmFactory.Context);
Assert.NotNull(importedSecondTime);
Debug.Assert(importedSecondTime != null);
@@ -360,8 +462,8 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
@@ -393,8 +495,8 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
var progressNotification = new ImportProgressNotification();
@@ -429,8 +531,8 @@ namespace osu.Game.Tests.Database
Interlocked.Increment(ref loggedExceptionCount);
};
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
@@ -479,8 +581,8 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
@@ -504,7 +606,7 @@ namespace osu.Game.Tests.Database
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
using var importer = new NonOptimisedBeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
@@ -527,8 +629,8 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
@@ -553,10 +655,10 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
- var metadata = new RealmBeatmapMetadata
+ var metadata = new BeatmapMetadata
{
Artist = "SomeArtist",
Author =
@@ -565,18 +667,18 @@ namespace osu.Game.Tests.Database
}
};
- var ruleset = realmFactory.Context.All().First();
+ var ruleset = realmFactory.Context.All().First();
- var toImport = new RealmBeatmapSet
+ var toImport = new BeatmapSetInfo
{
OnlineID = 1,
Beatmaps =
{
- new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata)
+ new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata)
{
OnlineID = 2,
},
- new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata)
+ new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata)
{
OnlineID = 2,
Status = BeatmapOnlineStatus.Loved,
@@ -599,13 +701,13 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
string? temp = TestResources.GetTestBeatmapForImport();
using (File.OpenRead(temp))
await importer.Import(temp);
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realmFactory.Context);
File.Delete(temp);
Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
});
@@ -616,8 +718,8 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -638,7 +740,7 @@ namespace osu.Game.Tests.Database
await importer.Import(temp);
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realmFactory.Context);
}
finally
{
@@ -652,8 +754,8 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -678,7 +780,7 @@ namespace osu.Game.Tests.Database
Assert.NotNull(imported);
Debug.Assert(imported != null);
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realmFactory.Context);
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("subfolder"))), "Files contain common subfolder");
}
@@ -694,8 +796,8 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -728,7 +830,7 @@ namespace osu.Game.Tests.Database
Assert.NotNull(imported);
Debug.Assert(imported != null);
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realmFactory.Context);
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("__MACOSX"))), "Files contain resource fork folder, which should be ignored");
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("actual_data"))), "Files contain common subfolder");
@@ -745,25 +847,25 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
string? temp = TestResources.GetTestBeatmapForImport();
await importer.Import(temp);
// Update via the beatmap, not the beatmap info, to ensure correct linking
- RealmBeatmapSet setToUpdate = realmFactory.Context.All().First();
+ BeatmapSetInfo setToUpdate = realmFactory.Context.All().First();
var beatmapToUpdate = setToUpdate.Beatmaps.First();
realmFactory.Context.Write(() => beatmapToUpdate.DifficultyName = "updated");
- RealmBeatmap updatedInfo = realmFactory.Context.All().First(b => b.ID == beatmapToUpdate.ID);
+ BeatmapInfo updatedInfo = realmFactory.Context.All().First(b => b.ID == beatmapToUpdate.ID);
Assert.That(updatedInfo.DifficultyName, Is.EqualTo("updated"));
});
}
- public static async Task LoadQuickOszIntoOsu(BeatmapImporter importer, Realm realm)
+ public static async Task LoadQuickOszIntoOsu(BeatmapImporter importer, Realm realm)
{
string? temp = TestResources.GetQuickTestBeatmapForImport();
@@ -771,14 +873,14 @@ namespace osu.Game.Tests.Database
Assert.NotNull(importedSet);
- ensureLoaded(realm);
+ EnsureLoaded(realm);
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
- return realm.All().FirstOrDefault(beatmapSet => beatmapSet.ID == importedSet!.ID);
+ return realm.All().FirstOrDefault(beatmapSet => beatmapSet.ID == importedSet!.ID);
}
- public static async Task LoadOszIntoStore(BeatmapImporter importer, Realm realm, string? path = null, bool virtualTrack = false)
+ public static async Task LoadOszIntoStore(BeatmapImporter importer, Realm realm, string? path = null, bool virtualTrack = false)
{
string? temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
@@ -787,24 +889,24 @@ namespace osu.Game.Tests.Database
Assert.NotNull(importedSet);
Debug.Assert(importedSet != null);
- ensureLoaded(realm);
+ EnsureLoaded(realm);
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
- return realm.All().First(beatmapSet => beatmapSet.ID == importedSet.ID);
+ return realm.All().First(beatmapSet => beatmapSet.ID == importedSet.ID);
}
- private void deleteBeatmapSet(RealmBeatmapSet imported, Realm realm)
+ private void deleteBeatmapSet(BeatmapSetInfo imported, Realm realm)
{
realm.Write(() => imported.DeletePending = true);
checkBeatmapSetCount(realm, 0);
checkBeatmapSetCount(realm, 1, true);
- Assert.IsTrue(realm.All().First(_ => true).DeletePending);
+ Assert.IsTrue(realm.All().First(_ => true).DeletePending);
}
- private static Task createScoreForBeatmap(Realm realm, RealmBeatmap beatmap)
+ private static Task createScoreForBeatmap(Realm realm, BeatmapInfo beatmap)
{
// TODO: reimplement when we have score support in realm.
// return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
@@ -820,8 +922,8 @@ namespace osu.Game.Tests.Database
private static void checkBeatmapSetCount(Realm realm, int expected, bool includeDeletePending = false)
{
Assert.AreEqual(expected, includeDeletePending
- ? realm.All().Count()
- : realm.All().Count(s => !s.DeletePending));
+ ? realm.All().Count()
+ : realm.All().Count(s => !s.DeletePending));
}
private static string hashFile(string filename)
@@ -832,7 +934,7 @@ namespace osu.Game.Tests.Database
private static void checkBeatmapCount(Realm realm, int expected)
{
- Assert.AreEqual(expected, realm.All().Where(_ => true).ToList().Count);
+ Assert.AreEqual(expected, realm.All().Where(_ => true).ToList().Count);
}
private static void checkSingleReferencedFileCount(Realm realm, int expected)
@@ -848,26 +950,25 @@ namespace osu.Game.Tests.Database
Assert.AreEqual(expected, singleReferencedCount);
}
- private static void ensureLoaded(Realm realm, int timeout = 60000)
+ internal static void EnsureLoaded(Realm realm, int timeout = 60000)
{
- IQueryable? resultSets = null;
+ IQueryable? resultSets = null;
waitForOrAssert(() =>
- {
- realm.Refresh();
- return (resultSets = realm.All().Where(s => !s.DeletePending && s.OnlineID == 241526)).Any();
- },
- @"BeatmapSet did not import to the database in allocated time.", timeout);
+ {
+ realm.Refresh();
+ return (resultSets = realm.All().Where(s => !s.DeletePending && s.OnlineID == 241526)).Any();
+ }, @"BeatmapSet did not import to the database in allocated time.", timeout);
// ensure we were stored to beatmap database backing...
Assert.IsTrue(resultSets?.Count() == 1, $@"Incorrect result count found ({resultSets?.Count()} but should be 1).");
- IEnumerable queryBeatmapSets() => realm.All().Where(s => !s.DeletePending && s.OnlineID == 241526);
+ IEnumerable queryBeatmapSets() => realm.All().Where(s => !s.DeletePending && s.OnlineID == 241526);
var set = queryBeatmapSets().First();
// ReSharper disable once PossibleUnintendedReferenceComparison
- IEnumerable queryBeatmaps() => realm.All().Where(s => s.BeatmapSet != null && s.BeatmapSet == set);
+ IEnumerable queryBeatmaps() => realm.All().Where(s => s.BeatmapSet != null && s.BeatmapSet == set);
Assert.AreEqual(12, queryBeatmaps().Count(), @"Beatmap count was not correct");
Assert.AreEqual(1, queryBeatmapSets().Count(), @"Beatmapset count was not correct");
@@ -880,7 +981,7 @@ namespace osu.Game.Tests.Database
countBeatmaps = queryBeatmaps().Count(),
$@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).");
- foreach (RealmBeatmap b in set.Beatmaps)
+ foreach (BeatmapInfo b in set.Beatmaps)
Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID));
Assert.IsTrue(set.Beatmaps.Count > 0);
}
diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs
index 2285b22a3a..0961ad71e4 100644
--- a/osu.Game.Tests/Database/GeneralUsageTests.cs
+++ b/osu.Game.Tests/Database/GeneralUsageTests.cs
@@ -5,8 +5,8 @@ using System;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
+using osu.Game.Beatmaps;
using osu.Game.Database;
-using osu.Game.Models;
#nullable enable
@@ -48,7 +48,7 @@ namespace osu.Game.Tests.Database
using (var context = realmFactory.CreateContext())
{
- var subscription = context.All().QueryAsyncWithNotifications((sender, changes, error) =>
+ var subscription = context.All().QueryAsyncWithNotifications((sender, changes, error) =>
{
using (realmFactory.CreateContext())
{
diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs
index 06cb5a3607..187fcd3ca7 100644
--- a/osu.Game.Tests/Database/RealmLiveTests.cs
+++ b/osu.Game.Tests/Database/RealmLiveTests.cs
@@ -6,9 +6,10 @@ using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
+using osu.Framework.Extensions;
using osu.Framework.Testing;
+using osu.Game.Beatmaps;
using osu.Game.Database;
-using osu.Game.Models;
using Realms;
#nullable enable
@@ -22,9 +23,9 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
- ILive beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive(realmFactory);
+ ILive beatmap = realmFactory.CreateContext().Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realmFactory);
- ILive beatmap2 = realmFactory.CreateContext().All().First().ToLive(realmFactory);
+ ILive beatmap2 = realmFactory.CreateContext().All().First().ToLive(realmFactory);
Assert.AreEqual(beatmap, beatmap2);
});
@@ -35,9 +36,9 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, storage) =>
{
- var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
+ var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
- ILive liveBeatmap;
+ ILive liveBeatmap;
using (var context = realmFactory.CreateContext())
{
@@ -62,7 +63,7 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
- var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
+ var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
var liveBeatmap = beatmap.ToLive(realmFactory);
@@ -76,7 +77,7 @@ namespace osu.Game.Tests.Database
[Test]
public void TestAccessNonManaged()
{
- var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
+ var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
var liveBeatmap = beatmap.ToLiveUnmanaged();
Assert.IsFalse(beatmap.Hidden);
@@ -95,16 +96,16 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
- ILive? liveBeatmap = null;
+ ILive? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
using (var threadContext = realmFactory.CreateContext())
{
- var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
+ var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
liveBeatmap = beatmap.ToLive(realmFactory);
}
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
@@ -115,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();
});
}
@@ -124,16 +125,16 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
- ILive? liveBeatmap = null;
+ ILive? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
using (var threadContext = realmFactory.CreateContext())
{
- var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
+ var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
liveBeatmap = beatmap.ToLive(realmFactory);
}
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
@@ -141,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();
});
}
@@ -150,7 +151,7 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
- var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
+ var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
var liveBeatmap = beatmap.ToLive(realmFactory);
Assert.DoesNotThrow(() =>
@@ -165,17 +166,17 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
- ILive? liveBeatmap = null;
+ ILive? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
using (var threadContext = realmFactory.CreateContext())
{
- var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
+ var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
liveBeatmap = beatmap.ToLive(realmFactory);
}
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
@@ -195,7 +196,7 @@ namespace osu.Game.Tests.Database
var __ = liveBeatmap.Value;
});
}
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
});
}
@@ -204,16 +205,16 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
- ILive? liveBeatmap = null;
+ ILive? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
using (var threadContext = realmFactory.CreateContext())
{
- var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
+ var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
liveBeatmap = beatmap.ToLive(realmFactory);
}
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
@@ -223,7 +224,7 @@ namespace osu.Game.Tests.Database
{
var unused = liveBeatmap.Value;
});
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
});
}
@@ -236,35 +237,35 @@ namespace osu.Game.Tests.Database
using (var updateThreadContext = realmFactory.CreateContext())
{
- updateThreadContext.All().QueryAsyncWithNotifications(gotChange);
- ILive? liveBeatmap = null;
+ updateThreadContext.All().QueryAsyncWithNotifications(gotChange);
+ ILive? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
using (var threadContext = realmFactory.CreateContext())
{
var ruleset = CreateRuleset();
- var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
+ var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata())));
// add a second beatmap to ensure that a full refresh occurs below.
// not just a refresh from the resolved Live.
- threadContext.Write(r => r.Add(new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
+ threadContext.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata())));
liveBeatmap = beatmap.ToLive(realmFactory);
}
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
// not yet seen by main context
- Assert.AreEqual(0, updateThreadContext.All().Count());
+ Assert.AreEqual(0, updateThreadContext.All().Count());
Assert.AreEqual(0, changesTriggered);
liveBeatmap.PerformRead(resolved =>
{
// retrieval causes an implicit refresh. even changes that aren't related to the retrieval are fired at this point.
// ReSharper disable once AccessToDisposedClosure
- Assert.AreEqual(2, updateThreadContext.All().Count());
+ Assert.AreEqual(2, updateThreadContext.All().Count());
Assert.AreEqual(1, changesTriggered);
// can access properties without a crash.
@@ -279,7 +280,7 @@ namespace osu.Game.Tests.Database
});
}
- void gotChange(IRealmCollection sender, ChangeSet changes, Exception error)
+ void gotChange(IRealmCollection sender, ChangeSet changes, Exception error)
{
changesTriggered++;
}
diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs
index 6904464485..0cee165f75 100644
--- a/osu.Game.Tests/Database/RealmTest.cs
+++ b/osu.Game.Tests/Database/RealmTest.cs
@@ -9,9 +9,11 @@ using osu.Framework.Extensions;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Testing;
+using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.IO;
using osu.Game.Models;
+using osu.Game.Rulesets;
#nullable enable
@@ -30,7 +32,7 @@ namespace osu.Game.Tests.Database
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(() =>
{
@@ -54,7 +56,7 @@ 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 () =>
{
@@ -74,24 +76,24 @@ namespace osu.Game.Tests.Database
}
}
- protected static RealmBeatmapSet CreateBeatmapSet(RealmRuleset ruleset)
+ protected static BeatmapSetInfo CreateBeatmapSet(RulesetInfo ruleset)
{
RealmFile createRealmFile() => new RealmFile { Hash = Guid.NewGuid().ToString().ComputeSHA2Hash() };
- var metadata = new RealmBeatmapMetadata
+ var metadata = new BeatmapMetadata
{
Title = "My Love",
Artist = "Kuba Oms"
};
- var beatmapSet = new RealmBeatmapSet
+ var beatmapSet = new BeatmapSetInfo
{
Beatmaps =
{
- new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) { DifficultyName = "Easy", },
- new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) { DifficultyName = "Normal", },
- new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) { DifficultyName = "Hard", },
- new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) { DifficultyName = "Insane", }
+ new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) { DifficultyName = "Easy", },
+ new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) { DifficultyName = "Normal", },
+ new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) { DifficultyName = "Hard", },
+ new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) { DifficultyName = "Insane", }
},
Files =
{
@@ -111,8 +113,8 @@ namespace osu.Game.Tests.Database
return beatmapSet;
}
- protected static RealmRuleset CreateRuleset() =>
- new RealmRuleset(0, "osu!", "osu", true);
+ protected static RulesetInfo CreateRuleset() =>
+ new RulesetInfo(0, "osu!", "osu", true);
private class RealmTestGame : Framework.Game
{
diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs
index cc7e8a0c97..4416da6f92 100644
--- a/osu.Game.Tests/Database/RulesetStoreTests.cs
+++ b/osu.Game.Tests/Database/RulesetStoreTests.cs
@@ -3,8 +3,7 @@
using System.Linq;
using NUnit.Framework;
-using osu.Game.Models;
-using osu.Game.Stores;
+using osu.Game.Rulesets;
namespace osu.Game.Tests.Database
{
@@ -15,10 +14,10 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, storage) =>
{
- var rulesets = new RealmRulesetStore(realmFactory, storage);
+ var rulesets = new RulesetStore(realmFactory, storage);
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
- Assert.AreEqual(4, realmFactory.Context.All().Count());
+ Assert.AreEqual(4, realmFactory.Context.All().Count());
});
}
@@ -27,14 +26,14 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, storage) =>
{
- var rulesets = new RealmRulesetStore(realmFactory, storage);
- var rulesets2 = new RealmRulesetStore(realmFactory, storage);
+ var rulesets = new RulesetStore(realmFactory, storage);
+ var rulesets2 = new RulesetStore(realmFactory, storage);
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
Assert.AreEqual(4, rulesets2.AvailableRulesets.Count());
Assert.AreEqual(rulesets.AvailableRulesets.First(), rulesets2.AvailableRulesets.First());
- Assert.AreEqual(4, realmFactory.Context.All().Count());
+ Assert.AreEqual(4, realmFactory.Context.All().Count());
});
}
@@ -43,7 +42,7 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, storage) =>
{
- var rulesets = new RealmRulesetStore(realmFactory, storage);
+ var rulesets = new RulesetStore(realmFactory, storage);
Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged);
Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged);
diff --git a/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs b/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs
index f9b7bfa586..614b9b4ac1 100644
--- a/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs
+++ b/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs
@@ -74,7 +74,7 @@ namespace osu.Game.Tests.Editing.Checks
[Test]
public void TestMissingFile()
{
- beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
+ beatmap.BeatmapInfo.BeatmapSet?.Files.Clear();
var issues = check.Run(getContext(null)).ToList();
diff --git a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs
index f36454aa71..01baaadc7d 100644
--- a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs
+++ b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Tests.Editing.Checks
[Test]
public void TestBackgroundSetAndNotInFiles()
{
- beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
+ beatmap.BeatmapInfo.BeatmapSet?.Files.Clear();
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
var issues = check.Run(context).ToList();
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/Editing/Checks/CheckTestHelpers.cs b/osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs
index f702921986..9067714ff9 100644
--- a/osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs
+++ b/osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs
@@ -1,18 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Beatmaps;
-using osu.Game.IO;
+using osu.Game.Models;
namespace osu.Game.Tests.Editing.Checks
{
public static class CheckTestHelpers
{
- public static BeatmapSetFileInfo CreateMockFile(string extension) =>
- new BeatmapSetFileInfo
- {
- Filename = $"abc123.{extension}",
- FileInfo = new FileInfo { Hash = "abcdef" }
- };
+ public static RealmNamedFileUsage CreateMockFile(string extension) =>
+ new RealmNamedFileUsage(new RealmFile { Hash = "abcdef" }, $"abc123.{extension}");
}
}
diff --git a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs
index 8adf0d3764..242fec2f68 100644
--- a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs
+++ b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Diagnostics;
using System.IO;
using System.Linq;
using ManagedBass;
@@ -45,6 +46,8 @@ namespace osu.Game.Tests.Editing.Checks
[Test]
public void TestDifferentExtension()
{
+ Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null);
+
beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
beatmap.BeatmapInfo.BeatmapSet.Files.Add(CheckTestHelpers.CreateMockFile("jpg"));
diff --git a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs
index 481cb3230e..2d61948a2a 100644
--- a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs
+++ b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs
@@ -2,7 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
-using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
@@ -158,7 +159,13 @@ namespace osu.Game.Tests.Editing
private (EditorChangeHandler, EditorBeatmap) createChangeHandler()
{
- var beatmap = new EditorBeatmap(new Beatmap());
+ var beatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ });
var changeHandler = new EditorChangeHandler(beatmap);
diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs
index 8eb9452736..43f22e4e90 100644
--- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs
+++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs
@@ -35,7 +35,13 @@ namespace osu.Game.Tests.Editing
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
- editorBeatmap = new EditorBeatmap(new OsuBeatmap()),
+ editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ }),
Content = new Container
{
RelativeSizeAxes = Axes.Both,
diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
index 3aab28886e..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,7 @@ 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;
@@ -34,6 +37,9 @@ namespace osu.Game.Tests.Gameplay
[HeadlessTest]
public class TestSceneStoryboardSamples : OsuTestScene, IStorageResourceProvider
{
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
[Test]
public void TestRetrieveTopLevelSample()
{
@@ -165,6 +171,39 @@ 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)
@@ -184,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;
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/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/BeatmapSetInfoEqualityTest.cs b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs
index 534983f869..1b6049fcb7 100644
--- a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs
+++ b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Extensions;
@@ -23,8 +24,10 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestDatabasedWithDatabased()
{
- var ourInfo = new BeatmapSetInfo { ID = 123 };
- var otherInfo = new BeatmapSetInfo { ID = 123 };
+ var guid = Guid.NewGuid();
+
+ var ourInfo = new BeatmapSetInfo { ID = guid };
+ var otherInfo = new BeatmapSetInfo { ID = guid };
Assert.AreEqual(ourInfo, otherInfo);
}
@@ -32,7 +35,7 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestDatabasedWithOnline()
{
- var ourInfo = new BeatmapSetInfo { ID = 123, OnlineID = 12 };
+ var ourInfo = new BeatmapSetInfo { ID = Guid.NewGuid(), OnlineID = 12 };
var otherInfo = new BeatmapSetInfo { OnlineID = 12 };
Assert.AreNotEqual(ourInfo, otherInfo);
diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
index 8a063b3c6e..61ef31e07e 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())
{
@@ -176,7 +179,7 @@ namespace osu.Game.Tests.NonVisual
{
var osu = LoadOsuIntoHost(host);
- const string database_filename = "client.db";
+ const string database_filename = "client.realm";
Assert.DoesNotThrow(() => osu.Migrate(customPath));
Assert.That(File.Exists(Path.Combine(customPath, database_filename)));
@@ -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..74904f4585 100644
--- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
+++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
@@ -16,9 +16,9 @@ namespace osu.Game.Tests.NonVisual.Filtering
{
private BeatmapInfo getExampleBeatmap() => new BeatmapInfo
{
- Ruleset = new RulesetInfo { OnlineID = 5 },
+ Ruleset = new RulesetInfo { OnlineID = 0 },
StarRating = 4.0d,
- BaseDifficulty = new BeatmapDifficulty
+ Difficulty = new BeatmapDifficulty
{
ApproachRate = 5.0f,
DrainRate = 3.0f,
@@ -30,7 +30,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
ArtistUnicode = "check unicode too",
Title = "Title goes here",
TitleUnicode = "Title goes here",
- AuthorString = "The Author",
+ Author = { Username = "The Author" },
Source = "unit tests",
Tags = "look for tags too",
},
diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
index bc0041e2c2..0c49a18c8f 100644
--- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
+++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
@@ -68,7 +68,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
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
index 2ec5b778d1..855de9b656 100644
--- a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs
+++ b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Tests.Online.Chat
}
[Test]
- public void TestContainsUsernameBetweenInterpunction()
+ public void TestContainsUsernameBetweenPunctuation()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("Hello 'test'-message", "Test"));
}
diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs
index 4b160e1d67..1b7a7656b5 100644
--- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs
+++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs
@@ -9,10 +9,12 @@ using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Online.API;
+using osu.Game.Online.API.Requests.Responses;
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;
@@ -93,7 +95,11 @@ namespace osu.Game.Tests.Online
[Test]
public void TestDeserialiseSubmittableScoreWithEmptyMods()
{
- var score = new SubmittableScore(new ScoreInfo());
+ var score = new SubmittableScore(new ScoreInfo
+ {
+ User = new APIUser(),
+ Ruleset = new OsuRuleset().RulesetInfo,
+ });
var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score));
@@ -105,7 +111,9 @@ namespace osu.Game.Tests.Online
{
var score = new SubmittableScore(new ScoreInfo
{
- Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } }
+ Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } },
+ User = new APIUser(),
+ Ruleset = new OsuRuleset().RulesetInfo,
});
var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score));
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/TestSceneBeatmapDownloading.cs b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs
index 4e77973655..ad9ea79646 100644
--- a/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs
+++ b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs
@@ -5,6 +5,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Models;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Notifications;
using osu.Game.Tests.Visual;
@@ -20,13 +21,19 @@ namespace osu.Game.Tests.Online
private static readonly BeatmapSetInfo test_db_model = new BeatmapSetInfo
{
OnlineID = 1,
- Metadata = new BeatmapMetadata
+ Beatmaps =
{
- Artist = "test author",
- Title = "test title",
- Author = new APIUser
+ new BeatmapInfo
{
- Username = "mapper"
+ Metadata = new BeatmapMetadata
+ {
+ Artist = "test author",
+ Title = "test title",
+ Author = new RealmUser
+ {
+ Username = "mapper"
+ }
+ }
}
}
};
diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
index 239c787349..8c24b2eef8 100644
--- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
+++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
@@ -60,9 +60,8 @@ namespace osu.Game.Tests.Online
testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile);
testBeatmapSet = testBeatmapInfo.BeatmapSet;
- var existing = beatmaps.QueryBeatmapSet(s => s.OnlineID == testBeatmapSet.OnlineID);
- if (existing != null)
- beatmaps.Delete(existing);
+ ContextFactory.Context.Write(r => r.RemoveAll());
+ ContextFactory.Context.Write(r => r.RemoveAll());
selectedItem.Value = new PlaylistItem
{
@@ -100,13 +99,13 @@ 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)));
+ AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID)!.Value));
addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
- AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID)));
+ AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID)!.Value));
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
}
@@ -114,12 +113,12 @@ namespace osu.Game.Tests.Online
public void TestTrackerRespectsChecksum()
{
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
- AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait());
+ 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 not downloaded", BeatmapAvailability.NotDownloaded);
@@ -129,7 +128,7 @@ namespace osu.Game.Tests.Online
});
addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded);
- AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait());
+ AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely());
addAvailabilityCheckStep("locally available after re-import", BeatmapAvailability.LocallyAvailable);
}
@@ -154,7 +153,6 @@ namespace osu.Game.Tests.Online
Debug.Assert(info.BeatmapSet != null);
info.BeatmapSet.Beatmaps.Add(info);
- info.BeatmapSet.Metadata = info.Metadata;
info.MD5Hash = stream.ComputeMD5Hash();
info.Hash = stream.ComputeSHA2Hash();
}
@@ -168,22 +166,22 @@ namespace osu.Game.Tests.Online
public Task> CurrentImportTask { get; private set; }
- public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null)
+ public TestBeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null)
: base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap)
{
}
- protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, GameHost host)
+ protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue)
{
- return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, api, host);
+ return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, onlineLookupQueue);
}
internal class TestBeatmapModelManager : BeatmapModelManager
{
private readonly TestBeatmapManager testBeatmapManager;
- public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, IDatabaseContextFactory databaseContextFactory, RulesetStore rulesetStore, IAPIProvider apiProvider, GameHost gameHost)
- : base(storage, databaseContextFactory, rulesetStore, gameHost)
+ public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmContextFactory databaseContextFactory, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue)
+ : base(databaseContextFactory, storage, beatmapOnlineLookupQueue)
{
this.testBeatmapManager = testBeatmapManager;
}
diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs
index 445394fc77..d2cab09ac9 100644
--- a/osu.Game.Tests/Resources/TestResources.cs
+++ b/osu.Game.Tests/Resources/TestResources.cs
@@ -89,7 +89,7 @@ namespace osu.Game.Tests.Resources
// Create random metadata, then we can check if sorting works based on these
Artist = "Some Artist " + RNG.Next(0, 9),
Title = $"Some Song (set id {setId}) {Guid.NewGuid()}",
- AuthorString = "Some Guy " + RNG.Next(0, 9),
+ Author = { Username = "Some Guy " + RNG.Next(0, 9) },
};
var beatmapSet = new BeatmapSetInfo
@@ -97,7 +97,6 @@ namespace osu.Game.Tests.Resources
OnlineID = setId,
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
DateAdded = DateTimeOffset.UtcNow,
- Metadata = metadata
};
foreach (var b in getBeatmaps(difficultyCount ?? RNG.Next(1, 20)))
@@ -131,10 +130,10 @@ namespace osu.Game.Tests.Resources
StarRating = diff,
Length = length,
BPM = bpm,
+ Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
Ruleset = rulesetInfo,
- RulesetID = rulesetInfo.ID ?? -1,
Metadata = metadata,
- BaseDifficulty = new BeatmapDifficulty
+ Difficulty = new BeatmapDifficulty
{
OverallDifficulty = diff,
}
@@ -166,7 +165,6 @@ namespace osu.Game.Tests.Resources
},
BeatmapInfo = beatmap,
Ruleset = beatmap.Ruleset,
- RulesetID = beatmap.Ruleset.ID ?? 0,
Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() },
TotalScore = 2845370,
Accuracy = 0.95,
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 bbc92b7817..dd12c94855 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -8,8 +8,8 @@ using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions;
using osu.Framework.Platform;
-using osu.Game.Beatmaps;
using osu.Game.IO.Archives;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Mods;
@@ -17,6 +17,8 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
+using osu.Game.Tests.Beatmaps.IO;
+using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Scores.IO
{
@@ -31,6 +33,8 @@ namespace osu.Game.Tests.Scores.IO
{
var osu = LoadOsuIntoHost(host, true);
+ var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
+
var toImport = new ScoreInfo
{
Rank = ScoreRank.B,
@@ -41,6 +45,8 @@ namespace osu.Game.Tests.Scores.IO
User = new APIUser { Username = "Test user" },
Date = DateTimeOffset.Now,
OnlineID = 12345,
+ Ruleset = new OsuRuleset().RulesetInfo,
+ BeatmapInfo = beatmap.Beatmaps.First()
};
var imported = await LoadScoreIntoOsu(osu, toImport);
@@ -49,7 +55,6 @@ namespace osu.Game.Tests.Scores.IO
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
Assert.AreEqual(toImport.Accuracy, imported.Accuracy);
Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo);
- Assert.AreEqual(toImport.Combo, imported.Combo);
Assert.AreEqual(toImport.User.Username, imported.User.Username);
Assert.AreEqual(toImport.Date, imported.Date);
Assert.AreEqual(toImport.OnlineID, imported.OnlineID);
@@ -70,8 +75,13 @@ namespace osu.Game.Tests.Scores.IO
{
var osu = LoadOsuIntoHost(host, true);
+ var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
+
var toImport = new ScoreInfo
{
+ User = new APIUser { Username = "Test user" },
+ BeatmapInfo = beatmap.Beatmaps.First(),
+ Ruleset = new OsuRuleset().RulesetInfo,
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
};
@@ -96,8 +106,13 @@ namespace osu.Game.Tests.Scores.IO
{
var osu = LoadOsuIntoHost(host, true);
+ var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
+
var toImport = new ScoreInfo
{
+ User = new APIUser { Username = "Test user" },
+ BeatmapInfo = beatmap.Beatmaps.First(),
+ Ruleset = new OsuRuleset().RulesetInfo,
Statistics = new Dictionary
{
{ HitResult.Perfect, 100 },
@@ -117,43 +132,6 @@ namespace osu.Game.Tests.Scores.IO
}
}
- [Test]
- public async Task TestImportWithDeletedBeatmapSet()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host, true);
-
- var toImport = new ScoreInfo
- {
- Hash = Guid.NewGuid().ToString(),
- Statistics = new Dictionary
- {
- { HitResult.Perfect, 100 },
- { HitResult.Miss, 50 }
- }
- };
-
- var imported = await LoadScoreIntoOsu(osu, toImport);
-
- var beatmapManager = osu.Dependencies.Get