diff --git a/.gitignore b/.gitignore
index de6a3ac848..5b19270ab9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -339,3 +339,4 @@ inspectcode
# Fody (pulled in by Realm) - schema file
FodyWeavers.xsd
+**/FodyWeavers.xml
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/Gemfile.lock b/Gemfile.lock
index 8ac863c9a8..86c8baabe6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,53 +1,75 @@
GEM
remote: https://rubygems.org/
specs:
- CFPropertyList (3.0.3)
- addressable (2.7.0)
+ CFPropertyList (3.0.5)
+ rexml
+ addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
+ artifactory (3.0.15)
atomos (0.1.3)
- aws-eventstream (1.1.0)
- aws-partitions (1.413.0)
- aws-sdk-core (3.110.0)
+ aws-eventstream (1.2.0)
+ aws-partitions (1.551.0)
+ aws-sdk-core (3.125.5)
aws-eventstream (~> 1, >= 1.0.2)
- aws-partitions (~> 1, >= 1.239.0)
+ aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
- aws-sdk-kms (1.40.0)
- aws-sdk-core (~> 3, >= 3.109.0)
+ aws-sdk-kms (1.53.0)
+ aws-sdk-core (~> 3, >= 3.125.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.87.0)
- aws-sdk-core (~> 3, >= 3.109.0)
+ aws-sdk-s3 (1.111.3)
+ aws-sdk-core (~> 3, >= 3.125.0)
aws-sdk-kms (~> 1)
- aws-sigv4 (~> 1.1)
- aws-sigv4 (1.2.2)
+ aws-sigv4 (~> 1.4)
+ aws-sigv4 (1.4.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
- claide (1.0.3)
+ claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.6)
highline (~> 1.7.2)
declarative (0.0.20)
- declarative-option (0.1.0)
- digest-crc (0.6.3)
+ digest-crc (0.6.4)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
- emoji_regex (3.2.1)
- excon (0.78.1)
- faraday (1.2.0)
- multipart-post (>= 1.2, < 3)
- ruby2_keywords
+ emoji_regex (3.2.3)
+ excon (0.90.0)
+ faraday (1.9.3)
+ faraday-em_http (~> 1.0)
+ faraday-em_synchrony (~> 1.0)
+ faraday-excon (~> 1.1)
+ faraday-httpclient (~> 1.0)
+ faraday-multipart (~> 1.0)
+ faraday-net_http (~> 1.0)
+ faraday-net_http_persistent (~> 1.0)
+ faraday-patron (~> 1.0)
+ faraday-rack (~> 1.0)
+ faraday-retry (~> 1.0)
+ ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
- faraday_middleware (1.0.0)
+ faraday-em_http (1.0.0)
+ faraday-em_synchrony (1.0.0)
+ faraday-excon (1.1.0)
+ faraday-httpclient (1.0.1)
+ faraday-multipart (1.0.3)
+ multipart-post (>= 1.2, < 3)
+ faraday-net_http (1.0.1)
+ faraday-net_http_persistent (1.2.0)
+ faraday-patron (1.0.0)
+ faraday-rack (1.0.0)
+ faraday-retry (1.0.3)
+ faraday_middleware (1.2.0)
faraday (~> 1.0)
- fastimage (2.2.1)
- fastlane (2.170.0)
+ fastimage (2.2.6)
+ fastlane (2.181.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
+ artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
@@ -68,6 +90,7 @@ GEM
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (~> 2.0.0)
+ naturally (~> 2.2)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
@@ -94,65 +117,80 @@ GEM
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.12)
- google-cloud-core (1.5.0)
+ google-apis-core (0.4.2)
+ addressable (~> 2.5, >= 2.5.1)
+ googleauth (>= 0.16.2, < 2.a)
+ httpclient (>= 2.8.1, < 3.a)
+ mini_mime (~> 1.0)
+ representable (~> 3.0)
+ retriable (>= 2.0, < 4.a)
+ rexml
+ webrick
+ google-apis-iamcredentials_v1 (0.10.0)
+ google-apis-core (>= 0.4, < 2.a)
+ google-apis-storage_v1 (0.11.0)
+ google-apis-core (>= 0.4, < 2.a)
+ google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
- google-cloud-env (1.4.0)
+ google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0)
- google-cloud-errors (1.0.1)
- google-cloud-storage (1.29.2)
- addressable (~> 2.5)
+ google-cloud-errors (1.2.0)
+ google-cloud-storage (1.36.0)
+ addressable (~> 2.8)
digest-crc (~> 0.4)
- google-api-client (~> 0.33)
- google-cloud-core (~> 1.2)
- googleauth (~> 0.9)
+ google-apis-iamcredentials_v1 (~> 0.1)
+ google-apis-storage_v1 (~> 0.1)
+ google-cloud-core (~> 1.6)
+ googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
- googleauth (0.14.0)
+ googleauth (0.17.1)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
- signet (~> 0.14)
+ signet (~> 0.15)
highline (1.7.10)
- http-cookie (1.0.3)
+ http-cookie (1.0.4)
domain_name (~> 0.5)
httpclient (2.8.3)
- jmespath (1.4.0)
- json (2.5.1)
- jwt (2.2.2)
+ jmespath (1.5.0)
+ json (2.6.1)
+ jwt (2.3.0)
memoist (0.16.2)
mini_magick (4.11.0)
- mini_mime (1.0.2)
+ mini_mime (1.1.2)
mini_portile2 (2.4.0)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
- naturally (2.2.0)
+ naturally (2.2.1)
nokogiri (1.10.10)
mini_portile2 (~> 2.4.0)
- os (1.1.1)
- plist (3.5.0)
+ os (1.1.4)
+ plist (3.6.0)
public_suffix (4.0.6)
- rake (13.0.3)
- representable (3.0.4)
+ rake (13.0.6)
+ representable (3.1.1)
declarative (< 0.1.0)
- declarative-option (< 0.2.0)
+ trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
+ rexml (3.2.5)
rouge (2.0.7)
- ruby2_keywords (0.0.2)
- rubyzip (2.3.0)
+ ruby2_keywords (0.0.5)
+ rubyzip (2.3.2)
security (0.1.3)
- signet (0.14.0)
- addressable (~> 2.3)
+ signet (0.16.0)
+ addressable (~> 2.8)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
CFPropertyList
naturally
- slack-notifier (2.3.2)
+ slack-notifier (2.4.0)
souyuz (0.9.1)
fastlane (>= 1.103.0)
highline (~> 1.7)
@@ -160,6 +198,7 @@ GEM
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
+ trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
@@ -167,18 +206,20 @@ GEM
uber (0.1.0)
unf (0.1.4)
unf_ext
- unf_ext (0.0.7.7)
- unicode-display_width (1.7.0)
+ unf_ext (0.0.8)
+ unicode-display_width (1.8.0)
+ webrick (1.7.0)
word_wrap (1.0.0)
- xcodeproj (1.19.0)
+ xcodeproj (1.21.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
+ rexml (~> 3.2.4)
xcpretty (0.3.0)
rouge (~> 2.0.7)
- xcpretty-travis-formatter (1.0.0)
+ xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
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-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs
index 4f810ce17f..03ee7c9204 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
{
host.Run(new OsuTestBrowser());
return 0;
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs
index cc4483de31..a9bc8dc10e 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays
protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any();
- public override void CollectPendingInputs(List inputs)
+ protected override void CollectReplayInputs(List inputs)
{
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
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-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
index fd6bd9b714..55c0cf6a3b 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
{
host.Run(new OsuTestBrowser());
return 0;
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs
index e005346e1e..dbfaf8a01d 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays
protected override bool IsImportant(PippidonReplayFrame frame) => true;
- public override void CollectPendingInputs(List inputs)
+ protected override void CollectReplayInputs(List inputs)
{
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
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-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs
index 65cfb2bff4..b45505678c 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
{
host.Run(new OsuTestBrowser());
return 0;
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs
index 4b998cfca3..1d33ab8a54 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Replays
protected override bool IsImportant(EmptyScrollingReplayFrame frame) => frame.Actions.Any();
- public override void CollectPendingInputs(List inputs)
+ protected override void CollectReplayInputs(List inputs)
{
inputs.Add(new ReplayState
{
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.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
index fd6bd9b714..55c0cf6a3b 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
{
host.Run(new OsuTestBrowser());
return 0;
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs
index 7652357b4d..702f6fdb04 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays
protected override bool IsImportant(PippidonReplayFrame frame) => frame.Actions.Any();
- public override void CollectPendingInputs(List inputs)
+ protected override void CollectReplayInputs(List inputs)
{
inputs.Add(new ReplayState
{
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/fastlane/README.md b/fastlane/README.md
index a400ed9516..8273fdaa5d 100644
--- a/fastlane/README.md
+++ b/fastlane/README.md
@@ -12,7 +12,7 @@ Install _fastlane_ using
```
[sudo] gem install fastlane -NV
```
-or alternatively using `brew cask install fastlane`
+or alternatively using `brew install fastlane`
# Available Actions
## Android
diff --git a/osu.Android.props b/osu.Android.props
index 67a9cd41dd..2902c74f0a 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,11 +51,11 @@
-
-
+
+
-
+
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/Program.cs b/osu.Desktop/Program.cs
index 7ec7d53a7e..b944068e78 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -55,7 +55,7 @@ namespace osu.Desktop
}
}
- using (DesktopGameHost host = Host.GetSuitableHost(gameName, true))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { BindIPC = true }))
{
host.ExceptionThrown += handleException;
diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs
index 62ea3e0399..8f3ad853dc 100644
--- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs
+++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs
@@ -73,7 +73,7 @@ namespace osu.Desktop.Security
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours, NotificationOverlay notificationOverlay)
+ private void load(OsuColour colours)
{
Icon = FontAwesome.Solid.ShieldAlt;
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.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs
new file mode 100644
index 0000000000..bf9467700c
--- /dev/null
+++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs
@@ -0,0 +1,141 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using System.Threading;
+using BenchmarkDotNet.Attributes;
+using osu.Framework.Testing;
+using osu.Framework.Threading;
+using osu.Game.Beatmaps;
+using osu.Game.Database;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Tests.Resources;
+
+namespace osu.Game.Benchmarks
+{
+ public class BenchmarkRealmReads : BenchmarkTest
+ {
+ private TemporaryNativeStorage storage;
+ private RealmAccess realm;
+ private UpdateThread updateThread;
+
+ [Params(1, 100, 1000)]
+ public int ReadsPerFetch { get; set; }
+
+ public override void SetUp()
+ {
+ storage = new TemporaryNativeStorage("realm-benchmark");
+ storage.DeleteDirectory(string.Empty);
+
+ realm = new RealmAccess(storage, "client");
+
+ realm.Run(r =>
+ {
+ realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo })));
+ });
+
+ updateThread = new UpdateThread(() => { }, null);
+ updateThread.Start();
+ }
+
+ [Benchmark]
+ public void BenchmarkDirectPropertyRead()
+ {
+ realm.Run(r =>
+ {
+ var beatmapSet = r.All().First();
+
+ for (int i = 0; i < ReadsPerFetch; i++)
+ {
+ string _ = beatmapSet.Beatmaps.First().Hash;
+ }
+ });
+ }
+
+ [Benchmark]
+ public void BenchmarkDirectPropertyReadUpdateThread()
+ {
+ var done = new ManualResetEventSlim();
+
+ updateThread.Scheduler.Add(() =>
+ {
+ try
+ {
+ var beatmapSet = realm.Realm.All().First();
+
+ for (int i = 0; i < ReadsPerFetch; i++)
+ {
+ string _ = beatmapSet.Beatmaps.First().Hash;
+ }
+ }
+ finally
+ {
+ done.Set();
+ }
+ });
+
+ done.Wait();
+ }
+
+ [Benchmark]
+ public void BenchmarkRealmLivePropertyRead()
+ {
+ realm.Run(r =>
+ {
+ var beatmapSet = r.All().First().ToLive(realm);
+
+ for (int i = 0; i < ReadsPerFetch; i++)
+ {
+ string _ = beatmapSet.PerformRead(b => b.Beatmaps.First().Hash);
+ }
+ });
+ }
+
+ [Benchmark]
+ public void BenchmarkRealmLivePropertyReadUpdateThread()
+ {
+ var done = new ManualResetEventSlim();
+
+ updateThread.Scheduler.Add(() =>
+ {
+ try
+ {
+ var beatmapSet = realm.Realm.All().First().ToLive(realm);
+
+ for (int i = 0; i < ReadsPerFetch; i++)
+ {
+ string _ = beatmapSet.PerformRead(b => b.Beatmaps.First().Hash);
+ }
+ }
+ finally
+ {
+ done.Set();
+ }
+ });
+
+ done.Wait();
+ }
+
+ [Benchmark]
+ public void BenchmarkDetachedPropertyRead()
+ {
+ realm.Run(r =>
+ {
+ var beatmapSet = r.All().First().Detach();
+
+ for (int i = 0; i < ReadsPerFetch; i++)
+ {
+ string _ = beatmapSet.Beatmaps.First().Hash;
+ }
+ });
+ }
+
+ [GlobalCleanup]
+ public void Cleanup()
+ {
+ realm?.Dispose();
+ storage?.Dispose();
+ updateThread?.Exit();
+ }
+ }
+}
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 23f6222eb6..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
},
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/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/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
index f399f48ebd..2d92c925d7 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
@@ -3,6 +3,7 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
@@ -15,9 +16,26 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public override double ScoreMultiplier => 1.12;
- private const float default_flashlight_size = 350;
+ [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
+ public override BindableNumber SizeMultiplier { get; } = new BindableNumber
+ {
+ MinValue = 0.5f,
+ MaxValue = 1.5f,
+ Default = 1f,
+ Value = 1f,
+ Precision = 0.1f
+ };
- public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield);
+ [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
+ public override BindableBool ComboBasedSize { get; } = new BindableBool
+ {
+ Default = true,
+ Value = true
+ };
+
+ public override float DefaultFlashlightSize => 350;
+
+ protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield);
private CatchPlayfield playfield;
@@ -31,10 +49,11 @@ namespace osu.Game.Rulesets.Catch.Mods
{
private readonly CatchPlayfield playfield;
- public CatchFlashlight(CatchPlayfield playfield)
+ public CatchFlashlight(CatchModFlashlight modFlashlight, CatchPlayfield playfield)
+ : base(modFlashlight)
{
this.playfield = playfield;
- FlashlightSize = new Vector2(0, getSizeFor(0));
+ FlashlightSize = new Vector2(0, GetSizeFor(0));
}
protected override void Update()
@@ -44,19 +63,9 @@ namespace osu.Game.Rulesets.Catch.Mods
FlashlightPosition = playfield.CatcherArea.ToSpaceOfOtherDrawable(playfield.Catcher.DrawPosition, this);
}
- private float getSizeFor(int combo)
- {
- if (combo > 200)
- return default_flashlight_size * 0.8f;
- else if (combo > 100)
- return default_flashlight_size * 0.9f;
- else
- return default_flashlight_size;
- }
-
protected override void OnComboChange(ValueChangedEvent e)
{
- this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
+ this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
index bd742ce6a6..b6af88a771 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Replays
protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any();
- public override void CollectPendingInputs(List inputs)
+ protected override void CollectReplayInputs(List inputs)
{
float position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
index faad95e386..b2a555f89d 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
@@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
case CatchSkinComponents.CatchComboCounter:
if (providesComboCounter)
- return new LegacyCatchComboCounter(Skin);
+ return new LegacyCatchComboCounter();
return null;
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs
index 33c3867f5a..b4d29988d9 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
private readonly LegacyRollingCounter explosion;
- public LegacyCatchComboCounter(ISkin skin)
+ public LegacyCatchComboCounter()
{
AutoSizeAxes = Axes.Both;
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/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs
new file mode 100644
index 0000000000..7970d5b594
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs
@@ -0,0 +1,103 @@
+// 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.Mania.Mods;
+using osu.Game.Tests.Visual;
+using System.Collections.Generic;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mania.Beatmaps;
+
+namespace osu.Game.Rulesets.Mania.Tests.Mods
+{
+ public class TestSceneManiaModHoldOff : ModTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
+
+ [Test]
+ public void TestMapHasNoHoldNotes()
+ {
+ var testBeatmap = createModdedBeatmap();
+ Assert.False(testBeatmap.HitObjects.OfType().Any());
+ }
+
+ [Test]
+ public void TestCorrectNoteValues()
+ {
+ var testBeatmap = createRawBeatmap();
+ var noteValues = new List(testBeatmap.HitObjects.OfType().Count());
+
+ foreach (HoldNote h in testBeatmap.HitObjects.OfType())
+ {
+ noteValues.Add(ManiaModHoldOff.GetNoteDurationInBeatLength(h, testBeatmap));
+ }
+
+ noteValues.Sort();
+ Assert.AreEqual(noteValues, new List { 0.125, 0.250, 0.500, 1.000, 2.000 });
+ }
+
+ [Test]
+ public void TestCorrectObjectCount()
+ {
+ // Ensure that the mod produces the expected number of objects when applied.
+
+ var rawBeatmap = createRawBeatmap();
+ var testBeatmap = createModdedBeatmap();
+
+ // Calculate expected number of objects
+ int expectedObjectCount = 0;
+
+ foreach (ManiaHitObject h in rawBeatmap.HitObjects)
+ {
+ // Both notes and hold notes account for at least one object
+ expectedObjectCount++;
+
+ if (h.GetType() == typeof(HoldNote))
+ {
+ double noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, rawBeatmap);
+
+ if (noteValue >= ManiaModHoldOff.END_NOTE_ALLOW_THRESHOLD)
+ {
+ // Should generate an end note if it's longer than the minimum note value
+ expectedObjectCount++;
+ }
+ }
+ }
+
+ Assert.That(testBeatmap.HitObjects.Count == expectedObjectCount);
+ }
+
+ private static ManiaBeatmap createModdedBeatmap()
+ {
+ var beatmap = createRawBeatmap();
+ var holdOffMod = new ManiaModHoldOff();
+
+ foreach (var hitObject in beatmap.HitObjects)
+ hitObject.ApplyDefaults(beatmap.ControlPointInfo, new BeatmapDifficulty());
+
+ holdOffMod.ApplyToBeatmap(beatmap);
+
+ return beatmap;
+ }
+
+ private static ManiaBeatmap createRawBeatmap()
+ {
+ var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
+ beatmap.ControlPointInfo.Add(0.0, new TimingControlPoint { BeatLength = 1000 }); // Set BPM to 60
+
+ // Add test hit objects
+ beatmap.HitObjects.Add(new Note { StartTime = 4000 });
+ beatmap.HitObjects.Add(new Note { StartTime = 4500 });
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 125 }); // 1/8 note
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 250 }); // 1/4 note
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 500 }); // 1/2 note
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 1000 }); // 1/1 note
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 2000 }); // 2/1 note
+
+ return beatmap;
+ }
+ }
+}
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/TestSceneColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs
index 215f8fb1d5..8034341d15 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
- Child = new ColumnHitObjectArea(0, new HitObjectContainer())
+ Child = new ColumnHitObjectArea(new HitObjectContainer())
{
RelativeSizeAxes = Axes.Both
}
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
- Child = new ColumnHitObjectArea(1, new HitObjectContainer())
+ Child = new ColumnHitObjectArea(new HitObjectContainer())
{
RelativeSizeAxes = Axes.Both
}
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/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/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/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/ManiaFilterCriteria.cs b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs
index 0290230490..c8832dfdfb 100644
--- a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs
+++ b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania
public bool Matches(BeatmapInfo beatmapInfo)
{
- return !keys.HasFilter || (beatmapInfo.RulesetID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo)));
+ return !keys.HasFilter || (beatmapInfo.Ruleset.OnlineID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo)));
}
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value)
diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs
index 186fc4b15d..14ca27a11a 100644
--- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs
+++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs
@@ -2,11 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
+using osu.Framework.Allocation;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania
{
+ [Cached] // Used for touch input, see ColumnTouchInputArea.
public class ManiaInputManager : RulesetInputManager
{
public ManiaInputManager(RulesetInfo ruleset, int variant)
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index b0e7545d3e..2098c7f5d8 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);
@@ -243,7 +243,8 @@ namespace osu.Game.Rulesets.Mania
new ManiaModDifficultyAdjust(),
new ManiaModClassic(),
new ManiaModInvert(),
- new ManiaModConstantSpeed()
+ new ManiaModConstantSpeed(),
+ new ManiaModHoldOff()
};
case ModType.Automation:
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
index 86a00271e9..1ee4ea12e3 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
@@ -5,6 +5,7 @@ using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Layout;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
using osuTK;
@@ -16,17 +17,35 @@ namespace osu.Game.Rulesets.Mania.Mods
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModHidden) };
- private const float default_flashlight_size = 180;
+ [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
+ public override BindableNumber SizeMultiplier { get; } = new BindableNumber
+ {
+ MinValue = 0.5f,
+ MaxValue = 3f,
+ Default = 1f,
+ Value = 1f,
+ Precision = 0.1f
+ };
- public override Flashlight CreateFlashlight() => new ManiaFlashlight();
+ [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
+ public override BindableBool ComboBasedSize { get; } = new BindableBool
+ {
+ Default = false,
+ Value = false
+ };
+
+ public override float DefaultFlashlightSize => 50;
+
+ protected override Flashlight CreateFlashlight() => new ManiaFlashlight(this);
private class ManiaFlashlight : Flashlight
{
private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize);
- public ManiaFlashlight()
+ public ManiaFlashlight(ManiaModFlashlight modFlashlight)
+ : base(modFlashlight)
{
- FlashlightSize = new Vector2(0, default_flashlight_size);
+ FlashlightSize = new Vector2(DrawWidth, GetSizeFor(0));
AddLayout(flashlightProperties);
}
@@ -46,6 +65,7 @@ namespace osu.Game.Rulesets.Mania.Mods
protected override void OnComboChange(ValueChangedEvent e)
{
+ this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "RectangularFlashlight";
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs
new file mode 100644
index 0000000000..a65938184c
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs
@@ -0,0 +1,72 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mods;
+using osu.Framework.Graphics.Sprites;
+using System.Collections.Generic;
+using osu.Game.Rulesets.Mania.Beatmaps;
+
+namespace osu.Game.Rulesets.Mania.Mods
+{
+ public class ManiaModHoldOff : Mod, IApplicableAfterBeatmapConversion
+ {
+ public override string Name => "Hold Off";
+
+ public override string Acronym => "HO";
+
+ public override double ScoreMultiplier => 1;
+
+ public override string Description => @"Replaces all hold notes with normal notes.";
+
+ public override IconUsage? Icon => FontAwesome.Solid.DotCircle;
+
+ public override ModType Type => ModType.Conversion;
+
+ public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert) };
+
+ public const double END_NOTE_ALLOW_THRESHOLD = 0.5;
+
+ public void ApplyToBeatmap(IBeatmap beatmap)
+ {
+ var maniaBeatmap = (ManiaBeatmap)beatmap;
+
+ var newObjects = new List();
+
+ foreach (var h in beatmap.HitObjects.OfType())
+ {
+ // Add a note for the beginning of the hold note
+ newObjects.Add(new Note
+ {
+ Column = h.Column,
+ StartTime = h.StartTime,
+ Samples = h.GetNodeSamples(0)
+ });
+
+ // Don't add an end note if the duration is shorter than the threshold
+ double noteValue = GetNoteDurationInBeatLength(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc.
+
+ if (noteValue >= END_NOTE_ALLOW_THRESHOLD)
+ {
+ newObjects.Add(new Note
+ {
+ Column = h.Column,
+ StartTime = h.EndTime,
+ Samples = h.GetNodeSamples((h.NodeSamples?.Count - 1) ?? 1)
+ });
+ }
+ }
+
+ maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList();
+ }
+
+ public static double GetNoteDurationInBeatLength(HoldNote holdNote, ManiaBeatmap beatmap)
+ {
+ double beatLength = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BeatLength;
+ return holdNote.Duration / beatLength;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs
index 1ea45c295c..4cbdaee323 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs
@@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Mania.Mods
public override ModType Type => ModType.Conversion;
+ public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) };
+
public void ApplyToBeatmap(IBeatmap beatmap)
{
var maniaBeatmap = (ManiaBeatmap)beatmap;
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
index aa0c148caf..aa164f95da 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Replays
protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any();
- public override void CollectPendingInputs(List inputs)
+ protected override void CollectReplayInputs(List inputs)
{
inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() });
}
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/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index 9d060944cd..a04f5ef98e 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -62,13 +62,14 @@ namespace osu.Game.Rulesets.Mania.UI
sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer),
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
background.CreateProxy(),
- HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
+ HitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both },
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
{
RelativeSizeAxes = Axes.Both
},
background,
- TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
+ TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both },
+ new ColumnTouchInputArea(this)
};
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
@@ -139,5 +140,50 @@ namespace osu.Game.Rulesets.Mania.UI
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
// This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border
=> DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos));
+
+ public class ColumnTouchInputArea : Drawable
+ {
+ private readonly Column column;
+
+ [Resolved(canBeNull: true)]
+ private ManiaInputManager maniaInputManager { get; set; }
+
+ private KeyBindingContainer keyBindingContainer;
+
+ public ColumnTouchInputArea(Column column)
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ this.column = column;
+ }
+
+ protected override void LoadComplete()
+ {
+ keyBindingContainer = maniaInputManager?.KeyBindingContainer;
+ }
+
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ keyBindingContainer?.TriggerPressed(column.Action.Value);
+ return base.OnMouseDown(e);
+ }
+
+ protected override void OnMouseUp(MouseUpEvent e)
+ {
+ keyBindingContainer?.TriggerReleased(column.Action.Value);
+ base.OnMouseUp(e);
+ }
+
+ protected override bool OnTouchDown(TouchDownEvent e)
+ {
+ keyBindingContainer?.TriggerPressed(column.Action.Value);
+ return true;
+ }
+
+ protected override void OnTouchUp(TouchUpEvent e)
+ {
+ keyBindingContainer?.TriggerReleased(column.Action.Value);
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
index f69d2aafdc..51c138f5e1 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
private readonly Drawable hitTarget;
- public ColumnHitObjectArea(int columnIndex, HitObjectContainer hitObjectContainer)
+ public ColumnHitObjectArea(HitObjectContainer hitObjectContainer)
: base(hitObjectContainer)
{
AddRangeInternal(new[]
diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs
index 267ed1f5f4..15018b464f 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs
@@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
AlwaysPresent = true
}
}
- }
+ },
}
};
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/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/Editor/TestSceneSliderVelocityAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs
new file mode 100644
index 0000000000..4750c97566
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs
@@ -0,0 +1,98 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Diagnostics;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Input;
+using osu.Framework.Testing;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Edit;
+using osu.Game.Screens.Edit.Compose.Components.Timeline;
+using osu.Game.Screens.Edit.Timing;
+using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Visual;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Osu.Tests.Editor
+{
+ public class TestSceneSliderVelocityAdjust : OsuGameTestScene
+ {
+ private Screens.Edit.Editor editor => Game.ScreenStack.CurrentScreen as Screens.Edit.Editor;
+
+ private EditorBeatmap editorBeatmap => editor.ChildrenOfType().FirstOrDefault();
+
+ private EditorClock editorClock => editor.ChildrenOfType().FirstOrDefault();
+
+ private Slider slider => editorBeatmap.HitObjects.OfType().FirstOrDefault();
+
+ private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType().FirstOrDefault();
+
+ private DifficultyPointPiece difficultyPointPiece => blueprint.ChildrenOfType().First();
+
+ private IndeterminateSliderWithTextBoxInput velocityTextBox => Game.ChildrenOfType().First().ChildrenOfType>().First();
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
+
+ private bool editorComponentsReady => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true
+ && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true
+ && editor?.ChildrenOfType().FirstOrDefault()?.IsLoaded == true;
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestVelocityChangeSavesCorrectly(bool adjustVelocity)
+ {
+ double? velocity = null;
+
+ AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader()));
+ AddUntilStep("wait for editor load", () => editorComponentsReady);
+
+ AddStep("seek to first control point", () => editorClock.Seek(editorBeatmap.ControlPointInfo.TimingPoints.First().Time));
+ AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3));
+
+ AddStep("move mouse to centre", () => InputManager.MoveMouseTo(editor.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre));
+ AddStep("start placement", () => InputManager.Click(MouseButton.Left));
+
+ AddStep("move mouse to bottom right", () => InputManager.MoveMouseTo(editor.ChildrenOfType().First().ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
+ AddStep("end placement", () => InputManager.Click(MouseButton.Right));
+
+ AddStep("exit placement mode", () => InputManager.Key(Key.Number1));
+
+ AddAssert("slider placed", () => slider != null);
+
+ AddStep("select slider", () => editorBeatmap.SelectedHitObjects.Add(slider));
+
+ AddAssert("ensure one slider placed", () => slider != null);
+
+ AddStep("store velocity", () => velocity = slider.Velocity);
+
+ if (adjustVelocity)
+ {
+ AddStep("open velocity adjust panel", () => difficultyPointPiece.TriggerClick());
+ AddStep("change velocity", () => velocityTextBox.Current.Value = 2);
+
+ AddAssert("velocity adjusted", () =>
+ {
+ Debug.Assert(velocity != null);
+ return Precision.AlmostEquals(velocity.Value * 2, slider.Velocity);
+ });
+
+ AddStep("store velocity", () => velocity = slider.Velocity);
+ }
+
+ AddStep("save", () => InputManager.Keys(PlatformAction.Save));
+ AddStep("exit", () => InputManager.Key(Key.Escape));
+
+ AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader()));
+ AddUntilStep("wait for editor load", () => editorComponentsReady);
+
+ AddStep("seek to slider", () => editorClock.Seek(slider.StartTime));
+ AddAssert("slider has correct velocity", () => slider.Velocity == velocity);
+ }
+ }
+}
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/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs
index 8e226c7ded..44404ca245 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs
@@ -145,6 +145,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
private bool isBreak() => Player.IsBreakTime.Value;
- private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha);
+ private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha, 0.1f);
}
}
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/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
index 1f01ba601b..a36f07ff7b 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
@@ -118,7 +118,6 @@ namespace osu.Game.Rulesets.Osu.Tests
public Drawable GetDrawableComponent(ISkinComponent component) => null;
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
public ISample GetSample(ISampleInfo sampleInfo) => null;
- public ISkin FindProvider(Func lookupFunction) => null;
public IBindable GetConfig(TLookup lookup)
{
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/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/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index d7d294df47..604ab73454 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -25,7 +25,7 @@ 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)
@@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
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))
@@ -97,7 +97,7 @@ 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)
- 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);
aimValue *= getComboScalingFactor();
@@ -144,7 +144,7 @@ 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)
- 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));
speedValue *= getComboScalingFactor();
@@ -228,7 +228,7 @@ 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));
flashlightValue *= getComboScalingFactor();
@@ -244,7 +244,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;
@@ -256,10 +256,10 @@ 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);
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 065d4737a5..ae4141073e 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
@@ -283,6 +283,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
}
}
+ // 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++)
@@ -290,6 +293,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
slider.Position = oldPosition;
slider.StartTime = oldStartTime;
+ // Snap the path length again to undo the invalid length.
+ slider.SnapTo(snapProvider);
return;
}
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 2aebe05c2f..6cf2a493a9 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -80,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);
}
@@ -208,6 +208,8 @@ 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, pathControlPoint);
+ HitObject.SnapTo(composer);
+
return pathControlPoint;
}
@@ -227,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);
@@ -242,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/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
index 300a9d48aa..b4eff57c55 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
@@ -12,7 +12,6 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Rulesets.UI;
using osuTK;
namespace osu.Game.Rulesets.Osu.Mods
@@ -21,27 +20,8 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override double ScoreMultiplier => 1.12;
- private const float default_flashlight_size = 180;
-
private const double default_follow_delay = 120;
- private OsuFlashlight flashlight;
-
- public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight();
-
- public void ApplyToDrawableHitObject(DrawableHitObject drawable)
- {
- if (drawable is DrawableSlider s)
- s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange;
- }
-
- public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
- {
- base.ApplyToDrawableRuleset(drawableRuleset);
-
- flashlight.FollowDelay = FollowDelay.Value;
- }
-
[SettingSource("Follow delay", "Milliseconds until the flashlight reaches the cursor")]
public BindableNumber FollowDelay { get; } = new BindableDouble(default_follow_delay)
{
@@ -50,13 +30,45 @@ namespace osu.Game.Rulesets.Osu.Mods
Precision = default_follow_delay,
};
+ [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
+ public override BindableNumber SizeMultiplier { get; } = new BindableNumber
+ {
+ MinValue = 0.5f,
+ MaxValue = 2f,
+ Default = 1f,
+ Value = 1f,
+ Precision = 0.1f
+ };
+
+ [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
+ public override BindableBool ComboBasedSize { get; } = new BindableBool
+ {
+ Default = true,
+ Value = true
+ };
+
+ public override float DefaultFlashlightSize => 180;
+
+ private OsuFlashlight flashlight;
+
+ protected override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(this);
+
+ public void ApplyToDrawableHitObject(DrawableHitObject drawable)
+ {
+ if (drawable is DrawableSlider s)
+ s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange;
+ }
+
private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition
{
- public double FollowDelay { private get; set; }
+ private readonly double followDelay;
- public OsuFlashlight()
+ public OsuFlashlight(OsuModFlashlight modFlashlight)
+ : base(modFlashlight)
{
- FlashlightSize = new Vector2(0, getSizeFor(0));
+ followDelay = modFlashlight.FollowDelay.Value;
+
+ FlashlightSize = new Vector2(0, GetSizeFor(0));
}
public void OnSliderTrackingChange(ValueChangedEvent e)
@@ -71,24 +83,14 @@ namespace osu.Game.Rulesets.Osu.Mods
var destination = e.MousePosition;
FlashlightPosition = Interpolation.ValueAt(
- Math.Min(Math.Abs(Clock.ElapsedFrameTime), FollowDelay), position, destination, 0, FollowDelay, Easing.Out);
+ Math.Min(Math.Abs(Clock.ElapsedFrameTime), followDelay), position, destination, 0, followDelay, Easing.Out);
return base.OnMouseMove(e);
}
- private float getSizeFor(int combo)
- {
- if (combo > 200)
- return default_flashlight_size * 0.8f;
- else if (combo > 100)
- return default_flashlight_size * 0.9f;
- else
- return default_flashlight_size;
- }
-
protected override void OnComboChange(ValueChangedEvent e)
{
- this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
+ this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";
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/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs
index 7d696dfb79..ea36ecc399 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Replays
protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any();
- public override void CollectPendingInputs(List inputs)
+ protected override void CollectReplayInputs(List inputs)
{
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
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/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/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/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/TestSceneEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs
deleted file mode 100644
index 42ab84714a..0000000000
--- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs
+++ /dev/null
@@ -1,91 +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.Linq;
-using NUnit.Framework;
-using osu.Framework.Input;
-using osu.Framework.Testing;
-using osu.Framework.Utils;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Taiko.Beatmaps;
-using osu.Game.Screens.Edit;
-using osu.Game.Screens.Edit.Setup;
-using osu.Game.Screens.Menu;
-using osu.Game.Screens.Select;
-using osu.Game.Tests.Visual;
-using osuTK.Input;
-
-namespace osu.Game.Rulesets.Taiko.Tests.Editor
-{
- public class TestSceneEditorSaving : OsuGameTestScene
- {
- private Screens.Edit.Editor editor => Game.ChildrenOfType().FirstOrDefault();
-
- private EditorBeatmap editorBeatmap => (EditorBeatmap)editor.Dependencies.Get(typeof(EditorBeatmap));
-
- ///
- /// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select.
- /// Emphasis is placed on , since taiko has special handling for it to keep compatibility with stable.
- ///
- [Test]
- public void TestNewBeatmapSaveThenLoad()
- {
- AddStep("set default beatmap", () => Game.Beatmap.SetDefault());
- AddStep("set taiko ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo);
-
- PushAndConfirm(() => new EditorLoader());
-
- AddUntilStep("wait for editor load", () => editor?.IsLoaded == true);
-
- AddUntilStep("wait for metadata screen load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
-
- // We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten.
-
- AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
- AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
-
- AddStep("Set slider multiplier", () => editorBeatmap.Difficulty.SliderMultiplier = 2);
- AddStep("Set artist and title", () =>
- {
- editorBeatmap.BeatmapInfo.Metadata.Artist = "artist";
- editorBeatmap.BeatmapInfo.Metadata.Title = "title";
- });
- AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty");
-
- checkMutations();
-
- AddStep("Save", () => InputManager.Keys(PlatformAction.Save));
-
- checkMutations();
-
- AddStep("Exit", () => InputManager.Key(Key.Escape));
-
- AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
-
- PushAndConfirm(() => new PlaySongSelect());
-
- AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault);
- AddStep("Open options", () => InputManager.Key(Key.F3));
- AddStep("Enter editor", () => InputManager.Key(Key.Number5));
-
- AddUntilStep("Wait for editor load", () => editor != null);
-
- checkMutations();
- }
-
- private void checkMutations()
- {
- AddAssert("Beatmap has correct slider multiplier", () =>
- {
- // we can only assert value correctness on TaikoMultiplierAppliedDifficulty, because that is the final difficulty converted taiko beatmaps use.
- // therefore, ensure that we have that difficulty type by calling .CopyFrom(), which is a no-op if the type is already correct.
- var taikoDifficulty = new TaikoBeatmapConverter.TaikoMultiplierAppliedDifficulty();
- taikoDifficulty.CopyFrom(editorBeatmap.Difficulty);
- return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2);
- });
- AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title");
- AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
- }
- }
-}
diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs
new file mode 100644
index 0000000000..33c2ba532e
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs
@@ -0,0 +1,38 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Taiko.Beatmaps;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests.Editor
+{
+ public class TestSceneTaikoEditorSaving : EditorSavingTestScene
+ {
+ protected override Ruleset CreateRuleset() => new TaikoRuleset();
+
+ [Test]
+ public void TestTaikoSliderMultiplier()
+ {
+ AddStep("Set slider multiplier", () => EditorBeatmap.Difficulty.SliderMultiplier = 2);
+
+ SaveEditor();
+
+ AddAssert("Beatmap has correct slider multiplier", assertTaikoSliderMulitplier);
+
+ ReloadEditorToSameBeatmap();
+
+ AddAssert("Beatmap still has correct slider multiplier", assertTaikoSliderMulitplier);
+
+ bool assertTaikoSliderMulitplier()
+ {
+ // we can only assert value correctness on TaikoMultiplierAppliedDifficulty, because that is the final difficulty converted taiko beatmaps use.
+ // therefore, ensure that we have that difficulty type by calling .CopyFrom(), which is a no-op if the type is already correct.
+ var taikoDifficulty = new TaikoBeatmapConverter.TaikoMultiplierAppliedDifficulty();
+ taikoDifficulty.CopyFrom(EditorBeatmap.Difficulty);
+ return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2);
+ }
+ }
+ }
+}
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/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/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index 9b2e9fedc5..b1d8575de4 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
Beatmap converted = base.ConvertBeatmap(original, cancellationToken);
- if (original.BeatmapInfo.RulesetID == 3)
+ if (original.BeatmapInfo.Ruleset.OnlineID == 3)
{
// Post processing step to transform mania hit objects with the same start time into strong hits
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
@@ -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/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
index 0a325f174e..fb07c687bb 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
@@ -4,6 +4,7 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Layout;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI;
@@ -16,9 +17,26 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public override double ScoreMultiplier => 1.12;
- private const float default_flashlight_size = 250;
+ [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
+ public override BindableNumber SizeMultiplier { get; } = new BindableNumber
+ {
+ MinValue = 0.5f,
+ MaxValue = 1.5f,
+ Default = 1f,
+ Value = 1f,
+ Precision = 0.1f
+ };
- public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield);
+ [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
+ public override BindableBool ComboBasedSize { get; } = new BindableBool
+ {
+ Default = true,
+ Value = true
+ };
+
+ public override float DefaultFlashlightSize => 250;
+
+ protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield);
private TaikoPlayfield playfield;
@@ -33,7 +51,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize);
private readonly TaikoPlayfield taikoPlayfield;
- public TaikoFlashlight(TaikoPlayfield taikoPlayfield)
+ public TaikoFlashlight(TaikoModFlashlight modFlashlight, TaikoPlayfield taikoPlayfield)
+ : base(modFlashlight)
{
this.taikoPlayfield = taikoPlayfield;
FlashlightSize = getSizeFor(0);
@@ -43,15 +62,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
private Vector2 getSizeFor(int combo)
{
- float size = default_flashlight_size;
-
- if (combo > 200)
- size *= 0.8f;
- else if (combo > 100)
- size *= 0.9f;
-
// Preserve flashlight size through the playfield's aspect adjustment.
- return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
+ return new Vector2(0, GetSizeFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
}
protected override void OnComboChange(ValueChangedEvent e)
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/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs
index 138e8f9785..2f9b6c7f60 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any();
- public override void CollectPendingInputs(List inputs)
+ protected override void CollectReplayInputs(List inputs)
{
inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() });
}
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/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs
index 8ca996159b..a106c4f629 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs
@@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
if (!effectPoint.KiaiMode)
return;
- if (beatIndex % (int)timingPoint.TimeSignature != 0)
+ if (beatIndex % timingPoint.TimeSignature.Numerator != 0)
return;
double duration = timingPoint.BeatLength * 2;
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/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 677aaf6f78..468cb7683c 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -60,7 +60,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
Assert.AreEqual(164471, metadata.PreviewTime);
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
- Assert.IsTrue(beatmapInfo.RulesetID == 0);
+ Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0);
Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
Assert.IsFalse(beatmapInfo.SpecialStyle);
Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
@@ -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);
}
}
@@ -178,17 +178,17 @@ namespace osu.Game.Tests.Beatmaps.Formats
var timingPoint = controlPoints.TimingPointAt(0);
Assert.AreEqual(956, timingPoint.Time);
Assert.AreEqual(329.67032967033, timingPoint.BeatLength);
- Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
+ Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
timingPoint = controlPoints.TimingPointAt(48428);
Assert.AreEqual(956, timingPoint.Time);
Assert.AreEqual(329.67032967033d, timingPoint.BeatLength);
- Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
+ Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
timingPoint = controlPoints.TimingPointAt(119637);
Assert.AreEqual(119637, timingPoint.Time);
Assert.AreEqual(659.340659340659, timingPoint.BeatLength);
- Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
+ Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
var difficultyPoint = controlPoints.DifficultyPointAt(0);
Assert.AreEqual(0, difficultyPoint.Time);
@@ -794,5 +794,74 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.That(path.Distance, Is.EqualTo(1));
}
}
+
+ [Test]
+ public void TestLegacyDefaultsPreserved()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+
+ using (var memoryStream = new MemoryStream())
+ using (var stream = new LineBufferedReader(memoryStream))
+ {
+ var decoded = decoder.Decode(stream);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(decoded.BeatmapInfo.AudioLeadIn, Is.EqualTo(0));
+ Assert.That(decoded.BeatmapInfo.StackLeniency, Is.EqualTo(0.7f));
+ Assert.That(decoded.BeatmapInfo.SpecialStyle, Is.False);
+ Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False);
+ Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False);
+ Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False);
+ Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False);
+ Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.Normal));
+ Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0));
+ Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1));
+ Assert.That(decoded.BeatmapInfo.Ruleset.OnlineID, Is.EqualTo(0));
+ });
+ }
+ }
+
+ [Test]
+ public void TestUndefinedApproachRateInheritsOverallDifficulty()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+
+ using (var resStream = TestResources.OpenResource("undefined-approach-rate.osu"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var decoded = decoder.Decode(stream);
+ Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(1));
+ Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1));
+ }
+ }
+
+ [Test]
+ public void TestApproachRateDefinedBeforeOverallDifficulty()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+
+ using (var resStream = TestResources.OpenResource("approach-rate-before-overall-difficulty.osu"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var decoded = decoder.Decode(stream);
+ Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(9));
+ Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1));
+ }
+ }
+
+ [Test]
+ public void TestApproachRateDefinedAfterOverallDifficulty()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+
+ using (var resStream = TestResources.OpenResource("approach-rate-after-overall-difficulty.osu"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var decoded = decoder.Decode(stream);
+ Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(9));
+ Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1));
+ }
+ }
}
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
index d12da1a22f..d19b3c71f1 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
@@ -195,7 +195,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
private IBeatmap convert(IBeatmap beatmap)
{
- switch (beatmap.BeatmapInfo.RulesetID)
+ switch (beatmap.BeatmapInfo.Ruleset.OnlineID)
{
case 0:
beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo;
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
index 81d89359e0..2ba8c51a10 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
@@ -12,6 +12,7 @@ using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
+using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Osu.UI;
@@ -51,6 +52,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(829_931, score.ScoreInfo.TotalScore);
Assert.AreEqual(3, score.ScoreInfo.MaxCombo);
+
+ Assert.IsTrue(score.ScoreInfo.Mods.Any(m => m is ManiaModClassic));
+ Assert.IsTrue(score.ScoreInfo.APIMods.Any(m => m.Acronym == "CL"));
+ Assert.IsTrue(score.ScoreInfo.ModsJson.Contains("CL"));
+
Assert.IsTrue(Precision.AlmostEquals(0.8889, score.ScoreInfo.Accuracy, 0.0001));
Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank);
@@ -95,7 +101,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 +134,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..2eb75259d9 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);
@@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
Assert.AreEqual(false, beatmapInfo.SpecialStyle);
- Assert.IsTrue(beatmapInfo.RulesetID == 0);
+ Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0);
Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks);
Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard);
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown);
diff --git a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs
new file mode 100644
index 0000000000..9e440c6bce
--- /dev/null
+++ b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs
@@ -0,0 +1,85 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.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 realm = osu.Dependencies.Get();
+
+ realm.Run(r => BeatmapImporterTests.EnsureLoaded(r, 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 9f3709f7a3..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())
- {
- 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())
- {
- 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())
- {
- 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())
- {
- 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())
- {
- 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())
- {
- 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())
- {
- 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())
- {
- 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())
- {
- 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())
- {
- 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())
- {
- 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())
- {
- 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())
- {
- 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())
- {
- 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(true))
- using (HeadlessGameHost client = new CleanRunHeadlessGameHost(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())
- {
- 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())
- {
- 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())
- {
- 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())
- {
- 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())
- {
- 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())
- {
- 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())
- {
- 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())
- {
- 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())
- {
- 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 d4ec5e897b..5cbede54f5 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;
@@ -154,7 +155,7 @@ namespace osu.Game.Tests.Collections.IO
}
// Name matches the automatically chosen name from `CleanRunHeadlessGameHost` above, so we end up using the same storage location.
- using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName))
+ using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName, null))
{
try
{
@@ -179,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..2c7d0211a0 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,56 +35,157 @@ namespace osu.Game.Tests.Database
[TestFixture]
public class BeatmapImporterTests : RealmTest
{
+ [Test]
+ public void TestDetachBeatmapSet()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ using (var importer = new BeatmapModelManager(realm, storage))
+ using (new RulesetStore(realm, storage))
+ {
+ Live? 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 (realm, storage) =>
+ {
+ using (var importer = new BeatmapModelManager(realm, storage))
+ using (new RulesetStore(realm, storage))
+ {
+ Live? 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) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using (var importer = new BeatmapImporter(realmFactory, storage))
- using (new RealmRulesetStore(realmFactory, storage))
+ using (var importer = new BeatmapModelManager(realm, storage))
+ using (new RulesetStore(realm, storage))
{
- ILive? imported;
+ Live? imported;
using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
imported = await importer.Import(reader);
- Assert.AreEqual(1, realmFactory.Context.All().Count());
+ Assert.AreEqual(1, realm.Realm.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, realm.Realm.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((realm, _) => { Assert.AreEqual(0, realm.Realm.All().Count()); });
}
[Test]
public void TestImportWhenClosed()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- await LoadOszIntoStore(importer, realmFactory.Context);
+ await LoadOszIntoStore(importer, realm.Realm);
});
}
[Test]
public void TestAccessFileAfterImport()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
var beatmap = imported.Beatmaps.First();
var file = beatmap.File;
@@ -96,33 +198,33 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenDelete()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
- deleteBeatmapSet(imported, realmFactory.Context);
+ deleteBeatmapSet(imported, realm.Realm);
});
}
[Test]
public void TestImportThenDeleteFromStream()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? tempPath = TestResources.GetTestBeatmapForImport();
- ILive? importedSet;
+ Live? importedSet;
using (var stream = File.OpenRead(tempPath))
{
importedSet = await importer.Import(new ImportTask(stream, Path.GetFileName(tempPath)));
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
}
Assert.NotNull(importedSet);
@@ -131,39 +233,39 @@ 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 = realm.Realm.All().First(beatmapSet => beatmapSet.ID == importedSet.ID);
- deleteBeatmapSet(imported, realmFactory.Context);
+ deleteBeatmapSet(imported, realm.Realm);
});
}
[Test]
public void TestImportThenImport()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
- var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
+ var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
// 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(realmFactory.Context, 1);
- checkSingleReferencedFileCount(realmFactory.Context, 18);
+ checkBeatmapSetCount(realm.Realm, 1);
+ checkSingleReferencedFileCount(realm.Realm, 18);
});
}
[Test]
public void TestImportThenImportWithReZip()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -172,7 +274,7 @@ namespace osu.Game.Tests.Database
try
{
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
string hashBefore = hashFile(temp);
@@ -190,7 +292,7 @@ namespace osu.Game.Tests.Database
var importedSecondTime = await importer.Import(new ImportTask(temp));
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
Assert.NotNull(importedSecondTime);
Debug.Assert(importedSecondTime != null);
@@ -209,10 +311,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenImportWithChangedHashedFile()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -221,9 +323,9 @@ namespace osu.Game.Tests.Database
try
{
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
- await createScoreForBeatmap(realmFactory.Context, imported.Beatmaps.First());
+ await createScoreForBeatmap(realm.Realm, imported.Beatmaps.First());
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);
@@ -241,7 +343,7 @@ namespace osu.Game.Tests.Database
var importedSecondTime = await importer.Import(new ImportTask(temp));
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
// check the newly "imported" beatmap is not the original.
Assert.NotNull(importedSecondTime);
@@ -261,10 +363,10 @@ namespace osu.Game.Tests.Database
[Ignore("intentionally broken by import optimisations")]
public void TestImportThenImportWithChangedFile()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -273,7 +375,7 @@ namespace osu.Game.Tests.Database
try
{
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);
@@ -290,7 +392,7 @@ namespace osu.Game.Tests.Database
var importedSecondTime = await importer.Import(new ImportTask(temp));
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
Assert.NotNull(importedSecondTime);
Debug.Assert(importedSecondTime != null);
@@ -309,10 +411,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenImportWithDifferentFilename()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -321,7 +423,7 @@ namespace osu.Game.Tests.Database
try
{
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);
@@ -338,7 +440,7 @@ namespace osu.Game.Tests.Database
var importedSecondTime = await importer.Import(new ImportTask(temp));
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
Assert.NotNull(importedSecondTime);
Debug.Assert(importedSecondTime != null);
@@ -358,12 +460,12 @@ namespace osu.Game.Tests.Database
[Ignore("intentionally broken by import optimisations")]
public void TestImportCorruptThenImport()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
var firstFile = imported.Files.First();
@@ -374,7 +476,7 @@ namespace osu.Game.Tests.Database
using (var stream = storage.GetStream(firstFile.File.GetStoragePath(), FileAccess.Write, FileMode.Create))
stream.WriteByte(0);
- var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
+ var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
using (var stream = storage.GetStream(firstFile.File.GetStoragePath()))
Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import");
@@ -383,18 +485,18 @@ namespace osu.Game.Tests.Database
Assert.IsTrue(imported.ID == importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
- checkBeatmapSetCount(realmFactory.Context, 1);
- checkSingleReferencedFileCount(realmFactory.Context, 18);
+ checkBeatmapSetCount(realm.Realm, 1);
+ checkSingleReferencedFileCount(realm.Realm, 18);
});
}
[Test]
public void TestModelCreationFailureDoesntReturn()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
var progressNotification = new ImportProgressNotification();
@@ -408,8 +510,8 @@ namespace osu.Game.Tests.Database
new ImportTask(zipStream, string.Empty)
);
- checkBeatmapSetCount(realmFactory.Context, 0);
- checkBeatmapCount(realmFactory.Context, 0);
+ checkBeatmapSetCount(realm.Realm, 0);
+ checkBeatmapCount(realm.Realm, 0);
Assert.IsEmpty(imported);
Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State);
@@ -419,7 +521,7 @@ namespace osu.Game.Tests.Database
[Test]
public void TestRollbackOnFailure()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
int loggedExceptionCount = 0;
@@ -429,16 +531,16 @@ 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(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
- realmFactory.Context.Write(() => imported.Hash += "-changed");
+ realm.Realm.Write(() => imported.Hash += "-changed");
- checkBeatmapSetCount(realmFactory.Context, 1);
- checkBeatmapCount(realmFactory.Context, 12);
- checkSingleReferencedFileCount(realmFactory.Context, 18);
+ checkBeatmapSetCount(realm.Realm, 1);
+ checkBeatmapCount(realm.Realm, 12);
+ checkSingleReferencedFileCount(realm.Realm, 18);
string? brokenTempFilename = TestResources.GetTestBeatmapForImport();
@@ -463,10 +565,10 @@ namespace osu.Game.Tests.Database
{
}
- checkBeatmapSetCount(realmFactory.Context, 1);
- checkBeatmapCount(realmFactory.Context, 12);
+ checkBeatmapSetCount(realm.Realm, 1);
+ checkBeatmapCount(realm.Realm, 12);
- checkSingleReferencedFileCount(realmFactory.Context, 18);
+ checkSingleReferencedFileCount(realm.Realm, 18);
Assert.AreEqual(1, loggedExceptionCount);
@@ -477,18 +579,18 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenDeleteThenImportOptimisedPath()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
- deleteBeatmapSet(imported, realmFactory.Context);
+ deleteBeatmapSet(imported, realm.Realm);
Assert.IsTrue(imported.DeletePending);
- var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
+ var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
@@ -499,20 +601,52 @@ namespace osu.Game.Tests.Database
}
[Test]
- public void TestImportThenDeleteThenImportNonOptimisedPath()
+ public void TestImportThenReimportAfterMissingFiles()
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new NonOptimisedBeatmapImporter(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 imported = await LoadOszIntoStore(importer, realmFactory.Realm);
- deleteBeatmapSet(imported, realmFactory.Context);
+ deleteBeatmapSet(imported, realmFactory.Realm);
Assert.IsTrue(imported.DeletePending);
- var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
+ // intentionally nuke all files
+ storage.DeleteDirectory("files");
+
+ Assert.That(imported.Files.All(f => !storage.GetStorageForDirectory("files").Exists(f.File.GetStoragePath())));
+
+ var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Realm);
+
+ // 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);
+ Assert.IsFalse(imported.DeletePending);
+ Assert.IsFalse(importedSecondTime.DeletePending);
+
+ // check that the files now exist, even though they were deleted above.
+ Assert.That(importedSecondTime.Files.All(f => storage.GetStorageForDirectory("files").Exists(f.File.GetStoragePath())));
+ });
+ }
+
+ [Test]
+ public void TestImportThenDeleteThenImportNonOptimisedPath()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ using var importer = new NonOptimisedBeatmapImporter(realm, storage);
+ using var store = new RulesetStore(realm, storage);
+
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
+
+ deleteBeatmapSet(imported, realm.Realm);
+
+ Assert.IsTrue(imported.DeletePending);
+
+ var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
@@ -525,22 +659,22 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenDeleteThenImportWithOnlineIDsMissing()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
- realmFactory.Context.Write(() =>
+ realm.Realm.Write(() =>
{
foreach (var b in imported.Beatmaps)
b.OnlineID = -1;
});
- deleteBeatmapSet(imported, realmFactory.Context);
+ deleteBeatmapSet(imported, realm.Realm);
- var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
+ var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
// check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched)
Assert.IsTrue(imported.ID != importedSecondTime.ID);
@@ -551,12 +685,12 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportWithDuplicateBeatmapIDs()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var metadata = new RealmBeatmapMetadata
+ var metadata = new BeatmapMetadata
{
Artist = "SomeArtist",
Author =
@@ -565,18 +699,18 @@ namespace osu.Game.Tests.Database
}
};
- var ruleset = realmFactory.Context.All().First();
+ var ruleset = realm.Realm.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,
@@ -584,7 +718,7 @@ namespace osu.Game.Tests.Database
}
};
- var imported = await importer.Import(toImport);
+ var imported = importer.Import(toImport);
Assert.NotNull(imported);
Debug.Assert(imported != null);
@@ -597,15 +731,15 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportWhenFileOpen()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
using (File.OpenRead(temp))
await importer.Import(temp);
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
File.Delete(temp);
Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
});
@@ -614,10 +748,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportWithDuplicateHashes()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -638,7 +772,7 @@ namespace osu.Game.Tests.Database
await importer.Import(temp);
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
}
finally
{
@@ -650,10 +784,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportNestedStructure()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -678,7 +812,7 @@ namespace osu.Game.Tests.Database
Assert.NotNull(imported);
Debug.Assert(imported != null);
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("subfolder"))), "Files contain common subfolder");
}
@@ -692,10 +826,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportWithIgnoredDirectoryInArchive()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -728,7 +862,7 @@ namespace osu.Game.Tests.Database
Assert.NotNull(imported);
Debug.Assert(imported != null);
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
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");
@@ -743,27 +877,27 @@ namespace osu.Game.Tests.Database
[Test]
public void TestUpdateBeatmapInfo()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, 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 = realm.Realm.All().First();
var beatmapToUpdate = setToUpdate.Beatmaps.First();
- realmFactory.Context.Write(() => beatmapToUpdate.DifficultyName = "updated");
+ realm.Realm.Write(() => beatmapToUpdate.DifficultyName = "updated");
- RealmBeatmap updatedInfo = realmFactory.Context.All().First(b => b.ID == beatmapToUpdate.ID);
+ BeatmapInfo updatedInfo = realm.Realm.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 +905,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 +921,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 +954,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 +966,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 +982,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 +1013,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);
}
@@ -903,8 +1036,8 @@ namespace osu.Game.Tests.Database
public class NonOptimisedBeatmapImporter : BeatmapImporter
{
- public NonOptimisedBeatmapImporter(RealmContextFactory realmFactory, Storage storage)
- : base(realmFactory, storage)
+ public NonOptimisedBeatmapImporter(RealmAccess realm, Storage storage)
+ : base(realm, storage)
{
}
diff --git a/osu.Game.Tests/Database/FileStoreTests.cs b/osu.Game.Tests/Database/FileStoreTests.cs
index 3cb4705381..98b0ed99b5 100644
--- a/osu.Game.Tests/Database/FileStoreTests.cs
+++ b/osu.Game.Tests/Database/FileStoreTests.cs
@@ -19,10 +19,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportFile()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realmAccess, storage) =>
{
- var realm = realmFactory.Context;
- var files = new RealmFileStore(realmFactory, storage);
+ var realm = realmAccess.Realm;
+ var files = new RealmFileStore(realmAccess, storage);
var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 });
@@ -36,10 +36,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportSameFileTwice()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realmAccess, storage) =>
{
- var realm = realmFactory.Context;
- var files = new RealmFileStore(realmFactory, storage);
+ var realm = realmAccess.Realm;
+ var files = new RealmFileStore(realmAccess, storage);
var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 });
@@ -53,10 +53,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestDontPurgeReferenced()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realmAccess, storage) =>
{
- var realm = realmFactory.Context;
- var files = new RealmFileStore(realmFactory, storage);
+ var realm = realmAccess.Realm;
+ var files = new RealmFileStore(realmAccess, storage);
var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm));
@@ -92,10 +92,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestPurgeUnreferenced()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realmAccess, storage) =>
{
- var realm = realmFactory.Context;
- var files = new RealmFileStore(realmFactory, storage);
+ var realm = realmAccess.Realm;
+ var files = new RealmFileStore(realmAccess, storage);
var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm));
diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs
index 2285b22a3a..8262ef18d4 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
@@ -21,15 +21,15 @@ namespace osu.Game.Tests.Database
[Test]
public void TestConstructRealm()
{
- RunTestWithRealm((realmFactory, _) => { realmFactory.CreateContext().Refresh(); });
+ RunTestWithRealm((realm, _) => { realm.Run(r => r.Refresh()); });
}
[Test]
public void TestBlockOperations()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- using (realmFactory.BlockAllOperations())
+ using (realm.BlockAllOperations())
{
}
});
@@ -42,27 +42,26 @@ namespace osu.Game.Tests.Database
[Test]
public void TestNestedContextCreationWithSubscription()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
bool callbackRan = false;
- using (var context = realmFactory.CreateContext())
+ realm.RegisterCustomSubscription(r =>
{
- var subscription = context.All().QueryAsyncWithNotifications((sender, changes, error) =>
+ var subscription = r.All().QueryAsyncWithNotifications((sender, changes, error) =>
{
- using (realmFactory.CreateContext())
+ realm.Run(_ =>
{
callbackRan = true;
- }
+ });
});
// Force the callback above to run.
- using (realmFactory.CreateContext())
- {
- }
+ realm.Run(rr => rr.Refresh());
subscription?.Dispose();
- }
+ return null;
+ });
Assert.IsTrue(callbackRan);
});
@@ -71,31 +70,36 @@ namespace osu.Game.Tests.Database
[Test]
public void TestBlockOperationsWithContention()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
ManualResetEventSlim stopThreadedUsage = new ManualResetEventSlim();
ManualResetEventSlim hasThreadedUsage = new ManualResetEventSlim();
Task.Factory.StartNew(() =>
{
- using (realmFactory.CreateContext())
+ realm.Run(_ =>
{
hasThreadedUsage.Set();
stopThreadedUsage.Wait();
- }
+ });
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler);
hasThreadedUsage.Wait();
Assert.Throws(() =>
{
- using (realmFactory.BlockAllOperations())
+ using (realm.BlockAllOperations())
{
}
});
stopThreadedUsage.Set();
+
+ // Ensure we can block a second time after the usage has ended.
+ using (realm.BlockAllOperations())
+ {
+ }
});
}
}
diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs
index 06cb5a3607..4bc1f5078a 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
@@ -20,11 +21,11 @@ namespace osu.Game.Tests.Database
[Test]
public void TestLiveEquality()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- ILive beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive(realmFactory);
+ Live beatmap = realm.Run(r => r.Write(_ => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realm));
- ILive beatmap2 = realmFactory.CreateContext().All().First().ToLive(realmFactory);
+ Live beatmap2 = realm.Run(r => r.All().First().ToLive(realm));
Assert.AreEqual(beatmap, beatmap2);
});
@@ -33,26 +34,29 @@ namespace osu.Game.Tests.Database
[Test]
public void TestAccessAfterStorageMigrate()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
- var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
+ var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
- ILive liveBeatmap;
+ Live? liveBeatmap = null;
- using (var context = realmFactory.CreateContext())
+ realm.Run(r =>
{
- context.Write(r => r.Add(beatmap));
+ r.Write(_ => r.Add(beatmap));
- liveBeatmap = beatmap.ToLive(realmFactory);
- }
+ liveBeatmap = beatmap.ToLive(realm);
+ });
using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
{
migratedStorage.DeleteDirectory(string.Empty);
- storage.Migrate(migratedStorage);
+ using (realm.BlockAllOperations())
+ {
+ storage.Migrate(migratedStorage);
+ }
- Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
+ Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden));
}
});
}
@@ -60,14 +64,13 @@ namespace osu.Game.Tests.Database
[Test]
public void TestAccessAfterAttach()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
+ var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
- var liveBeatmap = beatmap.ToLive(realmFactory);
+ var liveBeatmap = beatmap.ToLive(realm);
- using (var context = realmFactory.CreateContext())
- context.Write(r => r.Add(beatmap));
+ realm.Run(r => r.Write(_ => r.Add(beatmap)));
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
});
@@ -76,7 +79,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);
@@ -93,18 +96,18 @@ namespace osu.Game.Tests.Database
[Test]
public void TestScopedReadWithoutContext()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- ILive? liveBeatmap = null;
+ Live? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- using (var threadContext = realmFactory.CreateContext())
+ realm.Run(threadContext =>
{
- 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();
+ liveBeatmap = beatmap.ToLive(realm);
+ });
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
@@ -115,25 +118,25 @@ namespace osu.Game.Tests.Database
Assert.IsTrue(beatmap.IsValid);
Assert.IsFalse(beatmap.Hidden);
});
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
});
}
[Test]
public void TestScopedWriteWithoutContext()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- ILive? liveBeatmap = null;
+ Live? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- using (var threadContext = realmFactory.CreateContext())
+ realm.Run(threadContext =>
{
- 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();
+ liveBeatmap = beatmap.ToLive(realm);
+ });
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
@@ -141,17 +144,17 @@ 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();
});
}
[Test]
public void TestValueAccessNonManaged()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
- var liveBeatmap = beatmap.ToLive(realmFactory);
+ var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
+ var liveBeatmap = beatmap.ToLive(realm);
Assert.DoesNotThrow(() =>
{
@@ -163,19 +166,19 @@ namespace osu.Game.Tests.Database
[Test]
public void TestValueAccessWithOpenContextFails()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- ILive? liveBeatmap = null;
+ Live? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- using (var threadContext = realmFactory.CreateContext())
+ realm.Run(threadContext =>
{
- 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();
+ liveBeatmap = beatmap.ToLive(realm);
+ });
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
@@ -188,32 +191,32 @@ namespace osu.Game.Tests.Database
});
// Can't be used, even from within a valid context.
- using (realmFactory.CreateContext())
+ realm.Run(threadContext =>
{
Assert.Throws(() =>
{
var __ = liveBeatmap.Value;
});
- }
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ });
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
});
}
[Test]
public void TestValueAccessWithoutOpenContextFails()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- ILive? liveBeatmap = null;
+ Live? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- using (var threadContext = realmFactory.CreateContext())
+ realm.Run(threadContext =>
{
- 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();
+ liveBeatmap = beatmap.ToLive(realm);
+ });
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
@@ -223,63 +226,65 @@ namespace osu.Game.Tests.Database
{
var unused = liveBeatmap.Value;
});
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
});
}
[Test]
public void TestLiveAssumptions()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
int changesTriggered = 0;
- using (var updateThreadContext = realmFactory.CreateContext())
+ realm.RegisterCustomSubscription(outerRealm =>
{
- updateThreadContext.All().QueryAsyncWithNotifications(gotChange);
- ILive? liveBeatmap = null;
+ outerRealm.All().QueryAsyncWithNotifications(gotChange);
+ Live? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- using (var threadContext = realmFactory.CreateContext())
+ realm.Run(innerRealm =>
{
var ruleset = CreateRuleset();
- var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
+ var beatmap = innerRealm.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())));
+ innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata())));
- liveBeatmap = beatmap.ToLive(realmFactory);
- }
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ liveBeatmap = beatmap.ToLive(realm);
+ });
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
// not yet seen by main context
- Assert.AreEqual(0, updateThreadContext.All().Count());
+ Assert.AreEqual(0, outerRealm.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, outerRealm.All().Count());
Assert.AreEqual(1, changesTriggered);
// can access properties without a crash.
Assert.IsFalse(resolved.Hidden);
// ReSharper disable once AccessToDisposedClosure
- updateThreadContext.Write(r =>
+ outerRealm.Write(r =>
{
// can use with the main context.
r.Remove(resolved);
});
});
- }
- void gotChange(IRealmCollection sender, ChangeSet changes, Exception error)
+ return null;
+ });
+
+ void gotChange(IRealmCollection sender, ChangeSet changes, Exception error)
{
changesTriggered++;
}
diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs
new file mode 100644
index 0000000000..d62ce3b585
--- /dev/null
+++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs
@@ -0,0 +1,138 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+using osu.Game.Tests.Resources;
+using Realms;
+
+#nullable enable
+
+namespace osu.Game.Tests.Database
+{
+ [TestFixture]
+ public class RealmSubscriptionRegistrationTests : RealmTest
+ {
+ [Test]
+ public void TestSubscriptionWithContextLoss()
+ {
+ IEnumerable? resolvedItems = null;
+ ChangeSet? lastChanges = null;
+
+ RunTestWithRealm((realm, _) =>
+ {
+ realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
+
+ var registration = realm.RegisterForNotifications(r => r.All(), onChanged);
+
+ testEventsArriving(true);
+
+ // All normal until here.
+ // Now let's yank the main realm context.
+ resolvedItems = null;
+ lastChanges = null;
+
+ using (realm.BlockAllOperations())
+ Assert.That(resolvedItems, Is.Empty);
+
+ realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
+
+ testEventsArriving(true);
+
+ // Now let's try unsubscribing.
+ resolvedItems = null;
+ lastChanges = null;
+
+ registration.Dispose();
+
+ realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
+
+ testEventsArriving(false);
+
+ // And make sure even after another context loss we don't get firings.
+ using (realm.BlockAllOperations())
+ Assert.That(resolvedItems, Is.Null);
+
+ realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
+
+ testEventsArriving(false);
+
+ void testEventsArriving(bool shouldArrive)
+ {
+ realm.Run(r => r.Refresh());
+
+ if (shouldArrive)
+ Assert.That(resolvedItems, Has.One.Items);
+ else
+ Assert.That(resolvedItems, Is.Null);
+
+ realm.Write(r =>
+ {
+ r.RemoveAll();
+ r.RemoveAll();
+ });
+
+ realm.Run(r => r.Refresh());
+
+ if (shouldArrive)
+ Assert.That(lastChanges?.DeletedIndices, Has.One.Items);
+ else
+ Assert.That(lastChanges, Is.Null);
+ }
+ });
+
+ void onChanged(IRealmCollection sender, ChangeSet? changes, Exception error)
+ {
+ if (changes == null)
+ resolvedItems = sender;
+
+ lastChanges = changes;
+ }
+ }
+
+ [Test]
+ public void TestCustomRegisterWithContextLoss()
+ {
+ RunTestWithRealm((realm, _) =>
+ {
+ BeatmapSetInfo? beatmapSetInfo = null;
+
+ realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
+
+ var subscription = realm.RegisterCustomSubscription(r =>
+ {
+ beatmapSetInfo = r.All().First();
+
+ return new InvokeOnDisposal(() => beatmapSetInfo = null);
+ });
+
+ Assert.That(beatmapSetInfo, Is.Not.Null);
+
+ using (realm.BlockAllOperations())
+ {
+ // custom disposal action fired when context lost.
+ Assert.That(beatmapSetInfo, Is.Null);
+ }
+
+ // re-registration after context restore.
+ realm.Run(r => r.Refresh());
+ Assert.That(beatmapSetInfo, Is.Not.Null);
+
+ subscription.Dispose();
+
+ Assert.That(beatmapSetInfo, Is.Null);
+
+ using (realm.BlockAllOperations())
+ Assert.That(beatmapSetInfo, Is.Null);
+
+ realm.Run(r => r.Refresh());
+ Assert.That(beatmapSetInfo, Is.Null);
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs
index 4e67f09dca..838759c991 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
@@ -28,7 +30,7 @@ namespace osu.Game.Tests.Database
storage.DeleteDirectory(string.Empty);
}
- protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "")
+ protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "")
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
{
@@ -37,22 +39,22 @@ namespace osu.Game.Tests.Database
// ReSharper disable once AccessToDisposedClosure
var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller));
- using (var realmFactory = new RealmContextFactory(testStorage, "client"))
+ using (var realm = new RealmAccess(testStorage, "client"))
{
- Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
- testAction(realmFactory, testStorage);
+ Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}");
+ testAction(realm, testStorage);
- realmFactory.Dispose();
+ realm.Dispose();
- Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
- realmFactory.Compact();
- Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}");
+ Logger.Log($"Final database size: {getFileSize(testStorage, realm)}");
+ realm.Compact();
+ Logger.Log($"Final database size after compact: {getFileSize(testStorage, realm)}");
}
}));
}
}
- protected void RunTestWithRealmAsync(Func testAction, [CallerMemberName] string caller = "")
+ protected void RunTestWithRealmAsync(Func testAction, [CallerMemberName] string caller = "")
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
{
@@ -60,38 +62,38 @@ namespace osu.Game.Tests.Database
{
var testStorage = storage.GetStorageForDirectory(caller);
- using (var realmFactory = new RealmContextFactory(testStorage, "client"))
+ using (var realm = new RealmAccess(testStorage, "client"))
{
- Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
- await testAction(realmFactory, testStorage);
+ Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}");
+ await testAction(realm, testStorage);
- realmFactory.Dispose();
+ realm.Dispose();
- Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
- realmFactory.Compact();
+ Logger.Log($"Final database size: {getFileSize(testStorage, realm)}");
+ realm.Compact();
}
}));
}
}
- 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("osu", "osu!", string.Empty, 0) { Available = true };
private class RealmTestGame : Framework.Game
{
@@ -136,11 +138,11 @@ namespace osu.Game.Tests.Database
}
}
- private static long getFileSize(Storage testStorage, RealmContextFactory realmFactory)
+ private static long getFileSize(Storage testStorage, RealmAccess realm)
{
try
{
- using (var stream = testStorage.GetStream(realmFactory.Filename))
+ using (var stream = testStorage.GetStream(realm.Filename))
return stream?.Length ?? 0;
}
catch
diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs
index cc7e8a0c97..7544142b70 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
{
@@ -13,37 +12,37 @@ namespace osu.Game.Tests.Database
[Test]
public void TestCreateStore()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
- var rulesets = new RealmRulesetStore(realmFactory, storage);
+ var rulesets = new RulesetStore(realm, storage);
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
- Assert.AreEqual(4, realmFactory.Context.All().Count());
+ Assert.AreEqual(4, realm.Realm.All().Count());
});
}
[Test]
public void TestCreateStoreTwiceDoesntAddRulesetsAgain()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
- var rulesets = new RealmRulesetStore(realmFactory, storage);
- var rulesets2 = new RealmRulesetStore(realmFactory, storage);
+ var rulesets = new RulesetStore(realm, storage);
+ var rulesets2 = new RulesetStore(realm, 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, realm.Realm.All().Count());
});
}
[Test]
public void TestRetrievedRulesetsAreDetached()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
- var rulesets = new RealmRulesetStore(realmFactory, storage);
+ var rulesets = new RulesetStore(realm, storage);
Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged);
Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged);
diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs
index f05d9ab3dc..891801865f 100644
--- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs
+++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Tests.Database
private RealmKeyBindingStore keyBindingStore;
- private RealmContextFactory realmContextFactory;
+ private RealmAccess realm;
[SetUp]
public void SetUp()
@@ -33,8 +33,8 @@ namespace osu.Game.Tests.Database
storage = new NativeStorage(directory.FullName);
- realmContextFactory = new RealmContextFactory(storage, "test");
- keyBindingStore = new RealmKeyBindingStore(realmContextFactory, new ReadableKeyCombinationProvider());
+ realm = new RealmAccess(storage, "test");
+ keyBindingStore = new RealmKeyBindingStore(realm, new ReadableKeyCombinationProvider());
}
[Test]
@@ -60,29 +60,12 @@ namespace osu.Game.Tests.Database
KeyBindingContainer testContainer = new TestKeyBindingContainer();
// Add some excess bindings for an action which only supports 1.
- using (var realm = realmContextFactory.CreateContext())
- using (var transaction = realm.BeginWrite())
+ realm.Write(r =>
{
- realm.Add(new RealmKeyBinding
- {
- Action = GlobalAction.Back,
- KeyCombination = new KeyCombination(InputKey.A)
- });
-
- realm.Add(new RealmKeyBinding
- {
- Action = GlobalAction.Back,
- KeyCombination = new KeyCombination(InputKey.S)
- });
-
- realm.Add(new RealmKeyBinding
- {
- Action = GlobalAction.Back,
- KeyCombination = new KeyCombination(InputKey.D)
- });
-
- transaction.Commit();
- }
+ r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A)));
+ r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S)));
+ r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D)));
+ });
Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3));
@@ -93,13 +76,13 @@ namespace osu.Game.Tests.Database
private int queryCount(GlobalAction? match = null)
{
- using (var realm = realmContextFactory.CreateContext())
+ return realm.Run(r =>
{
- var results = realm.All();
+ var results = r.All();
if (match.HasValue)
results = results.Where(k => k.ActionInt == (int)match.Value);
return results.Count();
- }
+ });
}
[Test]
@@ -109,32 +92,32 @@ namespace osu.Game.Tests.Database
keyBindingStore.Register(testContainer, Enumerable.Empty());
- using (var primaryRealm = realmContextFactory.CreateContext())
+ realm.Run(outerRealm =>
{
- var backBinding = primaryRealm.All