diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index ce353d9b27..0000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-language: csharp
-solution: osu.sln
\ No newline at end of file
diff --git a/Gemfile.lock b/Gemfile.lock
index 17c0db12e7..f7c19064b4 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -6,7 +6,7 @@ GEM
public_suffix (>= 2.0.2, < 4.0)
atomos (0.1.3)
babosa (1.0.2)
- claide (1.0.2)
+ claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.6)
@@ -14,11 +14,11 @@ GEM
declarative (0.0.10)
declarative-option (0.1.0)
digest-crc (0.4.1)
- domain_name (0.5.20180417)
+ domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
- dotenv (2.7.1)
+ dotenv (2.7.5)
emoji_regex (1.0.1)
- excon (0.62.0)
+ excon (0.66.0)
faraday (0.15.4)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
@@ -27,7 +27,7 @@ GEM
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.5)
- fastlane (2.117.0)
+ fastlane (2.129.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
@@ -46,8 +46,8 @@ GEM
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
- mini_magick (~> 4.5.1)
- multi_json
+ jwt (~> 2.1.0)
+ mini_magick (>= 4.9.4, < 5.0.0)
multi_xml (~> 0.5)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
@@ -56,15 +56,15 @@ GEM
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
- terminal-notifier (>= 1.6.2, < 2.0.0)
+ terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
- xcodeproj (>= 1.6.0, < 2.0.0)
+ xcodeproj (>= 1.8.1, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
- fastlane-plugin-clean_testflight_testers (0.2.0)
+ fastlane-plugin-clean_testflight_testers (0.3.0)
fastlane-plugin-souyuz (0.8.1)
souyuz (>= 0.8.1)
fastlane-plugin-xamarin (0.6.3)
@@ -79,7 +79,7 @@ GEM
signet (~> 0.9)
google-cloud-core (1.3.0)
google-cloud-env (~> 1.0)
- google-cloud-env (1.0.5)
+ google-cloud-env (1.2.0)
faraday (~> 0.11)
google-cloud-storage (1.16.0)
digest-crc (~> 0.4)
@@ -102,17 +102,17 @@ GEM
memoist (0.16.0)
mime-types (3.2.2)
mime-types-data (~> 3.2015)
- mime-types-data (3.2018.0812)
- mini_magick (4.5.1)
+ mime-types-data (3.2019.0331)
+ mini_magick (4.9.5)
mini_portile2 (2.4.0)
multi_json (1.13.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
nanaimo (0.2.6)
naturally (2.2.0)
- nokogiri (1.10.1)
+ nokogiri (1.10.4)
mini_portile2 (~> 2.4.0)
- os (1.0.0)
+ os (1.0.1)
plist (3.5.0)
public_suffix (2.0.5)
representable (3.0.4)
@@ -121,7 +121,7 @@ GEM
uber (< 0.2.0)
retriable (3.1.2)
rouge (2.0.7)
- rubyzip (1.2.2)
+ rubyzip (1.2.3)
security (0.1.3)
signet (0.11.0)
addressable (~> 2.3)
@@ -136,20 +136,20 @@ GEM
fastlane (>= 2.29.0)
highline (~> 1.7)
nokogiri (~> 1.7)
- terminal-notifier (1.8.0)
+ terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
- tty-cursor (0.6.1)
- tty-screen (0.6.5)
- tty-spinner (0.9.0)
- tty-cursor (~> 0.6.0)
+ tty-cursor (0.7.0)
+ tty-screen (0.7.0)
+ tty-spinner (0.9.1)
+ tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
- unf_ext (0.0.7.5)
- unicode-display_width (1.4.1)
+ unf_ext (0.0.7.6)
+ unicode-display_width (1.6.0)
word_wrap (1.0.0)
- xcodeproj (1.8.1)
+ xcodeproj (1.12.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
diff --git a/README.md b/README.md
index 52fc29cb98..56491a4be4 100644
--- a/README.md
+++ b/README.md
@@ -19,8 +19,9 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh
## Requirements
- A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed.
+- When running on linux, please have a system-wide ffmpeg installation available to support video decoding.
+- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
-- Note that there are **[additional requirements for Windows 7 and Windows 8.1](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** which you may need to manually install if your operating system is not up-to-date.
## Running osu!
@@ -34,7 +35,7 @@ If you are not interested in developing the game, you can still consume our [bin
| ------------- | ------------- |
- **Linux** users are recommended to self-compile until we have official deployment in place.
-- **iOS** users can join the [TestFlight beta program](https://t.co/PasE1zrHhw) (note that due to high demand this is regularly full).
+- **iOS** users can join the [TestFlight beta program](https://testflight.apple.com/join/2tLcjWlF) (note that due to high demand this is regularly full).
- **Android** users can self-compile, and expect a public beta soon.
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
diff --git a/appveyor.yml b/appveyor.yml
index 4dcaa7b45e..be1727e7d7 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -2,7 +2,5 @@ clone_depth: 1
version: '{branch}-{build}'
image: Previous Visual Studio 2017
test: off
-install:
- - cmd: git submodule update --init --recursive --depth=5
build_script:
- cmd: PowerShell -Version 2.0 .\build.ps1
diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml
new file mode 100644
index 0000000000..d36298175b
--- /dev/null
+++ b/appveyor_deploy.yml
@@ -0,0 +1,10 @@
+clone_depth: 1
+version: '{build}'
+image: Previous Visual Studio 2017
+test: off
+skip_non_tags: true
+build_script:
+ - cmd: PowerShell -Version 2.0 .\build.ps1
+deploy:
+ - provider: Environment
+ name: nuget
diff --git a/build/cakebuild.csproj b/build/cakebuild.csproj
index 8ccce35e26..d5a6d44740 100644
--- a/build/cakebuild.csproj
+++ b/build/cakebuild.csproj
@@ -5,7 +5,7 @@
netcoreapp2.0
-
-
+
+
\ No newline at end of file
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 48c16caf0f..0b60e28b0f 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -1,22 +1,6 @@
update_fastlane
-default_platform(:ios)
-
platform :ios do
- lane :testflight_prune_dry do
- clean_testflight_testers(days_of_inactivity:45, dry_run: true)
- end
-
- # Specify a custom number for what's "inactive"
- lane :testflight_prune do
- clean_testflight_testers(days_of_inactivity: 45) # 120 days, so about 4 months
- end
-
- lane :update_version do |options|
- options[:plist_path] = '../osu.iOS/Info.plist'
- app_version(options)
- end
-
desc 'Deploy to testflight'
lane :beta do |options|
update_version(options)
@@ -62,4 +46,17 @@ platform :ios do
match(options)
end
+
+ lane :update_version do |options|
+ options[:plist_path] = '../osu.iOS/Info.plist'
+ app_version(options)
+ end
+
+ lane :testflight_prune_dry do
+ clean_testflight_testers(days_of_inactivity:45, dry_run: true)
+ end
+
+ lane :testflight_prune do
+ clean_testflight_testers(days_of_inactivity: 45)
+ end
end
diff --git a/fastlane/README.md b/fastlane/README.md
index 53bbc62cae..fbccf1c8c0 100644
--- a/fastlane/README.md
+++ b/fastlane/README.md
@@ -16,21 +16,6 @@ or alternatively using `brew cask install fastlane`
# Available Actions
## iOS
-### ios testflight_prune_dry
-```
-fastlane ios testflight_prune_dry
-```
-
-### ios testflight_prune
-```
-fastlane ios testflight_prune
-```
-
-### ios update_version
-```
-fastlane ios update_version
-```
-
### ios beta
```
fastlane ios beta
@@ -46,6 +31,21 @@ Compile the project
fastlane ios provision
```
Install provisioning profiles using match
+### ios update_version
+```
+fastlane ios update_version
+```
+
+### ios testflight_prune_dry
+```
+fastlane ios testflight_prune_dry
+```
+
+### ios testflight_prune
+```
+fastlane ios testflight_prune
+```
+
----
diff --git a/osu.Android.props b/osu.Android.props
index 6744590f0d..45c162a30e 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -1,5 +1,7 @@
+ Debug
+ AnyCPU
bin\$(Configuration)
4
2.0
@@ -35,7 +37,7 @@
None
True
prompt
- true
+ true
false
SdkOnly
False
@@ -49,9 +51,7 @@
osu.licenseheader
-
- Always
-
+
@@ -62,7 +62,7 @@
-
-
+
+
diff --git a/osu.Android/lib/arm64-v8a/libbass.so b/osu.Android/lib/arm64-v8a/libbass.so
deleted file mode 100644
index d5c24b3e4b..0000000000
Binary files a/osu.Android/lib/arm64-v8a/libbass.so and /dev/null differ
diff --git a/osu.Android/lib/arm64-v8a/libbass_fx.so b/osu.Android/lib/arm64-v8a/libbass_fx.so
deleted file mode 100644
index e498b10117..0000000000
Binary files a/osu.Android/lib/arm64-v8a/libbass_fx.so and /dev/null differ
diff --git a/osu.Android/lib/armeabi-v7a/libbass.so b/osu.Android/lib/armeabi-v7a/libbass.so
deleted file mode 100644
index f7d23b9052..0000000000
Binary files a/osu.Android/lib/armeabi-v7a/libbass.so and /dev/null differ
diff --git a/osu.Android/lib/armeabi-v7a/libbass_fx.so b/osu.Android/lib/armeabi-v7a/libbass_fx.so
deleted file mode 100644
index 006e2feb30..0000000000
Binary files a/osu.Android/lib/armeabi-v7a/libbass_fx.so and /dev/null differ
diff --git a/osu.Android/lib/x86/libbass.so b/osu.Android/lib/x86/libbass.so
deleted file mode 100644
index b0f758a42b..0000000000
Binary files a/osu.Android/lib/x86/libbass.so and /dev/null differ
diff --git a/osu.Android/lib/x86/libbass_fx.so b/osu.Android/lib/x86/libbass_fx.so
deleted file mode 100644
index 526dca39ca..0000000000
Binary files a/osu.Android/lib/x86/libbass_fx.so and /dev/null differ
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 975b7f9f5a..761f52f961 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -1,4 +1,4 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
@@ -38,9 +38,9 @@ namespace osu.Desktop
if (Host is DesktopGameHost desktopHost)
return new StableStorage(desktopHost);
}
- catch (Exception e)
+ catch (Exception)
{
- Logger.Error(e, "Error while searching for stable install");
+ Logger.Log("Could not find a stable install", LoggingTarget.Runtime, LogLevel.Important);
}
return null;
@@ -52,11 +52,7 @@ namespace osu.Desktop
if (!noVersionOverlay)
{
- LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, v =>
- {
- Add(v);
- v.Show();
- });
+ LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add);
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
Add(new SquirrelUpdateManager());
@@ -71,7 +67,7 @@ namespace osu.Desktop
switch (newScreen)
{
- case Intro _:
+ case IntroScreen _:
case MainMenu _:
versionManager?.Show();
break;
diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs
index 1f1d2cea5f..51e801c185 100644
--- a/osu.Desktop/Overlays/VersionManager.cs
+++ b/osu.Desktop/Overlays/VersionManager.cs
@@ -113,13 +113,14 @@ namespace osu.Desktop.Overlays
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours, ChangelogOverlay changelog)
+ private void load(OsuColour colours, ChangelogOverlay changelog, NotificationOverlay notificationOverlay)
{
Icon = FontAwesome.Solid.CheckSquare;
IconBackgound.Colour = colours.BlueDark;
Activated = delegate
{
+ notificationOverlay.Hide();
changelog.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version);
return true;
};
diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs
index 78a1e680ec..fa41c061b5 100644
--- a/osu.Desktop/Updater/SquirrelUpdateManager.cs
+++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs
@@ -27,6 +27,8 @@ namespace osu.Desktop.Updater
public Task PrepareUpdateAsync() => UpdateManager.RestartAppWhenExited();
+ private static readonly Logger logger = Logger.GetLogger("updater");
+
[BackgroundDependencyLoader]
private void load(NotificationOverlay notification, OsuGameBase game)
{
@@ -77,7 +79,7 @@ namespace osu.Desktop.Updater
{
if (useDeltaPatching)
{
- Logger.Error(e, @"delta patching failed!");
+ logger.Add(@"delta patching failed; will attempt full download!");
//could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
//try again without deltas.
@@ -163,16 +165,11 @@ namespace osu.Desktop.Updater
{
public LogLevel Level { get; set; } = LogLevel.Info;
- private Logger logger;
-
public void Write(string message, LogLevel logLevel)
{
if (logLevel < Level)
return;
- if (logger == null)
- logger = Logger.GetLogger("updater");
-
logger.Add(message);
}
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 8c9e1f279f..538aaf2d7a 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -28,8 +28,8 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj
index 37e7c45a4e..7990c35e09 100644
--- a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj
@@ -13,14 +13,6 @@
-
- libbass.a
- PreserveNewest
-
-
- libbass_fx.a
- PreserveNewest
-
Linker.xml
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index e45ed8c6f4..493ac7ae39 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Newtonsoft.Json;
using NUnit.Framework;
using osu.Framework.MathUtils;
+using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
@@ -22,10 +23,10 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestCase("spinner")]
[TestCase("spinner-and-circles")]
[TestCase("slider")]
- public new void Test(string name)
- {
- base.Test(name);
- }
+ [TestCase("hardrock-stream", new[] { typeof(CatchModHardRock) })]
+ [TestCase("hardrock-repeat-slider", new[] { typeof(CatchModHardRock) })]
+ [TestCase("hardrock-spinner", new[] { typeof(CatchModHardRock) })]
+ public new void Test(string name, params Type[] mods) => base.Test(name, mods);
protected override IEnumerable CreateConvertValue(HitObject hitObject)
{
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
index 33f93cdb4a..720ef1db42 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -14,7 +14,9 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osuTK.Graphics;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
namespace osu.Game.Rulesets.Catch.Tests
{
@@ -81,25 +83,24 @@ namespace osu.Game.Rulesets.Catch.Tests
remove { }
}
- public Drawable GetDrawableComponent(string componentName)
+ public Drawable GetDrawableComponent(ISkinComponent component)
{
- switch (componentName)
+ switch (component.LookupName)
{
- case "Play/Catch/fruit-catcher-idle":
+ case "Gameplay/catch/fruit-catcher-idle":
return new CatcherCustomSkin();
}
return null;
}
- public SampleChannel GetSample(string sampleName) =>
+ public SampleChannel GetSample(ISampleInfo sampleInfo) =>
throw new NotImplementedException();
public Texture GetTexture(string componentName) =>
throw new NotImplementedException();
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration =>
- throw new NotImplementedException();
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
}
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index 9acf47a67c..c527a81f51 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index 645cb5701a..5ab47c1611 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types;
using osuTK;
using osu.Game.Rulesets.Catch.MathUtils;
+using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
@@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
base.PostProcess();
- applyPositionOffsets();
+ ApplyPositionOffsets(Beatmap);
initialiseHyperDash((List)Beatmap.HitObjects);
@@ -40,19 +41,29 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
}
}
- private void applyPositionOffsets()
+ public static void ApplyPositionOffsets(IBeatmap beatmap, params Mod[] mods)
{
var rng = new FastRandom(RNG_SEED);
- // todo: HardRock displacement should be applied here
- foreach (var obj in Beatmap.HitObjects)
+ bool shouldApplyHardRockOffset = mods.Any(m => m is ModHardRock);
+ float? lastPosition = null;
+ double lastStartTime = 0;
+
+ foreach (var obj in beatmap.HitObjects.OfType())
{
+ obj.XOffset = 0;
+
switch (obj)
{
+ case Fruit fruit:
+ if (shouldApplyHardRockOffset)
+ applyHardRockOffset(fruit, ref lastPosition, ref lastStartTime, rng);
+ break;
+
case BananaShower bananaShower:
foreach (var banana in bananaShower.NestedHitObjects.OfType())
{
- banana.X = (float)rng.NextDouble();
+ banana.XOffset = (float)rng.NextDouble();
rng.Next(); // osu!stable retrieved a random banana type
rng.Next(); // osu!stable retrieved a random banana rotation
rng.Next(); // osu!stable retrieved a random banana colour
@@ -63,12 +74,13 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
case JuiceStream juiceStream:
foreach (var nested in juiceStream.NestedHitObjects)
{
- var hitObject = (CatchHitObject)nested;
- if (hitObject is TinyDroplet)
- hitObject.X += rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH;
- else if (hitObject is Droplet)
+ var catchObject = (CatchHitObject)nested;
+ catchObject.XOffset = 0;
+
+ if (catchObject is TinyDroplet)
+ catchObject.XOffset = MathHelper.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X);
+ else if (catchObject is Droplet)
rng.Next(); // osu!stable retrieved a random droplet rotation
- hitObject.X = MathHelper.Clamp(hitObject.X, 0, 1);
}
break;
@@ -76,6 +88,105 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
}
}
+ private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng)
+ {
+ if (hitObject is JuiceStream stream)
+ {
+ lastPosition = stream.EndX;
+ lastStartTime = stream.EndTime;
+ return;
+ }
+
+ if (!(hitObject is Fruit))
+ return;
+
+ float offsetPosition = hitObject.X;
+ double startTime = hitObject.StartTime;
+
+ if (lastPosition == null)
+ {
+ lastPosition = offsetPosition;
+ lastStartTime = startTime;
+
+ return;
+ }
+
+ float positionDiff = offsetPosition - lastPosition.Value;
+ double timeDiff = startTime - lastStartTime;
+
+ if (timeDiff > 1000)
+ {
+ lastPosition = offsetPosition;
+ lastStartTime = startTime;
+ return;
+ }
+
+ if (positionDiff == 0)
+ {
+ applyRandomOffset(ref offsetPosition, timeDiff / 4d, rng);
+ hitObject.XOffset = offsetPosition - hitObject.X;
+ return;
+ }
+
+ if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d)
+ applyOffset(ref offsetPosition, positionDiff);
+
+ hitObject.XOffset = offsetPosition - hitObject.X;
+
+ lastPosition = offsetPosition;
+ lastStartTime = startTime;
+ }
+
+ ///
+ /// Applies a random offset in a random direction to a position, ensuring that the final position remains within the boundary of the playfield.
+ ///
+ /// The position which the offset should be applied to.
+ /// The maximum offset, cannot exceed 20px.
+ /// The random number generator.
+ private static void applyRandomOffset(ref float position, double maxOffset, FastRandom rng)
+ {
+ bool right = rng.NextBool();
+ float rand = Math.Min(20, (float)rng.Next(0, Math.Max(0, maxOffset))) / CatchPlayfield.BASE_WIDTH;
+
+ if (right)
+ {
+ // Clamp to the right bound
+ if (position + rand <= 1)
+ position += rand;
+ else
+ position -= rand;
+ }
+ else
+ {
+ // Clamp to the left bound
+ if (position - rand >= 0)
+ position -= rand;
+ else
+ position += rand;
+ }
+ }
+
+ ///
+ /// Applies an offset to a position, ensuring that the final position remains within the boundary of the playfield.
+ ///
+ /// The position which the offset should be applied to.
+ /// The amount to offset by.
+ private static void applyOffset(ref float position, float amount)
+ {
+ if (amount > 0)
+ {
+ // Clamp to the right bound
+ if (position + amount < 1)
+ position += amount;
+ }
+ else
+ {
+ // Clamp to the left bound
+ if (position + amount > 0)
+ position += amount;
+ }
+ }
+
private void initialiseHyperDash(List objects)
{
List objectWithDroplets = new List();
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index ea9f225cc1..5428b4eeb8 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -11,7 +11,6 @@ using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
-using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
@@ -24,10 +23,12 @@ namespace osu.Game.Rulesets.Catch
{
public class CatchRuleset : Ruleset
{
- public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchRuleset(this, beatmap, mods);
+ public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchRuleset(this, beatmap, mods);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
+ public const string SHORT_NAME = "fruits";
+
public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
{
new KeyBinding(InputKey.Z, CatchAction.MoveLeft),
@@ -108,7 +109,7 @@ namespace osu.Game.Rulesets.Catch
case ModType.Fun:
return new Mod[]
{
- new MultiMod(new ModWindUp(), new ModWindDown())
+ new MultiMod(new ModWindUp(), new ModWindDown())
};
default:
@@ -118,7 +119,7 @@ namespace osu.Game.Rulesets.Catch
public override string Description => "osu!catch";
- public override string ShortName => "fruits";
+ public override string ShortName => SHORT_NAME;
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponent.cs b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs
new file mode 100644
index 0000000000..8bf53e53e3
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Catch
+{
+ public class CatchSkinComponent : GameplaySkinComponent
+ {
+ public CatchSkinComponent(CatchSkinComponents component)
+ : base(component)
+ {
+ }
+
+ protected override string RulesetPrefix => "catch"; // todo: use CatchRuleset.SHORT_NAME;
+
+ protected override string ComponentName => Component.ToString().ToLower();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
new file mode 100644
index 0000000000..7e482d4045
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
@@ -0,0 +1,9 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Catch
+{
+ public enum CatchSkinComponents
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
index b3605b013b..c721ff862a 100644
--- a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
+++ b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
@@ -61,6 +61,14 @@ namespace osu.Game.Rulesets.Catch.MathUtils
/// The random value.
public int Next(int lowerBound, int upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound));
+ ///
+ /// Generates a random integer value within the range [, ).
+ ///
+ /// The lower bound of the range.
+ /// The upper bound of the range.
+ /// The random value.
+ public int Next(double lowerBound, double upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound));
+
///
/// Generates a random double value within the range [0, 1).
///
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
index 060e51e31d..ced1900ba9 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
@@ -1,121 +1,17 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.MathUtils;
-using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
-using System;
-using osu.Game.Rulesets.Objects;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Beatmaps;
namespace osu.Game.Rulesets.Catch.Mods
{
- public class CatchModHardRock : ModHardRock, IApplicableToHitObject
+ public class CatchModHardRock : ModHardRock, IApplicableToBeatmap
{
public override double ScoreMultiplier => 1.12;
public override bool Ranked => true;
- private float? lastPosition;
- private double lastStartTime;
-
- public void ApplyToHitObject(HitObject hitObject)
- {
- if (hitObject is JuiceStream stream)
- {
- lastPosition = stream.EndX;
- lastStartTime = stream.EndTime;
- return;
- }
-
- if (!(hitObject is Fruit))
- return;
-
- var catchObject = (CatchHitObject)hitObject;
-
- float position = catchObject.X;
- double startTime = hitObject.StartTime;
-
- if (lastPosition == null)
- {
- lastPosition = position;
- lastStartTime = startTime;
-
- return;
- }
-
- float positionDiff = position - lastPosition.Value;
- double timeDiff = startTime - lastStartTime;
-
- if (timeDiff > 1000)
- {
- lastPosition = position;
- lastStartTime = startTime;
- return;
- }
-
- if (positionDiff == 0)
- {
- applyRandomOffset(ref position, timeDiff / 4d);
- catchObject.X = position;
- return;
- }
-
- if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d)
- applyOffset(ref position, positionDiff);
-
- catchObject.X = position;
-
- lastPosition = position;
- lastStartTime = startTime;
- }
-
- ///
- /// Applies a random offset in a random direction to a position, ensuring that the final position remains within the boundary of the playfield.
- ///
- /// The position which the offset should be applied to.
- /// The maximum offset, cannot exceed 20px.
- private void applyRandomOffset(ref float position, double maxOffset)
- {
- bool right = RNG.NextBool();
- float rand = Math.Min(20, (float)RNG.NextDouble(0, Math.Max(0, maxOffset))) / CatchPlayfield.BASE_WIDTH;
-
- if (right)
- {
- // Clamp to the right bound
- if (position + rand <= 1)
- position += rand;
- else
- position -= rand;
- }
- else
- {
- // Clamp to the left bound
- if (position - rand >= 0)
- position -= rand;
- else
- position += rand;
- }
- }
-
- ///
- /// Applies an offset to a position, ensuring that the final position remains within the boundary of the playfield.
- ///
- /// The position which the offset should be applied to.
- /// The amount to offset by.
- private void applyOffset(ref float position, float amount)
- {
- if (amount > 0)
- {
- // Clamp to the right bound
- if (position + amount < 1)
- position += amount;
- }
- else
- {
- // Clamp to the left bound
- if (position + amount > 0)
- position += amount;
- }
- }
+ public void ApplyToBeatmap(IBeatmap beatmap) => CatchBeatmapProcessor.ApplyPositionOffsets(beatmap, this);
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index be76edc01b..a25d9cb67e 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -3,8 +3,10 @@
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Objects
{
@@ -12,7 +14,18 @@ namespace osu.Game.Rulesets.Catch.Objects
{
public const double OBJECT_RADIUS = 44;
- public float X { get; set; }
+ private float x;
+
+ public float X
+ {
+ get => x + XOffset;
+ set => x = value;
+ }
+
+ ///
+ /// A random offset applied to , set by the .
+ ///
+ internal float XOffset { get; set; }
public double TimePreempt = 1000;
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
index a1279e8443..00734810b3 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
@@ -58,14 +58,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss);
}
- protected override bool UseTransformStateManagement => false;
+ protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
- protected override void UpdateState(ArmedState state)
+ protected override void UpdateInitialTransforms() => this.FadeInFromZero(200);
+
+ protected override void UpdateStateTransforms(ArmedState state)
{
- // TODO: update to use new state management.
- using (BeginAbsoluteSequence(HitObject.StartTime - HitObject.TimePreempt))
- this.FadeIn(200);
-
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
using (BeginAbsoluteSequence(endTime, true))
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
index ce2daebbf1..1af77b75fc 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
@@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AccentColour = Color4.Red,
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
Alpha = 0.5f,
Scale = new Vector2(1.333f)
});
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
index b9b6d5b924..1e9daf18db 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
- Blending = BlendingMode.Additive;
+ Blending = BlendingParameters.Additive;
Colour = Color4.White.Opacity(0.9f);
}
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json
new file mode 100644
index 0000000000..83f9e30800
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json
@@ -0,0 +1,150 @@
+{
+ "Mappings": [{
+ "StartTime": 369,
+ "Objects": [{
+ "StartTime": 369,
+ "Position": 177
+ },
+ {
+ "StartTime": 450,
+ "Position": 216.539276
+ },
+ {
+ "StartTime": 532,
+ "Position": 256.5667
+ },
+ {
+ "StartTime": 614,
+ "Position": 296.594116
+ },
+ {
+ "StartTime": 696,
+ "Position": 336.621521
+ },
+ {
+ "StartTime": 778,
+ "Position": 376.99762
+ },
+ {
+ "StartTime": 860,
+ "Position": 337.318878
+ },
+ {
+ "StartTime": 942,
+ "Position": 297.291443
+ },
+ {
+ "StartTime": 1024,
+ "Position": 257.264038
+ },
+ {
+ "StartTime": 1106,
+ "Position": 217.2366
+ },
+ {
+ "StartTime": 1188,
+ "Position": 177
+ },
+ {
+ "StartTime": 1270,
+ "Position": 216.818192
+ },
+ {
+ "StartTime": 1352,
+ "Position": 256.8456
+ },
+ {
+ "StartTime": 1434,
+ "Position": 296.873047
+ },
+ {
+ "StartTime": 1516,
+ "Position": 336.900452
+ },
+ {
+ "StartTime": 1598,
+ "Position": 376.99762
+ },
+ {
+ "StartTime": 1680,
+ "Position": 337.039948
+ },
+ {
+ "StartTime": 1762,
+ "Position": 297.0125
+ },
+ {
+ "StartTime": 1844,
+ "Position": 256.9851
+ },
+ {
+ "StartTime": 1926,
+ "Position": 216.957672
+ },
+ {
+ "StartTime": 2008,
+ "Position": 177
+ },
+ {
+ "StartTime": 2090,
+ "Position": 217.097137
+ },
+ {
+ "StartTime": 2172,
+ "Position": 257.124573
+ },
+ {
+ "StartTime": 2254,
+ "Position": 297.152
+ },
+ {
+ "StartTime": 2336,
+ "Position": 337.179443
+ },
+ {
+ "StartTime": 2418,
+ "Position": 376.99762
+ },
+ {
+ "StartTime": 2500,
+ "Position": 336.760956
+ },
+ {
+ "StartTime": 2582,
+ "Position": 296.733643
+ },
+ {
+ "StartTime": 2664,
+ "Position": 256.7062
+ },
+ {
+ "StartTime": 2746,
+ "Position": 216.678772
+ },
+ {
+ "StartTime": 2828,
+ "Position": 177
+ },
+ {
+ "StartTime": 2909,
+ "Position": 216.887909
+ },
+ {
+ "StartTime": 2991,
+ "Position": 256.915344
+ },
+ {
+ "StartTime": 3073,
+ "Position": 296.942749
+ },
+ {
+ "StartTime": 3155,
+ "Position": 336.970184
+ },
+ {
+ "StartTime": 3237,
+ "Position": 376.99762
+ }
+ ]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu
new file mode 100644
index 0000000000..c1971edf2c
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu
@@ -0,0 +1,18 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.4
+Mode: 0
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8
+SliderMultiplier:1.6
+SliderTickRate:4
+
+[TimingPoints]
+369,327.868852459016,4,2,2,32,1,0
+
+[HitObjects]
+177,191,369,6,0,L|382:192,7,200
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json
new file mode 100644
index 0000000000..7333b600fb
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json
@@ -0,0 +1,74 @@
+{
+ "Mappings": [{
+ "StartTime": 369,
+ "Objects": [{
+ "StartTime": 369,
+ "Position": 65
+ },
+ {
+ "StartTime": 450,
+ "Position": 482
+ },
+ {
+ "StartTime": 532,
+ "Position": 164
+ },
+ {
+ "StartTime": 614,
+ "Position": 315
+ },
+ {
+ "StartTime": 696,
+ "Position": 145
+ },
+ {
+ "StartTime": 778,
+ "Position": 159
+ },
+ {
+ "StartTime": 860,
+ "Position": 310
+ },
+ {
+ "StartTime": 942,
+ "Position": 441
+ },
+ {
+ "StartTime": 1024,
+ "Position": 428
+ },
+ {
+ "StartTime": 1106,
+ "Position": 243
+ },
+ {
+ "StartTime": 1188,
+ "Position": 422
+ },
+ {
+ "StartTime": 1270,
+ "Position": 481
+ },
+ {
+ "StartTime": 1352,
+ "Position": 104
+ },
+ {
+ "StartTime": 1434,
+ "Position": 473
+ },
+ {
+ "StartTime": 1516,
+ "Position": 135
+ },
+ {
+ "StartTime": 1598,
+ "Position": 360
+ },
+ {
+ "StartTime": 1680,
+ "Position": 123
+ }
+ ]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner.osu
new file mode 100644
index 0000000000..208d21a344
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner.osu
@@ -0,0 +1,18 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.4
+Mode: 0
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8
+SliderMultiplier:1.6
+SliderTickRate:4
+
+[TimingPoints]
+369,327.868852459016,4,2,2,32,1,0
+
+[HitObjects]
+256,192,369,12,0,1680,0:0:0:0:
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json
new file mode 100644
index 0000000000..bbc16ab912
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json
@@ -0,0 +1,234 @@
+{
+ "Mappings": [{
+ "StartTime": 369,
+ "Objects": [{
+ "StartTime": 369,
+ "Position": 258
+ }]
+ },
+ {
+ "StartTime": 450,
+ "Objects": [{
+ "StartTime": 450,
+ "Position": 254
+ }]
+ },
+ {
+ "StartTime": 532,
+ "Objects": [{
+ "StartTime": 532,
+ "Position": 241
+ }]
+ },
+ {
+ "StartTime": 614,
+ "Objects": [{
+ "StartTime": 614,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 696,
+ "Objects": [{
+ "StartTime": 696,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 778,
+ "Objects": [{
+ "StartTime": 778,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 860,
+ "Objects": [{
+ "StartTime": 860,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 942,
+ "Objects": [{
+ "StartTime": 942,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 1024,
+ "Objects": [{
+ "StartTime": 1024,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 1106,
+ "Objects": [{
+ "StartTime": 1106,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 1188,
+ "Objects": [{
+ "StartTime": 1188,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 1270,
+ "Objects": [{
+ "StartTime": 1270,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 1352,
+ "Objects": [{
+ "StartTime": 1352,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 1434,
+ "Objects": [{
+ "StartTime": 1434,
+ "Position": 258
+ }]
+ },
+ {
+ "StartTime": 1516,
+ "Objects": [{
+ "StartTime": 1516,
+ "Position": 253
+ }]
+ },
+ {
+ "StartTime": 1598,
+ "Objects": [{
+ "StartTime": 1598,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 1680,
+ "Objects": [{
+ "StartTime": 1680,
+ "Position": 260
+ }]
+ },
+ {
+ "StartTime": 1762,
+ "Objects": [{
+ "StartTime": 1762,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 1844,
+ "Objects": [{
+ "StartTime": 1844,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 1926,
+ "Objects": [{
+ "StartTime": 1926,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 2008,
+ "Objects": [{
+ "StartTime": 2008,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 2090,
+ "Objects": [{
+ "StartTime": 2090,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 2172,
+ "Objects": [{
+ "StartTime": 2172,
+ "Position": 243
+ }]
+ },
+ {
+ "StartTime": 2254,
+ "Objects": [{
+ "StartTime": 2254,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 2336,
+ "Objects": [{
+ "StartTime": 2336,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 2418,
+ "Objects": [{
+ "StartTime": 2418,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 2500,
+ "Objects": [{
+ "StartTime": 2500,
+ "Position": 258
+ }]
+ },
+ {
+ "StartTime": 2582,
+ "Objects": [{
+ "StartTime": 2582,
+ "Position": 256
+ }]
+ },
+ {
+ "StartTime": 2664,
+ "Objects": [{
+ "StartTime": 2664,
+ "Position": 242
+ }]
+ },
+ {
+ "StartTime": 2746,
+ "Objects": [{
+ "StartTime": 2746,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 2828,
+ "Objects": [{
+ "StartTime": 2828,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 2909,
+ "Objects": [{
+ "StartTime": 2909,
+ "Position": 271
+ }]
+ },
+ {
+ "StartTime": 2991,
+ "Objects": [{
+ "StartTime": 2991,
+ "Position": 254
+ }]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu
new file mode 100644
index 0000000000..321af4604b
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu
@@ -0,0 +1,50 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.4
+Mode: 0
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+369,327.868852459016,4,2,2,32,1,0
+
+[HitObjects]
+258,189,369,1,0,0:0:0:0:
+258,189,450,1,0,0:0:0:0:
+258,189,532,1,0,0:0:0:0:
+258,189,614,1,0,0:0:0:0:
+258,189,696,1,0,0:0:0:0:
+258,189,778,1,0,0:0:0:0:
+258,189,860,1,0,0:0:0:0:
+258,189,942,1,0,0:0:0:0:
+258,189,1024,1,0,0:0:0:0:
+258,189,1106,1,0,0:0:0:0:
+258,189,1188,1,0,0:0:0:0:
+258,189,1270,1,0,0:0:0:0:
+258,189,1352,1,0,0:0:0:0:
+258,189,1434,1,0,0:0:0:0:
+258,189,1516,1,0,0:0:0:0:
+258,189,1598,1,0,0:0:0:0:
+258,189,1680,1,0,0:0:0:0:
+258,189,1762,1,0,0:0:0:0:
+258,189,1844,1,0,0:0:0:0:
+258,189,1926,1,0,0:0:0:0:
+258,189,2008,1,0,0:0:0:0:
+258,189,2090,1,0,0:0:0:0:
+258,189,2172,1,0,0:0:0:0:
+258,189,2254,1,0,0:0:0:0:
+258,189,2336,1,0,0:0:0:0:
+258,189,2418,1,0,0:0:0:0:
+258,189,2500,1,0,0:0:0:0:
+258,189,2582,1,0,0:0:0:0:
+258,189,2664,1,0,0:0:0:0:
+258,189,2746,1,0,0:0:0:0:
+258,189,2828,1,0,0:0:0:0:
+258,189,2909,1,0,0:0:0:0:
+258,189,2991,1,0,0:0:0:0:
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitWindows.cs b/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs
similarity index 87%
rename from osu.Game.Rulesets.Catch/Objects/CatchHitWindows.cs
rename to osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs
index 837662f5fe..ff793a372e 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitWindows.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs
@@ -1,10 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
-namespace osu.Game.Rulesets.Catch.Objects
+namespace osu.Game.Rulesets.Catch.Scoring
{
public class CatchHitWindows : HitWindows
{
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index 99b22b2d56..18785d65ea 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -4,7 +4,6 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 0b06e958e6..ceda643335 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.UI
if (lastPlateableFruit == null)
return;
- // this is required to make this run after the last caught fruit runs UpdateState at least once.
+ // this is required to make this run after the last caught fruit runs updateState() at least once.
// TODO: find a better alternative
if (lastPlateableFruit.IsLoaded)
action();
@@ -201,7 +201,7 @@ namespace osu.Game.Rulesets.Catch.UI
additive.Scale = Scale;
additive.Colour = HyperDashing ? Color4.Red : Color4.White;
additive.RelativePositionAxes = RelativePositionAxes;
- additive.Blending = BlendingMode.Additive;
+ additive.Blending = BlendingParameters.Additive;
AdditiveTarget.Add(additive);
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
index c0c1952064..e3c6c93d01 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.UI
[BackgroundDependencyLoader]
private void load()
{
- InternalChild = new SkinnableSprite(@"Play/Catch/fruit-catcher-idle")
+ InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle")
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.TopCentre,
diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
index f48b84e344..6b7f00c5d0 100644
--- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
@@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Catch.UI
protected override bool UserScrollSpeedAdjustment => false;
- public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableCatchRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
Direction.Value = ScrollingDirection.Down;
- TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
+ TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
}
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);
diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj
index 24abccb19d..58c2e2aa5a 100644
--- a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj
+++ b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj
@@ -13,14 +13,6 @@
-
- libbass.a
- PreserveNewest
-
-
- libbass_fx.a
- PreserveNewest
-
Linker.xml
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index 6b95975059..6f10540973 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -20,10 +20,7 @@ namespace osu.Game.Rulesets.Mania.Tests
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
[TestCase("basic")]
- public new void Test(string name)
- {
- base.Test(name);
- }
+ public void Test(string name) => base.Test(name);
protected override IEnumerable CreateConvertValue(HitObject hitObject)
{
@@ -35,11 +32,37 @@ namespace osu.Game.Rulesets.Mania.Tests
};
}
- protected override ManiaConvertMapping CreateConvertMapping() => new ManiaConvertMapping(Converter);
+ private readonly Dictionary rngSnapshots = new Dictionary();
+
+ protected override void OnConversionGenerated(HitObject original, IEnumerable result, IBeatmapConverter beatmapConverter)
+ {
+ base.OnConversionGenerated(original, result, beatmapConverter);
+
+ rngSnapshots[original] = new RngSnapshot(beatmapConverter);
+ }
+
+ protected override ManiaConvertMapping CreateConvertMapping(HitObject source) => new ManiaConvertMapping(rngSnapshots[source]);
protected override Ruleset CreateRuleset() => new ManiaRuleset();
}
+ public class RngSnapshot
+ {
+ public readonly uint RandomW;
+ public readonly uint RandomX;
+ public readonly uint RandomY;
+ public readonly uint RandomZ;
+
+ public RngSnapshot(IBeatmapConverter converter)
+ {
+ var maniaConverter = (ManiaBeatmapConverter)converter;
+ RandomW = maniaConverter.Random.W;
+ RandomX = maniaConverter.Random.X;
+ RandomY = maniaConverter.Random.Y;
+ RandomZ = maniaConverter.Random.Z;
+ }
+ }
+
public class ManiaConvertMapping : ConvertMapping, IEquatable
{
public uint RandomW;
@@ -51,13 +74,12 @@ namespace osu.Game.Rulesets.Mania.Tests
{
}
- public ManiaConvertMapping(IBeatmapConverter converter)
+ public ManiaConvertMapping(RngSnapshot snapshot)
{
- var maniaConverter = (ManiaBeatmapConverter)converter;
- RandomW = maniaConverter.Random.W;
- RandomX = maniaConverter.Random.X;
- RandomY = maniaConverter.Random.Y;
- RandomZ = maniaConverter.Random.Z;
+ RandomW = snapshot.RandomW;
+ RandomX = snapshot.RandomX;
+ RandomY = snapshot.RandomY;
+ RandomZ = snapshot.RandomZ;
}
public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ;
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
index 20ac5eaa39..f260357df5 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
@@ -3,6 +3,7 @@
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Testing;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays;
@@ -12,6 +13,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
+ [HeadlessTest]
public class TestSceneAutoGeneration : OsuTestScene
{
[Test]
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
index 031abb08e2..8dae5e6d84 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
@@ -1,4 +1,4 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
@@ -11,6 +11,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
@@ -40,6 +41,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
Child = new FillFlowContainer
{
+ Clock = new FramedClock(new ManualClock()),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
@@ -62,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Tests
private Drawable createNoteDisplay(ScrollingDirection direction, int identifier, out DrawableNote hitObject)
{
- var note = new Note { StartTime = 999999999 };
+ var note = new Note { StartTime = 0 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return new ScrollingTestContainer(direction)
@@ -77,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Tests
private Drawable createHoldNoteDisplay(ScrollingDirection direction, int identifier, out DrawableHoldNote hitObject)
{
- var note = new HoldNote { StartTime = 999999999, Duration = 5000 };
+ var note = new HoldNote { StartTime = 0, Duration = 5000 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return new ScrollingTestContainer(direction)
@@ -133,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Tests
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Both,
Width = 1.25f,
- Colour = Color4.Black.Opacity(0.5f)
+ Colour = Color4.Green.Opacity(0.5f)
},
content = new Container { RelativeSizeAxes = Axes.Both }
}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
index 395e6daf0a..e7fd601abe 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
@@ -15,6 +15,7 @@ using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
using osuTK;
@@ -114,8 +115,7 @@ namespace osu.Game.Rulesets.Mania.Tests
var obj = new BarLine
{
StartTime = Time.Current + 2000,
- ControlPoint = new TimingControlPoint(),
- BeatIndex = major ? 0 : 1
+ Major = major,
};
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index df5131dd8b..af10d5e06e 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs
index a3cd455886..e4a28167ec 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
///
/// Keep the same as last row.
///
- ForceStack = 1 << 0,
+ ForceStack = 1,
///
/// Keep different from last row.
diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
index b591f9da22..f5412dcfc5 100644
--- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
+++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
{
base.InitialiseDefaults();
- Set(ManiaRulesetSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0);
+ Set(ManiaRulesetSetting.ScrollTime, 1500.0, 50.0, 5000.0, 50.0);
Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index 4a9c22d339..37cba1fd3c 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -11,7 +11,9 @@ using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mania.Difficulty.Skills;
using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Difficulty
{
@@ -32,12 +34,15 @@ namespace osu.Game.Rulesets.Mania.Difficulty
if (beatmap.HitObjects.Count == 0)
return new ManiaDifficultyAttributes { Mods = mods, Skills = skills };
+ HitWindows hitWindows = new ManiaHitWindows();
+ hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+
return new ManiaDifficultyAttributes
{
StarRating = difficultyValue(skills) * star_scaling_factor,
Mods = mods,
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
- GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate,
+ GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate,
Skills = skills
};
}
diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
index e5f379f608..97d8aaa052 100644
--- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
+++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Edit
{
public new IScrollingInfo ScrollingInfo => base.ScrollingInfo;
- public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableManiaEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
index 2729621ab3..0bfe6f9517 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Edit
[Cached(Type = typeof(IManiaHitObjectComposer))]
public class ManiaHitObjectComposer : HitObjectComposer, IManiaHitObjectComposer
{
- protected new DrawableManiaEditRuleset DrawableRuleset { get; private set; }
+ private DrawableManiaEditRuleset drawableRuleset;
public ManiaHitObjectComposer(Ruleset ruleset)
: base(ruleset)
@@ -33,23 +33,23 @@ namespace osu.Game.Rulesets.Mania.Edit
///
/// The screen-space position.
/// The column which intersects with .
- public Column ColumnAt(Vector2 screenSpacePosition) => DrawableRuleset.GetColumnByPosition(screenSpacePosition);
+ public Column ColumnAt(Vector2 screenSpacePosition) => drawableRuleset.GetColumnByPosition(screenSpacePosition);
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
- public int TotalColumns => ((ManiaPlayfield)DrawableRuleset.Playfield).TotalColumns;
+ public int TotalColumns => ((ManiaPlayfield)drawableRuleset.Playfield).TotalColumns;
- protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
{
- DrawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods);
+ drawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods);
// This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it
- dependencies.CacheAs(DrawableRuleset.ScrollingInfo);
+ dependencies.CacheAs(drawableRuleset.ScrollingInfo);
- return DrawableRuleset;
+ return drawableRuleset;
}
protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index d83033f9c6..0c4e7d4858 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -13,7 +13,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics;
-using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
@@ -32,10 +31,12 @@ namespace osu.Game.Rulesets.Mania
{
public class ManiaRuleset : Ruleset
{
- public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableManiaRuleset(this, beatmap, mods);
+ public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableManiaRuleset(this, beatmap, mods);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);
+ public const string SHORT_NAME = "mania";
+
public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this);
public override IEnumerable ConvertLegacyMods(LegacyMods mods)
@@ -154,7 +155,7 @@ namespace osu.Game.Rulesets.Mania
case ModType.Fun:
return new Mod[]
{
- new MultiMod(new ModWindUp(), new ModWindDown())
+ new MultiMod(new ModWindUp(), new ModWindDown())
};
default:
@@ -164,7 +165,7 @@ namespace osu.Game.Rulesets.Mania
public override string Description => "osu!mania";
- public override string ShortName => "mania";
+ public override string ShortName => SHORT_NAME;
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetMania };
diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs
new file mode 100644
index 0000000000..69bd4b0ecf
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Mania
+{
+ public class ManiaSkinComponent : GameplaySkinComponent
+ {
+ public ManiaSkinComponent(ManiaSkinComponents component)
+ : base(component)
+ {
+ }
+
+ protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME;
+
+ protected override string ComponentName => Component.ToString().ToLower();
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponents.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponents.cs
new file mode 100644
index 0000000000..6d85816e5a
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/ManiaSkinComponents.cs
@@ -0,0 +1,9 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Mania
+{
+ public enum ManiaSkinComponents
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs
index 17f4098420..485595cea9 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mania.Beatmaps;
namespace osu.Game.Rulesets.Mania.Mods
{
- public class ManiaModMirror : Mod, IApplicableToBeatmap
+ public class ManiaModMirror : Mod, IApplicableToBeatmap
{
public override string Name => "Mirror";
public override string Acronym => "MR";
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override double ScoreMultiplier => 1;
public override bool Ranked => true;
- public void ApplyToBeatmap(Beatmap beatmap)
+ public void ApplyToBeatmap(IBeatmap beatmap)
{
var availableColumns = ((ManiaBeatmap)beatmap).TotalColumns;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
index ba16140644..9275371a61 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
@@ -13,7 +13,7 @@ using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
{
- public class ManiaModRandom : Mod, IApplicableToBeatmap
+ public class ManiaModRandom : Mod, IApplicableToBeatmap
{
public override string Name => "Random";
public override string Acronym => "RD";
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Description => @"Shuffle around the keys!";
public override double ScoreMultiplier => 1;
- public void ApplyToBeatmap(Beatmap beatmap)
+ public void ApplyToBeatmap(IBeatmap beatmap)
{
var availableColumns = ((ManiaBeatmap)beatmap).TotalColumns;
var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => RNG.Next()).ToList();
diff --git a/osu.Game.Rulesets.Mania/Objects/BarLine.cs b/osu.Game.Rulesets.Mania/Objects/BarLine.cs
deleted file mode 100644
index 4c644a8f09..0000000000
--- a/osu.Game.Rulesets.Mania/Objects/BarLine.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Game.Beatmaps.ControlPoints;
-
-namespace osu.Game.Rulesets.Mania.Objects
-{
- public class BarLine : ManiaHitObject
- {
- ///
- /// The control point which this bar line is part of.
- ///
- public TimingControlPoint ControlPoint;
-
- ///
- /// The index of the beat which this bar line represents within the control point.
- /// This is a "major" bar line if % == 0.
- ///
- public int BeatIndex;
- }
-}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
index 9c3197504f..be21610525 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
@@ -4,6 +4,7 @@
using osuTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK.Graphics;
@@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// Visualises a . Although this derives DrawableManiaHitObject,
/// this does not handle input/sound like a normal hit object.
///
- public class DrawableBarLine : DrawableManiaHitObject
+ public class DrawableBarLine : DrawableHitObject
{
///
/// Height of major bar line triangles.
@@ -40,9 +41,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Colour = new Color4(255, 204, 33, 255),
});
- bool isMajor = barLine.BeatIndex % (int)barLine.ControlPoint.TimeSignature == 0;
-
- if (isMajor)
+ if (barLine.Major)
{
AddInternal(new EquilateralTriangle
{
@@ -65,11 +64,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
});
}
- if (!isMajor && barLine.BeatIndex % 2 == 1)
+ if (!barLine.Major)
Alpha = 0.2f;
}
- protected override void UpdateState(ArmedState state)
+ protected override void UpdateInitialTransforms()
+ {
+ }
+
+ protected override void UpdateStateTransforms(ArmedState state)
{
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 952c6e128e..c5c157608f 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Diagnostics;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
@@ -8,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling;
@@ -104,6 +106,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
}
+ protected override void UpdateStateTransforms(ArmedState state)
+ {
+ using (BeginDelayedSequence(HitObject.Duration, true))
+ base.UpdateStateTransforms(state);
+ }
+
protected void BeginHold()
{
holdStartTime = Time.Current;
@@ -202,6 +210,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
+ Debug.Assert(HitObject.HitWindows != null);
+
// Factor in the release lenience
timeOffset /= release_window_lenience;
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
index db6b53e76d..e5b114ca81 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
@@ -45,24 +45,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
}
- }
- public abstract class DrawableManiaHitObject : DrawableManiaHitObject
- where TObject : ManiaHitObject
- {
- public new readonly TObject HitObject;
-
- protected DrawableManiaHitObject(TObject hitObject)
- : base(hitObject)
+ protected override void UpdateStateTransforms(ArmedState state)
{
- HitObject = hitObject;
- }
-
- protected override bool UseTransformStateManagement => false;
-
- protected override void UpdateState(ArmedState state)
- {
- // TODO: update to use new state management.
switch (state)
{
case ArmedState.Miss:
@@ -75,4 +60,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
}
}
}
+
+ public abstract class DrawableManiaHitObject : DrawableManiaHitObject
+ where TObject : ManiaHitObject
+ {
+ public new readonly TObject HitObject;
+
+ protected DrawableManiaHitObject(TObject hitObject)
+ : base(hitObject)
+ {
+ HitObject = hitObject;
+ }
+ }
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
index dccff7f6ac..2cd81104a3 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Diagnostics;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -52,6 +53,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
+ Debug.Assert(HitObject.HitWindows != null);
+
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs
index 8102718edf..31a4857805 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs
@@ -26,14 +26,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
public BodyPiece()
{
- Blending = BlendingMode.Additive;
+ Blending = BlendingParameters.Additive;
Children = new[]
{
Background = new Box { RelativeSizeAxes = Axes.Both },
Foreground = new BufferedContainer
{
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
CacheDrawnFrameBuffer = true,
Children = new Drawable[]
@@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
}
}
- private Cached subtractionCache = new Cached();
+ private readonly Cached subtractionCache = new Cached();
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
{
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs
index 9e0307c5c2..48c7ea7b7f 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs
@@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
Name = "Top",
RelativeSizeAxes = Axes.Both,
Height = 0.5f,
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
Colour = ColourInfo.GradientVertical(Color4.Transparent, Color4.White.Opacity(alpha))
},
new Box
@@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
Height = 0.5f,
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
Colour = ColourInfo.GradientVertical(Color4.White.Opacity(alpha), Color4.Transparent)
}
};
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index 5e9f46d9c7..0c82cf7bbc 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -6,6 +6,7 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects
{
@@ -99,5 +100,7 @@ namespace osu.Game.Rulesets.Mania.Objects
}
public override Judgement CreateJudgement() => new HoldNoteJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs
index c133ee73b1..d0125f8793 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs
@@ -3,6 +3,7 @@
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects
{
@@ -12,5 +13,7 @@ namespace osu.Game.Rulesets.Mania.Objects
public class HoldNoteTick : ManiaHitObject
{
public override Judgement CreateJudgement() => new HoldNoteTickJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
index 70720a926b..995e1516cb 100644
--- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
@@ -3,7 +3,9 @@
using osu.Framework.Bindables;
using osu.Game.Rulesets.Mania.Objects.Types;
+using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects
{
diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
deleted file mode 100644
index 5f2ceab48b..0000000000
--- a/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Scoring;
-
-namespace osu.Game.Rulesets.Mania.Objects
-{
- public class ManiaHitWindows : HitWindows
- {
- private static readonly IReadOnlyDictionary base_ranges = new Dictionary
- {
- { HitResult.Perfect, (44.8, 38.8, 27.8) },
- { HitResult.Great, (128, 98, 68) },
- { HitResult.Good, (194, 164, 134) },
- { HitResult.Ok, (254, 224, 194) },
- { HitResult.Meh, (302, 272, 242) },
- { HitResult.Miss, (376, 346, 316) },
- };
-
- public override bool IsHitResultAllowed(HitResult result) => true;
-
- public override void SetDifficulty(double difficulty)
- {
- Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]);
- Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
- Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
- Ok = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Ok]);
- Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
- Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
index e5669816fa..7b8bbc2095 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays;
@@ -77,13 +78,37 @@ namespace osu.Game.Rulesets.Mania.Replays
private IEnumerable generateActionPoints()
{
- foreach (var obj in Beatmap.HitObjects)
+ for (int i = 0; i < Beatmap.HitObjects.Count; i++)
{
- yield return new HitPoint { Time = obj.StartTime, Column = obj.Column };
- yield return new ReleasePoint { Time = ((obj as IHasEndTime)?.EndTime ?? obj.StartTime) + RELEASE_DELAY, Column = obj.Column };
+ var currentObject = Beatmap.HitObjects[i];
+ var nextObjectInColumn = GetNextObject(i); // Get the next object that requires pressing the same button
+
+ double endTime = (currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime;
+
+ bool canDelayKeyUp = nextObjectInColumn == null ||
+ nextObjectInColumn.StartTime > endTime + RELEASE_DELAY;
+
+ double calculatedDelay = canDelayKeyUp ? RELEASE_DELAY : (nextObjectInColumn.StartTime - endTime) * 0.9;
+
+ yield return new HitPoint { Time = currentObject.StartTime, Column = currentObject.Column };
+
+ yield return new ReleasePoint { Time = endTime + calculatedDelay, Column = currentObject.Column };
}
}
+ protected override HitObject GetNextObject(int currentIndex)
+ {
+ int desiredColumn = Beatmap.HitObjects[currentIndex].Column;
+
+ for (int i = currentIndex + 1; i < Beatmap.HitObjects.Count; i++)
+ {
+ if (Beatmap.HitObjects[i].Column == desiredColumn)
+ return Beatmap.HitObjects[i];
+ }
+
+ return null;
+ }
+
private interface IActionPoint
{
double Time { get; set; }
diff --git a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
similarity index 58%
rename from osu.Game.Rulesets.Taiko/Objects/BarLine.cs
rename to osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
index a07012fd71..549f0f9214 100644
--- a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
@@ -1,9 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-namespace osu.Game.Rulesets.Taiko.Objects
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Scoring
{
- public class BarLine : TaikoHitObject
+ public class ManiaHitWindows : HitWindows
{
}
}
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
index 5caf08fb1e..49894a644c 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
@@ -4,7 +4,6 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
index b4e29ae9f9..5ee78aa496 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
Name = "Background Gradient Overlay",
RelativeSizeAxes = Axes.Both,
Height = 0.5f,
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
Alpha = 0
}
};
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
index c8aeda8fe4..29863fba2e 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
@@ -2,14 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
-using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Input;
-using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Input.Handlers;
using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Beatmaps;
@@ -19,8 +16,8 @@ using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
@@ -36,40 +33,16 @@ namespace osu.Game.Rulesets.Mania.UI
public IEnumerable BarLines;
+ protected override bool RelativeScaleBeatLengths => true;
+
protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config;
private readonly Bindable configDirection = new Bindable();
- public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableManiaRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
- // Generate the bar lines
- double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
-
- var timingPoints = Beatmap.ControlPointInfo.TimingPoints;
- var barLines = new List();
-
- for (int i = 0; i < timingPoints.Count; i++)
- {
- TimingControlPoint point = timingPoints[i];
-
- // Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object
- double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - point.BeatLength : lastObjectTime + point.BeatLength * (int)point.TimeSignature;
-
- int index = 0;
-
- for (double t = timingPoints[i].Time; Precision.DefinitelyBigger(endTime, t); t += point.BeatLength, index++)
- {
- barLines.Add(new BarLine
- {
- StartTime = t,
- ControlPoint = point,
- BeatIndex = index
- });
- }
- }
-
- BarLines = barLines;
+ BarLines = new BarLineGenerator(Beatmap).BarLines;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
index 5ab07416a6..12faa499ad 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
@@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
index a28de7ea58..98a4b7d0b6 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
@@ -12,6 +12,7 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj
index 9930a166e3..c7787bd162 100644
--- a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj
+++ b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj
@@ -13,14 +13,6 @@
-
- libbass.a
- PreserveNewest
-
-
- libbass_fx.a
- PreserveNewest
-
Linker.xml
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index f98d63e6c7..e9fdf924c3 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
@@ -8,7 +8,6 @@ using osu.Framework.MathUtils;
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.Tests.Beatmaps;
namespace osu.Game.Rulesets.Osu.Tests
@@ -21,10 +20,10 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestCase("basic")]
[TestCase("colinear-perfect-curve")]
[TestCase("slider-ticks")]
- public new void Test(string name)
- {
- base.Test(name);
- }
+ [TestCase("repeat-slider")]
+ [TestCase("uneven-repeat-slider")]
+ [TestCase("old-stacking")]
+ public void Test(string name) => base.Test(name);
protected override IEnumerable CreateConvertValue(HitObject hitObject)
{
@@ -32,22 +31,22 @@ namespace osu.Game.Rulesets.Osu.Tests
{
case Slider slider:
foreach (var nested in slider.NestedHitObjects)
- yield return createConvertValue(nested);
+ yield return createConvertValue((OsuHitObject)nested);
break;
default:
- yield return createConvertValue(hitObject);
+ yield return createConvertValue((OsuHitObject)hitObject);
break;
}
- ConvertValue createConvertValue(HitObject obj) => new ConvertValue
+ ConvertValue createConvertValue(OsuHitObject obj) => new ConvertValue
{
StartTime = obj.StartTime,
EndTime = (obj as IHasEndTime)?.EndTime ?? obj.StartTime,
- X = (obj as IHasPosition)?.X ?? OsuPlayfield.BASE_SIZE.X / 2,
- Y = (obj as IHasPosition)?.Y ?? OsuPlayfield.BASE_SIZE.Y / 2,
+ X = obj.StackedPosition.X,
+ Y = obj.StackedPosition.Y
};
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/approachcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/approachcircle@2x.png
new file mode 100644
index 0000000000..72ef665478
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/approachcircle@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit0@2x.png
new file mode 100644
index 0000000000..a91072eb5b
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit0@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit100@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit100@2x.png
new file mode 100644
index 0000000000..5eb202c021
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit100@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit300@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit300@2x.png
new file mode 100644
index 0000000000..878c11cd67
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit300@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit50@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit50@2x.png
new file mode 100644
index 0000000000..f64feded0c
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit50@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hitcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hitcircle@2x.png
new file mode 100644
index 0000000000..043bfbfae1
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hitcircle@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hitcircleoverlay@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hitcircleoverlay@2x.png
new file mode 100644
index 0000000000..4233d9bb6e
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hitcircleoverlay@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/approachcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/approachcircle@2x.png
new file mode 100755
index 0000000000..0a6ec6535c
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/approachcircle@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-0@2x.png
new file mode 100644
index 0000000000..37e7e9143f
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-0@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-1@2x.png
new file mode 100644
index 0000000000..b75c71927c
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-1@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-0@2x.png
new file mode 100644
index 0000000000..7932667408
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-0@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-1@2x.png
new file mode 100644
index 0000000000..0b0ae85972
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-1@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-0@2x.png
new file mode 100644
index 0000000000..441c3ec21f
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-0@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-1@2x.png
new file mode 100644
index 0000000000..910d8e2231
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-1@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-0@2x.png
new file mode 100644
index 0000000000..6f92db28d3
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-0@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-1@2x.png
new file mode 100644
index 0000000000..b28503e9f2
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-1@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircle@2x.png
new file mode 100755
index 0000000000..919d8f405c
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircle@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png
new file mode 100755
index 0000000000..a9b2d95d88
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
new file mode 100644
index 0000000000..38aac50df6
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
@@ -0,0 +1,82 @@
+// 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.Text.RegularExpressions;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.IO.Stores;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public abstract class SkinnableTestScene : OsuGridTestScene
+ {
+ private Skin metricsSkin;
+ private Skin defaultSkin;
+ private Skin specialSkin;
+
+ protected SkinnableTestScene()
+ : base(2, 2)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio, SkinManager skinManager)
+ {
+ var dllStore = new DllResourceStore("osu.Game.Rulesets.Osu.Tests.dll");
+
+ metricsSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true);
+ defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
+ specialSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/special_skin"), audio, true);
+ }
+
+ public void SetContents(Func creationFunction)
+ {
+ Cell(0).Child = createProvider(null, creationFunction);
+ Cell(1).Child = createProvider(metricsSkin, creationFunction);
+ Cell(2).Child = createProvider(defaultSkin, creationFunction);
+ Cell(3).Child = createProvider(specialSkin, creationFunction);
+ }
+
+ private Drawable createProvider(Skin skin, Func creationFunction)
+ {
+ var mainProvider = new SkinProvidingContainer(skin);
+
+ return mainProvider
+ .WithChild(new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider))
+ {
+ Child = creationFunction()
+ });
+ }
+
+ private class TestLegacySkin : LegacySkin
+ {
+ private readonly bool extrapolateAnimations;
+
+ public TestLegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager, bool extrapolateAnimations)
+ : base(skin, storage, audioManager, "skin.ini")
+ {
+ this.extrapolateAnimations = extrapolateAnimations;
+ }
+
+ public override Texture GetTexture(string componentName)
+ {
+ // extrapolate frames to test longer animations
+ if (extrapolateAnimations)
+ {
+ var match = Regex.Match(componentName, "-([0-9]*)");
+
+ if (match.Length > 0 && int.TryParse(match.Groups[1].Value, out var number) && number < 60)
+ return base.GetTexture(componentName.Replace($"-{number}", $"-{number % 2}"));
+ }
+
+ return base.GetTexture(componentName);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
new file mode 100644
index 0000000000..685a51d208
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
@@ -0,0 +1,128 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Testing.Input;
+using osu.Game.Audio;
+using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Rulesets.Osu.UI.Cursor;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneCursorTrail : OsuTestScene
+ {
+ [Test]
+ public void TestSmoothCursorTrail()
+ {
+ Container scalingContainer = null;
+
+ createTest(() => scalingContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new CursorTrail()
+ });
+
+ AddStep("set large scale", () => scalingContainer.Scale = new Vector2(10));
+ }
+
+ [Test]
+ public void TestLegacySmoothCursorTrail()
+ {
+ createTest(() => new LegacySkinContainer(false)
+ {
+ Child = new LegacyCursorTrail()
+ });
+ }
+
+ [Test]
+ public void TestLegacyDisjointCursorTrail()
+ {
+ createTest(() => new LegacySkinContainer(true)
+ {
+ Child = new LegacyCursorTrail()
+ });
+ }
+
+ private void createTest(Func createContent) => AddStep("create trail", () =>
+ {
+ Clear();
+
+ Add(new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.8f),
+ Child = new MovingCursorInputManager { Child = createContent?.Invoke() }
+ });
+ });
+
+ [Cached(typeof(ISkinSource))]
+ private class LegacySkinContainer : Container, ISkinSource
+ {
+ private readonly bool disjoint;
+
+ public LegacySkinContainer(bool disjoint)
+ {
+ this.disjoint = disjoint;
+
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException();
+
+ public Texture GetTexture(string componentName)
+ {
+ switch (componentName)
+ {
+ case "cursortrail":
+ var tex = new Texture(Texture.WhitePixel.TextureGL);
+
+ if (disjoint)
+ tex.ScaleAdjust = 1 / 25f;
+ return tex;
+
+ case "cursormiddle":
+ return disjoint ? null : Texture.WhitePixel;
+ }
+
+ return null;
+ }
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
+
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
+
+ public event Action SourceChanged;
+ }
+
+ private class MovingCursorInputManager : ManualInputManager
+ {
+ public MovingCursorInputManager()
+ {
+ UseParentInput = false;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ const double spin_duration = 1000;
+ double currentTime = Time.Current;
+
+ double angle = (currentTime % spin_duration) / spin_duration * 2 * Math.PI;
+ Vector2 rPos = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
+
+ MoveMouseTo(ToScreenSpace(DrawSize / 2 + DrawSize / 3 * rPos));
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
new file mode 100644
index 0000000000..433ec6bd25
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
@@ -0,0 +1,35 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Extensions;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneDrawableJudgement : SkinnableTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DrawableJudgement),
+ typeof(DrawableOsuJudgement)
+ };
+
+ public TestSceneDrawableJudgement()
+ {
+ foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1))
+ AddStep("Show " + result.GetDescription(), () => SetContents(() =>
+ new DrawableOsuJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }));
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
index 1b1cfa89c0..aa170eae1e 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
@@ -6,29 +6,68 @@ using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Cursor;
-using osu.Game.Graphics.Cursor;
+using osu.Framework.Testing.Input;
using osu.Game.Rulesets.Osu.UI.Cursor;
-using osu.Game.Rulesets.UI;
-using osu.Game.Tests.Visual;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
- public class TestSceneGameplayCursor : OsuTestScene, IProvideCursor
+ public class TestSceneGameplayCursor : SkinnableTestScene
{
- private GameplayCursorContainer cursorContainer;
-
- public override IReadOnlyList RequiredTypes => new[] { typeof(CursorTrail) };
-
- public CursorContainer Cursor => cursorContainer;
-
- public bool ProvidingUserCursor => true;
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(OsuCursorContainer),
+ typeof(CursorTrail)
+ };
[BackgroundDependencyLoader]
private void load()
{
- Add(cursorContainer = new OsuCursorContainer { RelativeSizeAxes = Axes.Both });
+ SetContents(() => new MovingCursorInputManager
+ {
+ Child = new ClickingCursorContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ }
+ });
+ }
+
+ private class ClickingCursorContainer : OsuCursorContainer
+ {
+ protected override void Update()
+ {
+ base.Update();
+
+ double currentTime = Time.Current;
+
+ if (((int)(currentTime / 1000)) % 2 == 0)
+ OnPressed(OsuAction.LeftButton);
+ else
+ OnReleased(OsuAction.LeftButton);
+ }
+ }
+
+ private class MovingCursorInputManager : ManualInputManager
+ {
+ public MovingCursorInputManager()
+ {
+ UseParentInput = false;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ const double spin_duration = 5000;
+ double currentTime = Time.Current;
+
+ double angle = (currentTime % spin_duration) / spin_duration * 2 * Math.PI;
+ Vector2 rPos = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
+
+ MoveMouseTo(ToScreenSpace(DrawSize / 2 + DrawSize / 3 * rPos));
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
index d44a0cd841..84a7bfc53e 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
@@ -7,7 +7,6 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Tests.Visual;
using osuTK;
using System.Collections.Generic;
using System;
@@ -19,37 +18,32 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
- public class TestSceneHitCircle : OsuTestScene
+ public class TestSceneHitCircle : SkinnableTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
typeof(DrawableHitCircle)
};
- private readonly Container content;
- protected override Container Content => content;
-
private int depthIndex;
public TestSceneHitCircle()
{
- base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
-
- AddStep("Miss Big Single", () => testSingle(2));
- AddStep("Miss Medium Single", () => testSingle(5));
- AddStep("Miss Small Single", () => testSingle(7));
- AddStep("Hit Big Single", () => testSingle(2, true));
- AddStep("Hit Medium Single", () => testSingle(5, true));
- AddStep("Hit Small Single", () => testSingle(7, true));
- AddStep("Miss Big Stream", () => testStream(2));
- AddStep("Miss Medium Stream", () => testStream(5));
- AddStep("Miss Small Stream", () => testStream(7));
- AddStep("Hit Big Stream", () => testStream(2, true));
- AddStep("Hit Medium Stream", () => testStream(5, true));
- AddStep("Hit Small Stream", () => testStream(7, true));
+ AddStep("Miss Big Single", () => SetContents(() => testSingle(2)));
+ AddStep("Miss Medium Single", () => SetContents(() => testSingle(5)));
+ AddStep("Miss Small Single", () => SetContents(() => testSingle(7)));
+ AddStep("Hit Big Single", () => SetContents(() => testSingle(2, true)));
+ AddStep("Hit Medium Single", () => SetContents(() => testSingle(5, true)));
+ AddStep("Hit Small Single", () => SetContents(() => testSingle(7, true)));
+ AddStep("Miss Big Stream", () => SetContents(() => testStream(2)));
+ AddStep("Miss Medium Stream", () => SetContents(() => testStream(5)));
+ AddStep("Miss Small Stream", () => SetContents(() => testStream(7)));
+ AddStep("Hit Big Stream", () => SetContents(() => testStream(2, true)));
+ AddStep("Hit Medium Stream", () => SetContents(() => testStream(5, true)));
+ AddStep("Hit Small Stream", () => SetContents(() => testStream(7, true)));
}
- private void testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
+ private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
{
positionOffset = positionOffset ?? Vector2.Zero;
@@ -61,27 +55,33 @@ namespace osu.Game.Rulesets.Osu.Tests
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
- var drawable = new TestDrawableHitCircle(circle, auto)
- {
- Anchor = Anchor.Centre,
- Depth = depthIndex++
- };
+ var drawable = CreateDrawableHitCircle(circle, auto);
foreach (var mod in Mods.Value.OfType())
mod.ApplyToDrawableHitObjects(new[] { drawable });
- Add(drawable);
+ return drawable;
}
- private void testStream(float circleSize, bool auto = false)
+ protected virtual TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) => new TestDrawableHitCircle(circle, auto)
{
+ Anchor = Anchor.Centre,
+ Depth = depthIndex++
+ };
+
+ private Drawable testStream(float circleSize, bool auto = false)
+ {
+ var container = new Container { RelativeSizeAxes = Axes.Both };
+
Vector2 pos = new Vector2(-250, 0);
for (int i = 0; i <= 1000; i += 100)
{
- testSingle(circleSize, auto, i, pos);
+ container.Add(testSingle(circleSize, auto, i, pos));
pos.X += 50;
}
+
+ return container;
}
protected class TestDrawableHitCircle : DrawableHitCircle
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
index 3d8afd66f4..863d0eda09 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
@@ -1,23 +1,25 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Graphics;
+using System.Diagnostics;
using osu.Framework.MathUtils;
+using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneShaking : TestSceneHitCircle
{
- public override void Add(Drawable drawable)
+ protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto)
{
- base.Add(drawable);
+ var drawableHitObject = base.CreateDrawableHitCircle(circle, auto);
- if (drawable is TestDrawableHitCircle hitObject)
- {
- Scheduler.AddDelayed(() => hitObject.TriggerJudgement(),
- hitObject.HitObject.StartTime - (hitObject.HitObject.HitWindows.HalfWindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current);
- }
+ Debug.Assert(drawableHitObject.HitObject.HitWindows != null);
+
+ double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current;
+ Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay);
+
+ return drawableHitObject;
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
new file mode 100644
index 0000000000..02c65db6ad
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
@@ -0,0 +1,159 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Timing;
+using osu.Game.Audio;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Screens.Play;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [TestFixture]
+ public class TestSceneSkinFallbacks : PlayerTestScene
+ {
+ private readonly TestSource testUserSkin;
+ private readonly TestSource testBeatmapSkin;
+
+ public TestSceneSkinFallbacks()
+ : base(new OsuRuleset())
+ {
+ testUserSkin = new TestSource("user");
+ testBeatmapSkin = new TestSource("beatmap");
+ }
+
+ [Test]
+ public void TestBeatmapSkinDefault()
+ {
+ AddStep("enable user provider", () => testUserSkin.Enabled = true);
+
+ AddStep("enable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, true));
+ checkNextHitObject("beatmap");
+
+ AddStep("disable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, false));
+ checkNextHitObject("user");
+
+ AddStep("disable user provider", () => testUserSkin.Enabled = false);
+ checkNextHitObject(null);
+ }
+
+ private void checkNextHitObject(string skin) =>
+ AddUntilStep($"check skin from {skin}", () =>
+ {
+ var firstObject = ((TestPlayer)Player).DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.OfType().FirstOrDefault();
+
+ if (firstObject == null)
+ return false;
+
+ var skinnable = firstObject.ApproachCircle.Child as SkinnableDrawable;
+
+ if (skin == null && skinnable?.Drawable is Sprite)
+ // check for default skin provider
+ return true;
+
+ var text = skinnable?.Drawable as SpriteText;
+
+ return text?.Text == skin;
+ });
+
+ [Resolved]
+ private AudioManager audio { get; set; }
+
+ protected override Player CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin);
+
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) => new CustomSkinWorkingBeatmap(beatmap, Clock, audio, testBeatmapSkin);
+
+ public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
+ {
+ private readonly ISkinSource skin;
+
+ public CustomSkinWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin)
+ : base(beatmap, frameBasedClock, audio)
+ {
+ this.skin = skin;
+ }
+
+ protected override ISkin GetSkin() => skin;
+ }
+
+ public class SkinProvidingPlayer : TestPlayer
+ {
+ private readonly TestSource userSkin;
+
+ public SkinProvidingPlayer(TestSource userSkin)
+ {
+ this.userSkin = userSkin;
+ }
+
+ private DependencyContainer dependencies;
+
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ {
+ dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+
+ dependencies.CacheAs(userSkin);
+
+ return dependencies;
+ }
+ }
+
+ public class TestSource : ISkinSource
+ {
+ private readonly string identifier;
+
+ public TestSource(string identifier)
+ {
+ this.identifier = identifier;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component)
+ {
+ if (!enabled) return null;
+
+ return new SpriteText
+ {
+ Text = identifier,
+ Font = OsuFont.Default.With(size: 30),
+ };
+ }
+
+ public Texture GetTexture(string componentName) => null;
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => null;
+
+ public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default;
+ public IBindable GetConfig(TLookup lookup) => null;
+
+ public event Action SourceChanged;
+
+ private bool enabled = true;
+
+ public bool Enabled
+ {
+ get => enabled;
+ set
+ {
+ if (value == enabled)
+ return;
+
+ enabled = value;
+ SourceChanged?.Invoke();
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
index c5a27205d6..29c71a8903 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
@@ -10,7 +10,6 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Graphics;
using osu.Game.Rulesets.Mods;
@@ -27,83 +26,96 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
- public class TestSceneSlider : OsuTestScene
+ public class TestSceneSlider : SkinnableTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
+ typeof(Slider),
+ typeof(SliderTick),
+ typeof(SliderTailCircle),
typeof(SliderBall),
typeof(SliderBody),
- typeof(SliderTick),
+ typeof(SnakingSliderBody),
typeof(DrawableSlider),
typeof(DrawableSliderTick),
+ typeof(DrawableSliderTail),
+ typeof(DrawableSliderHead),
typeof(DrawableRepeatPoint),
typeof(DrawableOsuHitObject)
};
- private readonly Container content;
- protected override Container Content => content;
+ private Container content;
+
+ protected override Container Content
+ {
+ get
+ {
+ if (content == null)
+ base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
+
+ return content;
+ }
+ }
private int depthIndex;
public TestSceneSlider()
{
- base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
+ AddStep("Big Single", () => SetContents(() => testSimpleBig()));
+ AddStep("Medium Single", () => SetContents(() => testSimpleMedium()));
+ AddStep("Small Single", () => SetContents(() => testSimpleSmall()));
+ AddStep("Big 1 Repeat", () => SetContents(() => testSimpleBig(1)));
+ AddStep("Medium 1 Repeat", () => SetContents(() => testSimpleMedium(1)));
+ AddStep("Small 1 Repeat", () => SetContents(() => testSimpleSmall(1)));
+ AddStep("Big 2 Repeats", () => SetContents(() => testSimpleBig(2)));
+ AddStep("Medium 2 Repeats", () => SetContents(() => testSimpleMedium(2)));
+ AddStep("Small 2 Repeats", () => SetContents(() => testSimpleSmall(2)));
- AddStep("Big Single", () => testSimpleBig());
- AddStep("Medium Single", () => testSimpleMedium());
- AddStep("Small Single", () => testSimpleSmall());
- AddStep("Big 1 Repeat", () => testSimpleBig(1));
- AddStep("Medium 1 Repeat", () => testSimpleMedium(1));
- AddStep("Small 1 Repeat", () => testSimpleSmall(1));
- AddStep("Big 2 Repeats", () => testSimpleBig(2));
- AddStep("Medium 2 Repeats", () => testSimpleMedium(2));
- AddStep("Small 2 Repeats", () => testSimpleSmall(2));
+ AddStep("Slow Slider", () => SetContents(testSlowSpeed)); // slow long sliders take ages already so no repeat steps
+ AddStep("Slow Short Slider", () => SetContents(() => testShortSlowSpeed()));
+ AddStep("Slow Short Slider 1 Repeats", () => SetContents(() => testShortSlowSpeed(1)));
+ AddStep("Slow Short Slider 2 Repeats", () => SetContents(() => testShortSlowSpeed(2)));
- AddStep("Slow Slider", testSlowSpeed); // slow long sliders take ages already so no repeat steps
- AddStep("Slow Short Slider", () => testShortSlowSpeed());
- AddStep("Slow Short Slider 1 Repeats", () => testShortSlowSpeed(1));
- AddStep("Slow Short Slider 2 Repeats", () => testShortSlowSpeed(2));
+ AddStep("Fast Slider", () => SetContents(() => testHighSpeed()));
+ AddStep("Fast Slider 1 Repeat", () => SetContents(() => testHighSpeed(1)));
+ AddStep("Fast Slider 2 Repeats", () => SetContents(() => testHighSpeed(2)));
+ AddStep("Fast Short Slider", () => SetContents(() => testShortHighSpeed()));
+ AddStep("Fast Short Slider 1 Repeat", () => SetContents(() => testShortHighSpeed(1)));
+ AddStep("Fast Short Slider 2 Repeats", () => SetContents(() => testShortHighSpeed(2)));
+ AddStep("Fast Short Slider 6 Repeats", () => SetContents(() => testShortHighSpeed(6)));
- AddStep("Fast Slider", () => testHighSpeed());
- AddStep("Fast Slider 1 Repeat", () => testHighSpeed(1));
- AddStep("Fast Slider 2 Repeats", () => testHighSpeed(2));
- AddStep("Fast Short Slider", () => testShortHighSpeed());
- AddStep("Fast Short Slider 1 Repeat", () => testShortHighSpeed(1));
- AddStep("Fast Short Slider 2 Repeats", () => testShortHighSpeed(2));
- AddStep("Fast Short Slider 6 Repeats", () => testShortHighSpeed(6));
+ AddStep("Perfect Curve", () => SetContents(() => testPerfect()));
+ AddStep("Perfect Curve 1 Repeat", () => SetContents(() => testPerfect(1)));
+ AddStep("Perfect Curve 2 Repeats", () => SetContents(() => testPerfect(2)));
- AddStep("Perfect Curve", () => testPerfect());
- AddStep("Perfect Curve 1 Repeat", () => testPerfect(1));
- AddStep("Perfect Curve 2 Repeats", () => testPerfect(2));
+ AddStep("Linear Slider", () => SetContents(() => testLinear()));
+ AddStep("Linear Slider 1 Repeat", () => SetContents(() => testLinear(1)));
+ AddStep("Linear Slider 2 Repeats", () => SetContents(() => testLinear(2)));
- AddStep("Linear Slider", () => testLinear());
- AddStep("Linear Slider 1 Repeat", () => testLinear(1));
- AddStep("Linear Slider 2 Repeats", () => testLinear(2));
+ AddStep("Bezier Slider", () => SetContents(() => testBezier()));
+ AddStep("Bezier Slider 1 Repeat", () => SetContents(() => testBezier(1)));
+ AddStep("Bezier Slider 2 Repeats", () => SetContents(() => testBezier(2)));
- AddStep("Bezier Slider", () => testBezier());
- AddStep("Bezier Slider 1 Repeat", () => testBezier(1));
- AddStep("Bezier Slider 2 Repeats", () => testBezier(2));
+ AddStep("Linear Overlapping", () => SetContents(() => testLinearOverlapping()));
+ AddStep("Linear Overlapping 1 Repeat", () => SetContents(() => testLinearOverlapping(1)));
+ AddStep("Linear Overlapping 2 Repeats", () => SetContents(() => testLinearOverlapping(2)));
- AddStep("Linear Overlapping", () => testLinearOverlapping());
- AddStep("Linear Overlapping 1 Repeat", () => testLinearOverlapping(1));
- AddStep("Linear Overlapping 2 Repeats", () => testLinearOverlapping(2));
+ AddStep("Catmull Slider", () => SetContents(() => testCatmull()));
+ AddStep("Catmull Slider 1 Repeat", () => SetContents(() => testCatmull(1)));
+ AddStep("Catmull Slider 2 Repeats", () => SetContents(() => testCatmull(2)));
- AddStep("Catmull Slider", () => testCatmull());
- AddStep("Catmull Slider 1 Repeat", () => testCatmull(1));
- AddStep("Catmull Slider 2 Repeats", () => testCatmull(2));
+ AddStep("Big Single, Large StackOffset", () => SetContents(() => testSimpleBigLargeStackOffset()));
+ AddStep("Big 1 Repeat, Large StackOffset", () => SetContents(() => testSimpleBigLargeStackOffset(1)));
- AddStep("Big Single, Large StackOffset", () => testSimpleBigLargeStackOffset());
- AddStep("Big 1 Repeat, Large StackOffset", () => testSimpleBigLargeStackOffset(1));
-
- AddStep("Distance Overflow", () => testDistanceOverflow());
- AddStep("Distance Overflow 1 Repeat", () => testDistanceOverflow(1));
+ AddStep("Distance Overflow", () => SetContents(() => testDistanceOverflow()));
+ AddStep("Distance Overflow 1 Repeat", () => SetContents(() => testDistanceOverflow(1)));
}
- private void testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
+ private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
- private void testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
+ private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
- private void testDistanceOverflow(int repeats = 0)
+ private Drawable testDistanceOverflow(int repeats = 0)
{
var slider = new Slider
{
@@ -120,22 +132,22 @@ namespace osu.Game.Rulesets.Osu.Tests
StackHeight = 10
};
- addSlider(slider, 2, 2);
+ return createDrawable(slider, 2, 2);
}
- private void testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats);
+ private Drawable testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats);
- private void testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats);
+ private Drawable testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats);
- private void testSlowSpeed() => createSlider(speedMultiplier: 0.5);
+ private Drawable testSlowSpeed() => createSlider(speedMultiplier: 0.5);
- private void testShortSlowSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 0.5);
+ private Drawable testShortSlowSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 0.5);
- private void testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15);
+ private Drawable testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15);
- private void testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15);
+ private Drawable testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15);
- private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0)
+ private Drawable createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0)
{
var slider = new Slider
{
@@ -151,10 +163,10 @@ namespace osu.Game.Rulesets.Osu.Tests
StackHeight = stackHeight
};
- addSlider(slider, circleSize, speedMultiplier);
+ return createDrawable(slider, circleSize, speedMultiplier);
}
- private void testPerfect(int repeats = 0)
+ private Drawable testPerfect(int repeats = 0)
{
var slider = new Slider
{
@@ -170,12 +182,12 @@ namespace osu.Game.Rulesets.Osu.Tests
NodeSamples = createEmptySamples(repeats)
};
- addSlider(slider, 2, 3);
+ return createDrawable(slider, 2, 3);
}
- private void testLinear(int repeats = 0) => createLinear(repeats);
+ private Drawable testLinear(int repeats = 0) => createLinear(repeats);
- private void createLinear(int repeats)
+ private Drawable createLinear(int repeats)
{
var slider = new Slider
{
@@ -194,12 +206,12 @@ namespace osu.Game.Rulesets.Osu.Tests
NodeSamples = createEmptySamples(repeats)
};
- addSlider(slider, 2, 3);
+ return createDrawable(slider, 2, 3);
}
- private void testBezier(int repeats = 0) => createBezier(repeats);
+ private Drawable testBezier(int repeats = 0) => createBezier(repeats);
- private void createBezier(int repeats)
+ private Drawable createBezier(int repeats)
{
var slider = new Slider
{
@@ -217,12 +229,12 @@ namespace osu.Game.Rulesets.Osu.Tests
NodeSamples = createEmptySamples(repeats)
};
- addSlider(slider, 2, 3);
+ return createDrawable(slider, 2, 3);
}
- private void testLinearOverlapping(int repeats = 0) => createOverlapping(repeats);
+ private Drawable testLinearOverlapping(int repeats = 0) => createOverlapping(repeats);
- private void createOverlapping(int repeats)
+ private Drawable createOverlapping(int repeats)
{
var slider = new Slider
{
@@ -241,12 +253,12 @@ namespace osu.Game.Rulesets.Osu.Tests
NodeSamples = createEmptySamples(repeats)
};
- addSlider(slider, 2, 3);
+ return createDrawable(slider, 2, 3);
}
- private void testCatmull(int repeats = 0) => createCatmull(repeats);
+ private Drawable testCatmull(int repeats = 0) => createCatmull(repeats);
- private void createCatmull(int repeats = 0)
+ private Drawable createCatmull(int repeats = 0)
{
var repeatSamples = new List>();
for (int i = 0; i < repeats; i++)
@@ -267,7 +279,7 @@ namespace osu.Game.Rulesets.Osu.Tests
NodeSamples = repeatSamples
};
- addSlider(slider, 3, 1);
+ return createDrawable(slider, 3, 1);
}
private List> createEmptySamples(int repeats)
@@ -278,7 +290,7 @@ namespace osu.Game.Rulesets.Osu.Tests
return repeatSamples;
}
- private void addSlider(Slider slider, float circleSize, double speedMultiplier)
+ private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier)
{
var cpi = new ControlPointInfo();
cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
@@ -296,7 +308,7 @@ namespace osu.Game.Rulesets.Osu.Tests
drawable.OnNewResult += onNewResult;
- Add(drawable);
+ return drawable;
}
private float judgementOffsetDirection = 1;
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index bb3e5a66f3..c331c811d2 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
index 7bb1f42802..bb19b783aa 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
@@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Beatmaps
{
@@ -208,17 +209,22 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
if (beatmap.HitObjects[j].StartTime - stackThreshold > startTime)
break;
+ // The start position of the hitobject, or the position at the end of the path if the hitobject is a slider
+ Vector2 position2 = currHitObject is Slider currSlider
+ ? currSlider.Position + currSlider.Path.PositionAt(1)
+ : currHitObject.Position;
+
if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance)
{
currHitObject.StackHeight++;
- startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime;
+ startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[j].StartTime;
}
- else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.EndPosition) < stack_distance)
+ else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, position2) < stack_distance)
{
//Case for sliders - bump notes down and right, rather than up and left.
sliderStack++;
beatmap.HitObjects[j].StackHeight -= sliderStack;
- startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime;
+ startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[j].StartTime;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index c197933233..b0d261a1cc 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -13,6 +13,8 @@ using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Skills;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Scoring;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Difficulty
{
@@ -34,8 +36,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
+ HitWindows hitWindows = new OsuHitWindows();
+ hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
- double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate;
+ double hitWindowGreat = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate;
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
int maxCombo = beatmap.HitObjects.Count;
diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
index bcb6099cfb..cc08d356f9 100644
--- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
+++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{
public class DrawableOsuEditRuleset : DrawableOsuRuleset
{
- public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableOsuEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index c5452ae0aa..1c040e9dee 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{
}
- protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
=> new DrawableOsuEditRuleset(ruleset, beatmap, mods);
protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs
index c7661bddb1..15444b847b 100644
--- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs
+++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Osu.Judgements
{
@@ -9,8 +10,8 @@ namespace osu.Game.Rulesets.Osu.Judgements
{
public ComboResult ComboType;
- public OsuJudgementResult(Judgement judgement)
- : base(judgement)
+ public OsuJudgementResult(HitObject hitObject, Judgement judgement)
+ : base(hitObject, judgement)
{
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
index 401bd28d7c..ca72f18e9c 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
@@ -2,13 +2,19 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
+using System.Linq;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.StateChanges;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Replays;
+using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModAutopilot : Mod
+ public class OsuModAutopilot : Mod, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToDrawableRuleset
{
public override string Name => "Autopilot";
public override string Acronym => "AP";
@@ -17,5 +23,40 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) };
+
+ public bool AllowFail => false;
+
+ private OsuInputManager inputManager;
+
+ private List replayFrames;
+
+ private int currentFrame;
+
+ public void Update(Playfield playfield)
+ {
+ if (currentFrame == replayFrames.Count - 1) return;
+
+ double time = playfield.Time.Current;
+
+ // Very naive implementation of autopilot based on proximity to replay frames.
+ // TODO: this needs to be based on user interactions to better match stable (pausing until judgement is registered).
+ if (Math.Abs(replayFrames[currentFrame + 1].Time - time) <= Math.Abs(replayFrames[currentFrame].Time - time))
+ {
+ currentFrame++;
+ new MousePositionAbsoluteInput { Position = playfield.ToScreenSpace(replayFrames[currentFrame].Position) }.Apply(inputManager.CurrentState, inputManager);
+ }
+
+ // TODO: Implement the functionality to automatically spin spinners
+ }
+
+ public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
+ {
+ // Grab the input manager to disable the user's cursor, and for future use
+ inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
+ inputManager.AllowUserCursorMovement = false;
+
+ // Generate the replay frames the cursor should follow
+ replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap).Generate().Frames.Cast().ToList();
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
index 445f81c6d4..1eb37f8119 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
@@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.Osu.Mods
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
- Texture = textures.Get("Play/osu/blinds-panel");
+ Texture = textures.Get("Gameplay/osu/blinds-panel");
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index 5625028707..649b01c132 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Types;
@@ -38,7 +39,12 @@ namespace osu.Game.Rulesets.Osu.Mods
if ((osuHit.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime) || osuHit.IsHit)
continue;
- requiresHit |= osuHit is DrawableHitCircle && osuHit.IsHovered && osuHit.HitObject.HitWindows.CanBeHit(relativetime);
+ if (osuHit is DrawableHitCircle && osuHit.IsHovered)
+ {
+ Debug.Assert(osuHit.HitObject.HitWindows != null);
+ requiresHit |= osuHit.HitObject.HitWindows.CanBeHit(relativetime);
+ }
+
requiresHold |= (osuHit is DrawableSlider slider && (slider.Ball.IsHovered || osuHit.IsHovered)) || osuHit is DrawableSpinner;
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
index aacf3ee08d..89ffddf4cb 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
{
Origin = Anchor.Centre;
- Child = new SkinnableDrawable("Play/osu/followpoint", _ => new Container
+ Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new Container
{
Masking = true,
AutoSizeAxes = Axes.Both,
@@ -36,12 +36,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
Child = new Box
{
Size = new Vector2(width),
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Alpha = 0.5f,
}
- }, restrictSize: false);
+ }, confineMode: ConfineMode.NoScaling);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
index 7569626230..a269b87c75 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
@@ -97,13 +97,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
Position = pointStartPosition,
Rotation = rotation,
Alpha = 0,
- Scale = new Vector2(1.5f),
+ Scale = new Vector2(1.5f * currHitObject.Scale),
});
using (fp.BeginAbsoluteSequence(fadeInTime))
{
fp.FadeIn(currHitObject.TimeFadeIn);
- fp.ScaleTo(1, currHitObject.TimeFadeIn, Easing.Out);
+ fp.ScaleTo(currHitObject.Scale, currHitObject.TimeFadeIn, Easing.Out);
fp.MoveTo(pointEndPosition, currHitObject.TimeFadeIn, Easing.Out);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index d3d763daf3..985dcbca86 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -2,37 +2,36 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
-using osuTK;
using osu.Game.Rulesets.Scoring;
+using osuTK;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
{
public ApproachCircle ApproachCircle;
- private readonly CirclePiece circle;
- private readonly RingPiece ring;
- private readonly FlashPiece flash;
- private readonly ExplodePiece explode;
- private readonly NumberPiece number;
- private readonly GlowPiece glow;
private readonly IBindable positionBindable = new Bindable();
private readonly IBindable stackHeightBindable = new Bindable();
private readonly IBindable scaleBindable = new Bindable();
- public OsuAction? HitAction => circle.HitAction;
-
- private readonly Container explodeContainer;
+ public OsuAction? HitAction => hitArea.HitAction;
private readonly Container scaleContainer;
+ private readonly HitArea hitArea;
+
+ private readonly SkinnableDrawable mainContent;
+
public DrawableHitCircle(HitCircle h)
: base(h)
{
@@ -47,44 +46,30 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
- Child = explodeContainer = new Container
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- Children = new Drawable[]
+ hitArea = new HitArea
{
- glow = new GlowPiece(),
- circle = new CirclePiece
+ Hit = () =>
{
- Hit = () =>
- {
- if (AllJudged)
- return false;
+ if (AllJudged)
+ return false;
- UpdateResult(true);
- return true;
- },
+ UpdateResult(true);
+ return true;
},
- number = new NumberPiece
- {
- Text = (HitObject.IndexInCurrentCombo + 1).ToString(),
- },
- ring = new RingPiece(),
- flash = new FlashPiece(),
- explode = new ExplodePiece(),
- ApproachCircle = new ApproachCircle
- {
- Alpha = 0,
- Scale = new Vector2(4),
- }
+ },
+ mainContent = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)),
+ ApproachCircle = new ApproachCircle
+ {
+ Alpha = 0,
+ Scale = new Vector2(4),
}
}
},
};
- //may not be so correct
- Size = circle.DrawSize;
+ Size = hitArea.DrawSize;
}
[BackgroundDependencyLoader]
@@ -98,17 +83,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
stackHeightBindable.BindTo(HitObject.StackHeightBindable);
scaleBindable.BindTo(HitObject.ScaleBindable);
- AccentColour.BindValueChanged(colour =>
- {
- explode.Colour = colour.NewValue;
- glow.Colour = colour.NewValue;
- circle.Colour = colour.NewValue;
- ApproachCircle.Colour = colour.NewValue;
- }, true);
+ AccentColour.BindValueChanged(accent => ApproachCircle.Colour = accent.NewValue, true);
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
+ Debug.Assert(HitObject.HitWindows != null);
+
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
@@ -121,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (result == HitResult.None)
{
- Shake(Math.Abs(timeOffset) - HitObject.HitWindows.HalfWindowFor(HitResult.Miss));
+ Shake(Math.Abs(timeOffset) - HitObject.HitWindows.WindowFor(HitResult.Miss));
return;
}
@@ -132,14 +113,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.UpdateInitialTransforms();
+ mainContent.FadeInFromZero(HitObject.TimeFadeIn);
+
ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt));
- ApproachCircle.ScaleTo(1.1f, HitObject.TimePreempt);
+ ApproachCircle.ScaleTo(1f, HitObject.TimePreempt);
ApproachCircle.Expire(true);
}
protected override void UpdateStateTransforms(ArmedState state)
{
- glow.FadeOut(400);
+ Debug.Assert(HitObject.HitWindows != null);
switch (state)
{
@@ -148,10 +131,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Expire(true);
- circle.HitAction = null;
+ hitArea.HitAction = null;
// override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early.
- LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.HalfWindowFor(HitResult.Miss);
+ LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.WindowFor(HitResult.Miss);
break;
case ArmedState.Miss:
@@ -163,29 +146,50 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
case ArmedState.Hit:
ApproachCircle.FadeOut(50);
- const double flash_in = 40;
- flash.FadeTo(0.8f, flash_in)
- .Then()
- .FadeOut(100);
-
- explode.FadeIn(flash_in);
-
- using (BeginDelayedSequence(flash_in, true))
- {
- //after the flash, we can hide some elements that were behind it
- ring.FadeOut();
- circle.FadeOut();
- number.FadeOut();
-
- this.FadeOut(800);
- explodeContainer.ScaleTo(1.5f, 400, Easing.OutQuad);
- }
-
- Expire();
+ // todo: temporary / arbitrary
+ this.Delay(800).Expire();
break;
}
}
public Drawable ProxiedLayer => ApproachCircle;
+
+ private class HitArea : Drawable, IKeyBindingHandler
+ {
+ // IsHovered is used
+ public override bool HandlePositionalInput => true;
+
+ public Func Hit;
+
+ public OsuAction? HitAction;
+
+ public HitArea()
+ {
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+ }
+
+ public bool OnPressed(OsuAction action)
+ {
+ switch (action)
+ {
+ case OsuAction.LeftButton:
+ case OsuAction.RightButton:
+ if (IsHovered && (Hit?.Invoke() ?? false))
+ {
+ HitAction = action;
+ return true;
+ }
+
+ break;
+ }
+
+ return false;
+ }
+
+ public bool OnReleased(OsuAction action) => false;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
index 579f16e0d4..fcd42314fc 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
@@ -1,4 +1,4 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects.Drawables;
@@ -36,13 +36,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
- protected override void UpdateInitialTransforms() => this.FadeIn(HitObject.TimeFadeIn);
-
private OsuInputManager osuActionInputManager;
internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager);
protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength);
- protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement);
+ protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement);
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
index 1e2c0ae59f..00a943a67f 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
@@ -3,12 +3,14 @@
using System;
using System.Collections.Generic;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Drawables;
-using osuTK;
using osu.Game.Rulesets.Scoring;
+using osuTK;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
@@ -20,27 +22,40 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private double animDuration;
+ private readonly SkinnableDrawable scaleContainer;
+
public DrawableRepeatPoint(RepeatPoint repeatPoint, DrawableSlider drawableSlider)
: base(repeatPoint)
{
this.repeatPoint = repeatPoint;
this.drawableSlider = drawableSlider;
- Size = new Vector2(45 * repeatPoint.Scale);
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
- Blending = BlendingMode.Additive;
+ Blending = BlendingParameters.Additive;
Origin = Anchor.Centre;
- InternalChildren = new Drawable[]
+ InternalChild = scaleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon
{
- new SkinnableDrawable("Play/osu/reversearrow", _ => new SpriteIcon
- {
- RelativeSizeAxes = Axes.Both,
- Icon = FontAwesome.Solid.ChevronRight
- }, restrictSize: false)
+ RelativeSizeAxes = Axes.Both,
+ Icon = FontAwesome.Solid.ChevronRight,
+ Size = new Vector2(0.35f)
+ }, confineMode: ConfineMode.NoScaling)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
};
}
+ private readonly IBindable scaleBindable = new Bindable();
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ scaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true);
+ scaleBindable.BindTo(HitObject.ScaleBindable);
+ }
+
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (repeatPoint.StartTime <= Time.Current)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 4a6bd45007..00c953c393 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -12,6 +12,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Configuration;
+using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Scoring;
using osuTK.Graphics;
using osu.Game.Skinning;
@@ -48,10 +49,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
InternalChildren = new Drawable[]
{
- Body = new SnakingSliderBody(s)
- {
- PathRadius = s.Scale * OsuHitObject.OBJECT_RADIUS,
- },
+ Body = new SnakingSliderBody(s),
ticks = new Container { RelativeSizeAxes = Axes.Both },
repeatPoints = new Container { RelativeSizeAxes = Axes.Both },
Ball = new SliderBall(s, this)
@@ -96,6 +94,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
}
+ protected override void UpdateInitialTransforms()
+ {
+ base.UpdateInitialTransforms();
+
+ Body.FadeInFromZero(HitObject.TimeFadeIn);
+ }
+
[BackgroundDependencyLoader]
private void load()
{
@@ -105,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
scaleBindable.BindValueChanged(scale =>
{
- Body.PathRadius = scale.NewValue * 64;
+ updatePathRadius();
Ball.Scale = new Vector2(scale.NewValue);
});
@@ -118,7 +123,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AccentColour.BindValueChanged(colour =>
{
Body.AccentColour = colour.NewValue;
- Ball.AccentColour = colour.NewValue;
foreach (var drawableHitObject in NestedHitObjects)
drawableHitObject.AccentColour.Value = colour.NewValue;
@@ -157,16 +161,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Body.RecyclePath();
}
+ private float sliderPathRadius;
+
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
- Body.BorderSize = skin.GetValue(s => s.SliderBorderSize) ?? SliderBody.DEFAULT_BORDER_SIZE;
- Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour.Value;
- Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Color4.White;
- Ball.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? AccentColour.Value;
+ Body.BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE;
+ sliderPathRadius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
+ updatePathRadius();
+
+ Body.AccentColour = skin.GetConfig(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value;
+ Body.BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
}
+ private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius;
+
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (userTriggered || Time.Current < slider.EndTime)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
index f5f92dd05d..ba931976a8 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
@@ -1,14 +1,16 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@@ -16,36 +18,49 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public const double ANIM_DURATION = 150;
+ private const float default_tick_size = 16;
+
public bool Tracking { get; set; }
public override bool DisplayResult => false;
+ private readonly SkinnableDrawable scaleContainer;
+
public DrawableSliderTick(SliderTick sliderTick)
: base(sliderTick)
{
- Size = new Vector2(16) * sliderTick.Scale;
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Origin = Anchor.Centre;
- InternalChildren = new Drawable[]
+ InternalChild = scaleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderScorePoint), _ => new CircularContainer
{
- new SkinnableDrawable("Play/osu/sliderscorepoint", _ => new Container
+ Masking = true,
+ Origin = Anchor.Centre,
+ Size = new Vector2(default_tick_size),
+ BorderThickness = default_tick_size / 4,
+ BorderColour = Color4.White,
+ Child = new Box
{
- Masking = true,
RelativeSizeAxes = Axes.Both,
- Origin = Anchor.Centre,
- CornerRadius = Size.X / 2,
- BorderThickness = 2,
- BorderColour = Color4.White,
- Child = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = AccentColour.Value,
- Alpha = 0.3f,
- }
- }, restrictSize: false)
+ Colour = AccentColour.Value,
+ Alpha = 0.3f,
+ }
+ })
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
};
}
+ private readonly IBindable scaleBindable = new Bindable();
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ scaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true);
+ scaleBindable.BindTo(HitObject.ScaleBindable);
+ }
+
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (timeOffset >= 0)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index a0bd301fdb..49aaa2aaea 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -13,8 +13,8 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
-using osu.Game.Screens.Ranking;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Screens.Ranking;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
index 9981585f9e..1b474f265c 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
@@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Game.Skinning;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -24,7 +25,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
- Child = new SkinnableSprite("Play/osu/approachcircle");
+ Child = new SkinnableApproachCircle();
+ }
+
+ private class SkinnableApproachCircle : SkinnableSprite
+ {
+ public SkinnableApproachCircle()
+ : base("Gameplay/osu/approachcircle")
+ {
+ }
+
+ protected override Drawable CreateDefault(ISkinComponent component)
+ {
+ var drawable = base.CreateDefault(component);
+
+ // account for the sprite being used for the default approach circle being taken from stable,
+ // when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
+ drawable.Scale = new Vector2(128 / 118f);
+
+ return drawable;
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
index dc0b149140..210d5ff839 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
@@ -1,24 +1,17 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Input.Bindings;
-using osu.Game.Skinning;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
- public class CirclePiece : Container, IKeyBindingHandler
+ public class CirclePiece : CompositeDrawable
{
- // IsHovered is used
- public override bool HandlePositionalInput => true;
-
- public Func Hit;
-
- public OsuAction? HitAction;
-
public CirclePiece()
{
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
@@ -27,28 +20,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
-
- InternalChild = new SkinnableDrawable("Play/osu/hitcircle", _ => new DefaultCirclePiece());
}
- public bool OnPressed(OsuAction action)
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
{
- switch (action)
+ InternalChildren = new Drawable[]
{
- case OsuAction.LeftButton:
- case OsuAction.RightButton:
- if (IsHovered && (Hit?.Invoke() ?? false))
- {
- HitAction = action;
- return true;
- }
-
- break;
- }
-
- return false;
+ new Sprite
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Texture = textures.Get(@"Gameplay/osu/disc"),
+ },
+ new TrianglesPiece
+ {
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingParameters.Additive,
+ Alpha = 0.5f,
+ }
+ };
}
-
- public bool OnReleased(OsuAction action) => false;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs
deleted file mode 100644
index 047ff943ff..0000000000
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.Textures;
-
-namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
-{
- public class DefaultCirclePiece : Container
- {
- [BackgroundDependencyLoader]
- private void load(TextureStore textures)
- {
- RelativeSizeAxes = Axes.Both;
- Children = new Drawable[]
- {
- new Sprite
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Texture = textures.Get(@"Play/osu/disc"),
- },
- new TrianglesPiece
- {
- RelativeSizeAxes = Axes.Both,
- Blending = BlendingMode.Additive,
- Alpha = 0.5f,
- }
- };
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs
index 8ff16f8b84..6381ddca69 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs
@@ -3,7 +3,6 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
@@ -17,15 +16,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
- Blending = BlendingMode.Additive;
+ Blending = BlendingParameters.Additive;
Alpha = 0;
- Child = new SkinnableDrawable("Play/osu/hitcircle-explode", _ => new TrianglesPiece
+ Child = new TrianglesPiece
{
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
Alpha = 0.2f,
- }, s => s.GetTexture("Play/osu/hitcircle") == null);
+ };
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs
index c22073f56c..038a2299e9 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs
@@ -5,7 +5,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osuTK;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -18,10 +17,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
- Blending = BlendingMode.Additive;
+ Blending = BlendingParameters.Additive;
Alpha = 0;
- Child = new SkinnableDrawable("Play/osu/hitcircle-flash", name => new CircularContainer
+ Child = new CircularContainer
{
Masking = true,
RelativeSizeAxes = Axes.Both,
@@ -29,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
RelativeSizeAxes = Axes.Both
}
- }, s => s.GetTexture("Play/osu/hitcircle") == null);
+ };
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs
index 917695c790..30937313fd 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs
@@ -6,7 +6,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
-using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -22,14 +21,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
- Child = new SkinnableDrawable("Play/osu/ring-glow", name => new Sprite
+ Child = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Texture = textures.Get(name),
- Blending = BlendingMode.Additive,
+ Texture = textures.Get("Gameplay/osu/ring-glow"),
+ Blending = BlendingParameters.Additive,
Alpha = 0.5f
- }, s => s.GetTexture("Play/osu/hitcircle") == null);
+ };
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs
new file mode 100644
index 0000000000..944c93bb6d
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs
@@ -0,0 +1,94 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Objects.Drawables;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
+{
+ public class MainCirclePiece : CompositeDrawable
+ {
+ private readonly CirclePiece circle;
+ private readonly RingPiece ring;
+ private readonly FlashPiece flash;
+ private readonly ExplodePiece explode;
+ private readonly NumberPiece number;
+ private readonly GlowPiece glow;
+
+ public MainCirclePiece(int index)
+ {
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ InternalChildren = new Drawable[]
+ {
+ glow = new GlowPiece(),
+ circle = new CirclePiece(),
+ number = new NumberPiece
+ {
+ Text = (index + 1).ToString(),
+ },
+ ring = new RingPiece(),
+ flash = new FlashPiece(),
+ explode = new ExplodePiece(),
+ };
+ }
+
+ private readonly IBindable state = new Bindable();
+
+ private readonly Bindable accentColour = new Bindable();
+
+ [BackgroundDependencyLoader]
+ private void load(DrawableHitObject drawableObject)
+ {
+ state.BindTo(drawableObject.State);
+ state.BindValueChanged(updateState, true);
+
+ accentColour.BindTo(drawableObject.AccentColour);
+ accentColour.BindValueChanged(colour =>
+ {
+ explode.Colour = colour.NewValue;
+ glow.Colour = colour.NewValue;
+ circle.Colour = colour.NewValue;
+ }, true);
+ }
+
+ private void updateState(ValueChangedEvent state)
+ {
+ glow.FadeOut(400);
+
+ switch (state.NewValue)
+ {
+ case ArmedState.Hit:
+ const double flash_in = 40;
+ const double flash_out = 100;
+
+ flash.FadeTo(0.8f, flash_in)
+ .Then()
+ .FadeOut(flash_out);
+
+ explode.FadeIn(flash_in);
+ this.ScaleTo(1.5f, 400, Easing.OutQuad);
+
+ using (BeginDelayedSequence(flash_in, true))
+ {
+ //after the flash, we can hide some elements that were behind it
+ ring.FadeOut();
+ circle.FadeOut();
+ number.FadeOut();
+
+ this.FadeOut(800);
+ }
+
+ break;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
index 84034d3ee9..62c4ba5ee3 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Children = new Drawable[]
{
- new SkinnableDrawable("Play/osu/number-glow", name => new CircularContainer
+ new CircularContainer
{
Masking = true,
Origin = Anchor.Centre,
@@ -41,12 +41,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Colour = Color4.White.Opacity(0.5f),
},
Child = new Box()
- }, s => s.GetTexture("Play/osu/hitcircle") == null),
- number = new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText
+ },
+ number = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText
{
Font = OsuFont.Numeric.With(size: 40),
UseFullGlyphHeight = false,
- }, restrictSize: false)
+ }, confineMode: ConfineMode.NoScaling)
{
Text = @"1"
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs
index 575f2c92c5..c97b74756a 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs
@@ -6,7 +6,6 @@ using osu.Framework.Graphics.Containers;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
- InternalChild = new SkinnableDrawable("Play/osu/hitcircleoverlay", _ => new Container
+ InternalChild = new Container
{
Masking = true,
CornerRadius = Size.X / 2,
@@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
RelativeSizeAxes = Axes.Both
}
}
- });
+ };
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
index 9ba8ad3474..ef7b077480 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
@@ -3,12 +3,15 @@
using System;
using System.Linq;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Skinning;
using osuTK.Graphics;
using osu.Game.Skinning;
using osuTK;
@@ -17,88 +20,43 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition
{
- private Color4 accentColour = Color4.Black;
-
public Func GetInitialHitAction;
- ///
- /// The colour that is used for the slider ball.
- ///
- public Color4 AccentColour
- {
- get => accentColour;
- set
- {
- accentColour = value;
- if (drawableBall != null)
- drawableBall.Colour = value;
- }
- }
-
private readonly Slider slider;
public readonly Drawable FollowCircle;
- private Drawable drawableBall;
private readonly DrawableSlider drawableSlider;
public SliderBall(Slider slider, DrawableSlider drawableSlider = null)
{
this.drawableSlider = drawableSlider;
this.slider = slider;
- Masking = true;
- AutoSizeAxes = Axes.Both;
- Blending = BlendingMode.Additive;
+
+ Blending = BlendingParameters.Additive;
Origin = Anchor.Centre;
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
+
Children = new[]
{
- FollowCircle = new Container
+ FollowCircle = new FollowCircleContainer
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
- Width = OsuHitObject.OBJECT_RADIUS * 2,
- Height = OsuHitObject.OBJECT_RADIUS * 2,
+ RelativeSizeAxes = Axes.Both,
Alpha = 0,
- Child = new SkinnableDrawable("Play/osu/sliderfollowcircle", _ => new CircularContainer
- {
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- BorderThickness = 5,
- BorderColour = Color4.Orange,
- Blending = BlendingMode.Additive,
- Child = new Box
- {
- Colour = Color4.Orange,
- RelativeSizeAxes = Axes.Both,
- Alpha = 0.2f,
- }
- }),
+ Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()),
},
new CircularContainer
{
Masking = true,
- AutoSizeAxes = Axes.Both,
+ RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Alpha = 1,
Child = new Container
{
- Width = OsuHitObject.OBJECT_RADIUS * 2,
- Height = OsuHitObject.OBJECT_RADIUS * 2,
- // TODO: support skin filename animation (sliderb0, sliderb1...)
- Child = new SkinnableDrawable("Play/osu/sliderb", _ => new CircularContainer
- {
- Masking = true,
- RelativeSizeAxes = Axes.Both,
- BorderThickness = 10,
- BorderColour = Color4.White,
- Alpha = 1,
- Child = drawableBall = new Box
- {
- Colour = AccentColour,
- RelativeSizeAxes = Axes.Both,
- Alpha = 0.4f,
- }
- }),
+ RelativeSizeAxes = Axes.Both,
+ Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBall), _ => new DefaultSliderBall()),
}
}
};
@@ -191,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
// in valid time range
Time.Current >= slider.StartTime && Time.Current < slider.EndTime &&
// in valid position range
- lastScreenSpaceMousePosition.HasValue && base.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) &&
+ lastScreenSpaceMousePosition.HasValue && FollowCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) &&
// valid action
(actions?.Any(isValidTrackingAction) ?? false);
}
@@ -210,9 +168,77 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
return action == OsuAction.LeftButton || action == OsuAction.RightButton;
}
+ private Vector2? lastPosition;
+
public void UpdateProgress(double completionProgress)
{
- Position = slider.CurvePositionAt(completionProgress);
+ var newPos = slider.CurvePositionAt(completionProgress);
+
+ var diff = lastPosition.HasValue ? lastPosition.Value - newPos : newPos - slider.CurvePositionAt(completionProgress + 0.01f);
+ if (diff == Vector2.Zero)
+ return;
+
+ Position = newPos;
+ Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI);
+
+ lastPosition = newPos;
+ }
+
+ private class FollowCircleContainer : Container
+ {
+ public override bool HandlePositionalInput => true;
+ }
+
+ public class DefaultFollowCircle : CompositeDrawable
+ {
+ public DefaultFollowCircle()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ InternalChild = new CircularContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ BorderThickness = 5,
+ BorderColour = Color4.Orange,
+ Blending = BlendingParameters.Additive,
+ Child = new Box
+ {
+ Colour = Color4.Orange,
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.2f,
+ }
+ };
+ }
+ }
+
+ public class DefaultSliderBall : CompositeDrawable
+ {
+ [BackgroundDependencyLoader]
+ private void load(DrawableHitObject drawableObject, ISkinSource skin)
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ float radius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
+
+ InternalChild = new CircularContainer
+ {
+ Masking = true,
+ RelativeSizeAxes = Axes.Both,
+ Scale = new Vector2(radius / OsuHitObject.OBJECT_RADIUS),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ BorderThickness = 10,
+ BorderColour = Color4.White,
+ Alpha = 1,
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.White,
+ Alpha = 0.4f,
+ }
+ };
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
index 6bc19ee3b5..24a437c20e 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
protected Path Path => path;
- public float PathRadius
+ public virtual float PathRadius
{
get => path.PathRadius;
set => path.PathRadius = value;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
index a3d3893c8b..70a1bad4a3 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
@@ -24,6 +24,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public double? SnakedStart { get; private set; }
public double? SnakedEnd { get; private set; }
+ public override float PathRadius
+ {
+ get => base.PathRadius;
+ set
+ {
+ if (base.PathRadius == value)
+ return;
+
+ base.PathRadius = value;
+
+ Refresh();
+ }
+ }
+
public override Vector2 PathOffset => snakedPathOffset;
///
@@ -55,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
var spanProgress = slider.ProgressAt(completionProgress);
double start = 0;
- double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1;
+ double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / (slider.TimePreempt / 3), 0, 1) : 1;
if (span >= slider.SpanCount() - 1)
{
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index d1221fd2d3..2cf877b000 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -7,6 +7,8 @@ using osu.Game.Rulesets.Objects;
using osuTK;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Osu.Scoring;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
@@ -69,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Objects
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
- TimeFadeIn = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300);
+ TimeFadeIn = 400; // as per osu-stable
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
}
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs
deleted file mode 100644
index add8fd53c7..0000000000
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Scoring;
-
-namespace osu.Game.Rulesets.Osu.Objects
-{
- public class OsuHitWindows : HitWindows
- {
- private static readonly IReadOnlyDictionary base_ranges = new Dictionary
- {
- { HitResult.Great, (160, 100, 40) },
- { HitResult.Good, (280, 200, 120) },
- { HitResult.Meh, (400, 300, 200) },
- { HitResult.Miss, (400, 400, 400) },
- };
-
- public override void SetDifficulty(double difficulty)
- {
- Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
- Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
- Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
- Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs
index 63713541b4..a794e57c9e 100644
--- a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs
@@ -6,6 +6,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
@@ -28,5 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public override Judgement CreateJudgement() => new OsuJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index a4638c31f2..2805494021 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -13,6 +13,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
@@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
public double Duration => EndTime - StartTime;
- private Cached endPositionCache;
+ private readonly Cached endPositionCache = new Cached();
public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1);
@@ -229,5 +230,7 @@ namespace osu.Game.Rulesets.Osu.Objects
nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples;
public override Judgement CreateJudgement() => new OsuJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
index 4f2af64161..7e540a577b 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
@@ -5,6 +5,7 @@ using osu.Framework.Bindables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
@@ -23,5 +24,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public override Judgement CreateJudgement() => new OsuSliderTailJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
index 85439699dd..af7cf5b144 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
@@ -5,6 +5,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
@@ -30,5 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public override Judgement CreateJudgement() => new OsuJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 8a2fd3b7aa..2e7b763966 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -7,6 +7,7 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
@@ -31,5 +32,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public override Judgement CreateJudgement() => new OsuJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs
index b9e083d35b..cdea7276f3 100644
--- a/osu.Game.Rulesets.Osu/OsuInputManager.cs
+++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs
@@ -18,6 +18,12 @@ namespace osu.Game.Rulesets.Osu
set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowUserPresses = value;
}
+ ///
+ /// Whether the user's cursor movement events should be accepted.
+ /// Can be used to block only movement while still accepting button input.
+ ///
+ public bool AllowUserCursorMovement { get; set; } = true;
+
protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new OsuKeyBindingContainer(ruleset, variant, unique);
@@ -26,6 +32,13 @@ namespace osu.Game.Rulesets.Osu
{
}
+ protected override bool Handle(UIEvent e)
+ {
+ if (e is MouseMoveEvent && !AllowUserCursorMovement) return false;
+
+ return base.Handle(e);
+ }
+
private class OsuKeyBindingContainer : RulesetKeyBindingContainer
{
public bool AllowUserPresses = true;
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 7f45fbe1dd..ceb9ed9343 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -14,7 +14,6 @@ using osu.Game.Overlays.Settings;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
@@ -24,16 +23,20 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Difficulty;
+using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Scoring;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu
{
public class OsuRuleset : Ruleset
{
- public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuRuleset(this, beatmap, mods);
+ public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuRuleset(this, beatmap, mods);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
+ public const string SHORT_NAME = "osu";
+
public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
{
new KeyBinding(InputKey.Z, OsuAction.LeftButton),
@@ -136,7 +139,7 @@ namespace osu.Game.Rulesets.Osu
new OsuModWiggle(),
new OsuModSpinIn(),
new MultiMod(new OsuModGrow(), new OsuModDeflate()),
- new MultiMod(new ModWindUp(), new ModWindDown()),
+ new MultiMod(new ModWindUp(), new ModWindDown()),
};
case ModType.System:
@@ -160,10 +163,12 @@ namespace osu.Game.Rulesets.Osu
public override string Description => "osu!";
- public override string ShortName => "osu";
+ public override string ShortName => SHORT_NAME;
public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this);
+ public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkinTransformer(source);
+
public override int? LegacyID => 0;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame();
diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponent.cs b/osu.Game.Rulesets.Osu/OsuSkinComponent.cs
new file mode 100644
index 0000000000..1d223f231b
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponent.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Osu
+{
+ public class OsuSkinComponent : GameplaySkinComponent
+ {
+ public OsuSkinComponent(OsuSkinComponents component)
+ : base(component)
+ {
+ }
+
+ protected override string RulesetPrefix => OsuRuleset.SHORT_NAME;
+
+ protected override string ComponentName => Component.ToString().ToLower();
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
new file mode 100644
index 0000000000..8dd48eace0
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Osu
+{
+ public enum OsuSkinComponents
+ {
+ HitCircle,
+ FollowPoint,
+ Cursor,
+ CursorTrail,
+ SliderScorePoint,
+ ApproachCircle,
+ ReverseArrow,
+ HitCircleText,
+ SliderFollowCircle,
+ SliderBall
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
index 690263c6a0..24320b6579 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
@@ -6,11 +6,13 @@ using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using System;
+using System.Diagnostics;
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Replays;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Replays
@@ -36,6 +38,8 @@ namespace osu.Game.Rulesets.Osu.Replays
///
private readonly double reactionTime;
+ private readonly HitWindows defaultHitWindows;
+
///
/// What easing to use when moving between hitobjects
///
@@ -50,6 +54,9 @@ namespace osu.Game.Rulesets.Osu.Replays
{
// Already superhuman, but still somewhat realistic
reactionTime = ApplyModsToRate(100);
+
+ defaultHitWindows = new OsuHitWindows();
+ defaultHitWindows.SetDifficulty(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
}
#endregion
@@ -91,21 +98,49 @@ namespace osu.Game.Rulesets.Osu.Replays
{
double endTime = (prev as IHasEndTime)?.EndTime ?? prev.StartTime;
+ HitWindows hitWindows = null;
+
+ switch (h)
+ {
+ case HitCircle hitCircle:
+ hitWindows = hitCircle.HitWindows;
+ break;
+
+ case Slider slider:
+ hitWindows = slider.TailCircle.HitWindows;
+ break;
+
+ case Spinner _:
+ hitWindows = defaultHitWindows;
+ break;
+ }
+
+ Debug.Assert(hitWindows != null);
+
// Make the cursor stay at a hitObject as long as possible (mainly for autopilot).
- if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Miss) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50)
+ if (h.StartTime - hitWindows.WindowFor(HitResult.Miss) > endTime + hitWindows.WindowFor(HitResult.Meh) + 50)
{
- if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
- if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Miss), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
+ if (!(prev is Spinner) && h.StartTime - endTime < 1000)
+ AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
+
+ if (!(h is Spinner))
+ AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Miss), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
}
- else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50)
+ else if (h.StartTime - hitWindows.WindowFor(HitResult.Meh) > endTime + hitWindows.WindowFor(HitResult.Meh) + 50)
{
- if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
- if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
+ if (!(prev is Spinner) && h.StartTime - endTime < 1000)
+ AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
+
+ if (!(h is Spinner))
+ AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
}
- else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Good) > endTime + h.HitWindows.HalfWindowFor(HitResult.Good) + 50)
+ else if (h.StartTime - hitWindows.WindowFor(HitResult.Good) > endTime + hitWindows.WindowFor(HitResult.Good) + 50)
{
- if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
- if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
+ if (!(prev is Spinner) && h.StartTime - endTime < 1000)
+ AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
+
+ if (!(h is Spinner))
+ AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
}
}
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json
new file mode 100644
index 0000000000..b994cbd85a
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json
@@ -0,0 +1,278 @@
+{
+ "Mappings": [{
+ "StartTime": 32165,
+ "Objects": [{
+ "StartTime": 32165,
+ "EndTime": 32165,
+ "X": 32,
+ "Y": 320
+ }]
+ },
+ {
+ "StartTime": 32517,
+ "Objects": [{
+ "StartTime": 32517,
+ "EndTime": 32517,
+ "X": 246.396057,
+ "Y": 182.396057
+ }]
+ },
+ {
+ "StartTime": 32605,
+ "Objects": [{
+ "StartTime": 32605,
+ "EndTime": 32605,
+ "X": 249.597382,
+ "Y": 185.597382
+ }]
+ },
+ {
+ "StartTime": 32693,
+ "Objects": [{
+ "StartTime": 32693,
+ "EndTime": 32693,
+ "X": 252.798691,
+ "Y": 188.798691
+ }]
+ },
+ {
+ "StartTime": 32781,
+ "Objects": [{
+ "StartTime": 32781,
+ "EndTime": 32781,
+ "X": 256,
+ "Y": 192
+ }]
+ },
+ {
+ "StartTime": 33248,
+ "Objects": [{
+ "StartTime": 33248,
+ "EndTime": 33248,
+ "X": 39.3960648,
+ "Y": 76.3960648
+ }]
+ },
+ {
+ "StartTime": 33307,
+ "Objects": [{
+ "StartTime": 33307,
+ "EndTime": 33307,
+ "X": 42.5973778,
+ "Y": 79.597374
+ }]
+ },
+ {
+ "StartTime": 33383,
+ "Objects": [{
+ "StartTime": 33383,
+ "EndTime": 33383,
+ "X": 45.798687,
+ "Y": 82.79869
+ }]
+ },
+ {
+ "StartTime": 33459,
+ "Objects": [{
+ "StartTime": 33459,
+ "EndTime": 33459,
+ "X": 49,
+ "Y": 86
+ },
+ {
+ "StartTime": 33635,
+ "EndTime": 33635,
+ "X": 123.847847,
+ "Y": 85.7988
+ },
+ {
+ "StartTime": 33811,
+ "EndTime": 33811,
+ "X": 198.6957,
+ "Y": 85.5975952
+ },
+ {
+ "StartTime": 33988,
+ "EndTime": 33988,
+ "X": 273.9688,
+ "Y": 85.39525
+ },
+ {
+ "StartTime": 34164,
+ "EndTime": 34164,
+ "X": 348.816681,
+ "Y": 85.19404
+ },
+ {
+ "StartTime": 34246,
+ "EndTime": 34246,
+ "X": 398.998718,
+ "Y": 85.05914
+ }
+ ]
+ },
+ {
+ "StartTime": 34341,
+ "Objects": [{
+ "StartTime": 34341,
+ "EndTime": 34341,
+ "X": 401.201324,
+ "Y": 88.20131
+ }]
+ },
+ {
+ "StartTime": 34400,
+ "Objects": [{
+ "StartTime": 34400,
+ "EndTime": 34400,
+ "X": 404.402618,
+ "Y": 91.402626
+ }]
+ },
+ {
+ "StartTime": 34459,
+ "Objects": [{
+ "StartTime": 34459,
+ "EndTime": 34459,
+ "X": 407.603943,
+ "Y": 94.6039352
+ }]
+ },
+ {
+ "StartTime": 34989,
+ "Objects": [{
+ "StartTime": 34989,
+ "EndTime": 34989,
+ "X": 163,
+ "Y": 138
+ },
+ {
+ "StartTime": 35018,
+ "EndTime": 35018,
+ "X": 188,
+ "Y": 138
+ }
+ ]
+ },
+ {
+ "StartTime": 35106,
+ "Objects": [{
+ "StartTime": 35106,
+ "EndTime": 35106,
+ "X": 163,
+ "Y": 138
+ },
+ {
+ "StartTime": 35135,
+ "EndTime": 35135,
+ "X": 188,
+ "Y": 138
+ }
+ ]
+ },
+ {
+ "StartTime": 35224,
+ "Objects": [{
+ "StartTime": 35224,
+ "EndTime": 35224,
+ "X": 163,
+ "Y": 138
+ },
+ {
+ "StartTime": 35253,
+ "EndTime": 35253,
+ "X": 188,
+ "Y": 138
+ }
+ ]
+ },
+ {
+ "StartTime": 35695,
+ "Objects": [{
+ "StartTime": 35695,
+ "EndTime": 35695,
+ "X": 166,
+ "Y": 76
+ },
+ {
+ "StartTime": 35871,
+ "EndTime": 35871,
+ "X": 240.99855,
+ "Y": 75.53417
+ },
+ {
+ "StartTime": 36011,
+ "EndTime": 36011,
+ "X": 315.9971,
+ "Y": 75.0683441
+ }
+ ]
+ },
+ {
+ "StartTime": 36106,
+ "Objects": [{
+ "StartTime": 36106,
+ "EndTime": 36106,
+ "X": 315,
+ "Y": 75
+ },
+ {
+ "StartTime": 36282,
+ "EndTime": 36282,
+ "X": 240.001526,
+ "Y": 75.47769
+ },
+ {
+ "StartTime": 36422,
+ "EndTime": 36422,
+ "X": 165.003052,
+ "Y": 75.95539
+ }
+ ]
+ },
+ {
+ "StartTime": 36518,
+ "Objects": [{
+ "StartTime": 36518,
+ "EndTime": 36518,
+ "X": 166,
+ "Y": 76
+ },
+ {
+ "StartTime": 36694,
+ "EndTime": 36694,
+ "X": 240.99855,
+ "Y": 75.53417
+ },
+ {
+ "StartTime": 36834,
+ "EndTime": 36834,
+ "X": 315.9971,
+ "Y": 75.0683441
+ }
+ ]
+ },
+ {
+ "StartTime": 36929,
+ "Objects": [{
+ "StartTime": 36929,
+ "EndTime": 36929,
+ "X": 315,
+ "Y": 75
+ },
+ {
+ "StartTime": 37105,
+ "EndTime": 37105,
+ "X": 240.001526,
+ "Y": 75.47769
+ },
+ {
+ "StartTime": 37245,
+ "EndTime": 37245,
+ "X": 165.003052,
+ "Y": 75.95539
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking.osu
new file mode 100644
index 0000000000..4bc9226d67
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking.osu
@@ -0,0 +1,40 @@
+osu file format v3
+
+[Difficulty]
+HPDrainRate:3
+CircleSize:5
+OverallDifficulty:8
+ApproachRate:8
+SliderMultiplier:1.5
+SliderTickRate:2
+
+[TimingPoints]
+48,352.941176470588,4,1,1,100,1,0
+
+[HitObjects]
+// Hit circles
+32,320,32165,5,2,0:0:0:0:
+256,192,32517,5,0,0:0:0:0:
+256,192,32605,1,0,0:0:0:0:
+256,192,32693,1,0,0:0:0:0:
+256,192,32781,1,0,0:0:0:0:
+
+// Hit circles on slider endpoints
+49,86,33248,1,0,0:0:0:0:
+49,86,33307,1,0,0:0:0:0:
+49,86,33383,1,0,0:0:0:0:
+49,86,33459,2,0,L|421:85,1,350
+398,85,34341,1,0,0:0:0:0:
+398,85,34400,1,0,0:0:0:0:
+398,85,34459,1,0,0:0:0:0:
+
+// Sliders
+163,138,34989,2,0,L|196:138,1,25
+163,138,35106,2,0,L|196:138,1,25
+163,138,35224,2,0,L|196:138,1,25
+
+// Reversed sliders
+166,76,35695,2,0,L|327:75,1,150
+315,75,36106,2,0,L|158:76,1,150
+166,76,36518,2,0,L|327:75,1,150
+315,75,36929,2,0,L|158:76,1,150
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider-expected-conversion.json
new file mode 100644
index 0000000000..fb4050963f
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider-expected-conversion.json
@@ -0,0 +1,222 @@
+{
+ "Mappings": [{
+ "StartTime": 369,
+ "Objects": [{
+ "StartTime": 369,
+ "EndTime": 369,
+ "X": 177,
+ "Y": 191
+ },
+ {
+ "StartTime": 450,
+ "EndTime": 450,
+ "X": 216.539276,
+ "Y": 191.192871
+ },
+ {
+ "StartTime": 532,
+ "EndTime": 532,
+ "X": 256.5667,
+ "Y": 191.388138
+ },
+ {
+ "StartTime": 614,
+ "EndTime": 614,
+ "X": 296.594116,
+ "Y": 191.583389
+ },
+ {
+ "StartTime": 696,
+ "EndTime": 696,
+ "X": 336.621521,
+ "Y": 191.778641
+ },
+ {
+ "StartTime": 778,
+ "EndTime": 778,
+ "X": 376.648926,
+ "Y": 191.9739
+ },
+ {
+ "StartTime": 860,
+ "EndTime": 860,
+ "X": 337.318878,
+ "Y": 191.782043
+ },
+ {
+ "StartTime": 942,
+ "EndTime": 942,
+ "X": 297.291443,
+ "Y": 191.586792
+ },
+ {
+ "StartTime": 1024,
+ "EndTime": 1024,
+ "X": 257.264038,
+ "Y": 191.391541
+ },
+ {
+ "StartTime": 1106,
+ "EndTime": 1106,
+ "X": 217.2366,
+ "Y": 191.196274
+ },
+ {
+ "StartTime": 1188,
+ "EndTime": 1188,
+ "X": 177.209213,
+ "Y": 191.001022
+ },
+ {
+ "StartTime": 1270,
+ "EndTime": 1270,
+ "X": 216.818192,
+ "Y": 191.194229
+ },
+ {
+ "StartTime": 1352,
+ "EndTime": 1352,
+ "X": 256.8456,
+ "Y": 191.3895
+ },
+ {
+ "StartTime": 1434,
+ "EndTime": 1434,
+ "X": 296.873047,
+ "Y": 191.584747
+ },
+ {
+ "StartTime": 1516,
+ "EndTime": 1516,
+ "X": 336.900452,
+ "Y": 191.78
+ },
+ {
+ "StartTime": 1598,
+ "EndTime": 1598,
+ "X": 376.927917,
+ "Y": 191.975266
+ },
+ {
+ "StartTime": 1680,
+ "EndTime": 1680,
+ "X": 337.039948,
+ "Y": 191.780685
+ },
+ {
+ "StartTime": 1762,
+ "EndTime": 1762,
+ "X": 297.0125,
+ "Y": 191.585434
+ },
+ {
+ "StartTime": 1844,
+ "EndTime": 1844,
+ "X": 256.9851,
+ "Y": 191.390167
+ },
+ {
+ "StartTime": 1926,
+ "EndTime": 1926,
+ "X": 216.957672,
+ "Y": 191.194916
+ },
+ {
+ "StartTime": 2008,
+ "EndTime": 2008,
+ "X": 177.069717,
+ "Y": 191.000336
+ },
+ {
+ "StartTime": 2090,
+ "EndTime": 2090,
+ "X": 217.097137,
+ "Y": 191.1956
+ },
+ {
+ "StartTime": 2172,
+ "EndTime": 2172,
+ "X": 257.124573,
+ "Y": 191.390854
+ },
+ {
+ "StartTime": 2254,
+ "EndTime": 2254,
+ "X": 297.152,
+ "Y": 191.5861
+ },
+ {
+ "StartTime": 2336,
+ "EndTime": 2336,
+ "X": 337.179443,
+ "Y": 191.781372
+ },
+ {
+ "StartTime": 2418,
+ "EndTime": 2418,
+ "X": 376.7884,
+ "Y": 191.974579
+ },
+ {
+ "StartTime": 2500,
+ "EndTime": 2500,
+ "X": 336.760956,
+ "Y": 191.779327
+ },
+ {
+ "StartTime": 2582,
+ "EndTime": 2582,
+ "X": 296.733643,
+ "Y": 191.584076
+ },
+ {
+ "StartTime": 2664,
+ "EndTime": 2664,
+ "X": 256.7062,
+ "Y": 191.388809
+ },
+ {
+ "StartTime": 2746,
+ "EndTime": 2746,
+ "X": 216.678772,
+ "Y": 191.193558
+ },
+ {
+ "StartTime": 2828,
+ "EndTime": 2828,
+ "X": 177.348663,
+ "Y": 191.0017
+ },
+ {
+ "StartTime": 2909,
+ "EndTime": 2909,
+ "X": 216.887909,
+ "Y": 191.19458
+ },
+ {
+ "StartTime": 2991,
+ "EndTime": 2991,
+ "X": 256.915344,
+ "Y": 191.389832
+ },
+ {
+ "StartTime": 3073,
+ "EndTime": 3073,
+ "X": 296.942749,
+ "Y": 191.585083
+ },
+ {
+ "StartTime": 3155,
+ "EndTime": 3155,
+ "X": 336.970184,
+ "Y": 191.78035
+ },
+ {
+ "StartTime": 3201,
+ "EndTime": 3201,
+ "X": 376.99762,
+ "Y": 191.9756
+ }
+ ]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider.osu
new file mode 100644
index 0000000000..624d905384
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider.osu
@@ -0,0 +1,18 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.4
+Mode: 0
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8
+SliderMultiplier:1.6
+SliderTickRate:4
+
+[TimingPoints]
+369,327.868852459016,4,2,2,32,1,0
+
+[HitObjects]
+177,191,369,6,0,L|382:192,7,200
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json
new file mode 100644
index 0000000000..12d1645c04
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json
@@ -0,0 +1,348 @@
+{
+ "Mappings": [{
+ "StartTime": 369,
+ "Objects": [{
+ "StartTime": 369,
+ "EndTime": 369,
+ "X": 127,
+ "Y": 194
+ },
+ {
+ "StartTime": 450,
+ "EndTime": 450,
+ "X": 166.53389,
+ "Y": 193.8691
+ },
+ {
+ "StartTime": 532,
+ "EndTime": 532,
+ "X": 206.555847,
+ "Y": 193.736572
+ },
+ {
+ "StartTime": 614,
+ "EndTime": 614,
+ "X": 246.57782,
+ "Y": 193.60405
+ },
+ {
+ "StartTime": 696,
+ "EndTime": 696,
+ "X": 286.5998,
+ "Y": 193.471527
+ },
+ {
+ "StartTime": 778,
+ "EndTime": 778,
+ "X": 326.621765,
+ "Y": 193.339
+ },
+ {
+ "StartTime": 860,
+ "EndTime": 860,
+ "X": 366.6437,
+ "Y": 193.206482
+ },
+ {
+ "StartTime": 942,
+ "EndTime": 942,
+ "X": 406.66568,
+ "Y": 193.073959
+ },
+ {
+ "StartTime": 970,
+ "EndTime": 970,
+ "X": 420.331726,
+ "Y": 193.0287
+ },
+ {
+ "StartTime": 997,
+ "EndTime": 997,
+ "X": 407.153748,
+ "Y": 193.072342
+ },
+ {
+ "StartTime": 1079,
+ "EndTime": 1079,
+ "X": 367.131775,
+ "Y": 193.204865
+ },
+ {
+ "StartTime": 1161,
+ "EndTime": 1161,
+ "X": 327.1098,
+ "Y": 193.337387
+ },
+ {
+ "StartTime": 1243,
+ "EndTime": 1243,
+ "X": 287.08783,
+ "Y": 193.46991
+ },
+ {
+ "StartTime": 1325,
+ "EndTime": 1325,
+ "X": 247.0659,
+ "Y": 193.602432
+ },
+ {
+ "StartTime": 1407,
+ "EndTime": 1407,
+ "X": 207.043915,
+ "Y": 193.734955
+ },
+ {
+ "StartTime": 1489,
+ "EndTime": 1489,
+ "X": 167.021988,
+ "Y": 193.867477
+ },
+ {
+ "StartTime": 1571,
+ "EndTime": 1571,
+ "X": 127,
+ "Y": 194
+ },
+ {
+ "StartTime": 1653,
+ "EndTime": 1653,
+ "X": 167.021988,
+ "Y": 193.867477
+ },
+ {
+ "StartTime": 1735,
+ "EndTime": 1735,
+ "X": 207.043976,
+ "Y": 193.734955
+ },
+ {
+ "StartTime": 1817,
+ "EndTime": 1817,
+ "X": 247.065887,
+ "Y": 193.602432
+ },
+ {
+ "StartTime": 1899,
+ "EndTime": 1899,
+ "X": 287.08783,
+ "Y": 193.46991
+ },
+ {
+ "StartTime": 1981,
+ "EndTime": 1981,
+ "X": 327.1098,
+ "Y": 193.337387
+ },
+ {
+ "StartTime": 2062,
+ "EndTime": 2062,
+ "X": 366.643738,
+ "Y": 193.206482
+ },
+ {
+ "StartTime": 2144,
+ "EndTime": 2144,
+ "X": 406.665649,
+ "Y": 193.073959
+ },
+ {
+ "StartTime": 2172,
+ "EndTime": 2172,
+ "X": 420.331726,
+ "Y": 193.0287
+ },
+ {
+ "StartTime": 2199,
+ "EndTime": 2199,
+ "X": 407.153748,
+ "Y": 193.072342
+ },
+ {
+ "StartTime": 2281,
+ "EndTime": 2281,
+ "X": 367.1318,
+ "Y": 193.204865
+ },
+ {
+ "StartTime": 2363,
+ "EndTime": 2363,
+ "X": 327.1098,
+ "Y": 193.337387
+ },
+ {
+ "StartTime": 2445,
+ "EndTime": 2445,
+ "X": 287.08783,
+ "Y": 193.46991
+ },
+ {
+ "StartTime": 2527,
+ "EndTime": 2527,
+ "X": 247.065887,
+ "Y": 193.602432
+ },
+ {
+ "StartTime": 2609,
+ "EndTime": 2609,
+ "X": 207.043976,
+ "Y": 193.734955
+ },
+ {
+ "StartTime": 2691,
+ "EndTime": 2691,
+ "X": 167.021988,
+ "Y": 193.867477
+ },
+ {
+ "StartTime": 2773,
+ "EndTime": 2773,
+ "X": 127,
+ "Y": 194
+ },
+ {
+ "StartTime": 2855,
+ "EndTime": 2855,
+ "X": 167.021988,
+ "Y": 193.867477
+ },
+ {
+ "StartTime": 2937,
+ "EndTime": 2937,
+ "X": 207.043976,
+ "Y": 193.734955
+ },
+ {
+ "StartTime": 3019,
+ "EndTime": 3019,
+ "X": 247.065948,
+ "Y": 193.602432
+ },
+ {
+ "StartTime": 3101,
+ "EndTime": 3101,
+ "X": 287.087952,
+ "Y": 193.46991
+ },
+ {
+ "StartTime": 3183,
+ "EndTime": 3183,
+ "X": 327.109772,
+ "Y": 193.337387
+ },
+ {
+ "StartTime": 3265,
+ "EndTime": 3265,
+ "X": 367.131775,
+ "Y": 193.204865
+ },
+ {
+ "StartTime": 3347,
+ "EndTime": 3347,
+ "X": 407.153748,
+ "Y": 193.072342
+ },
+ {
+ "StartTime": 3374,
+ "EndTime": 3374,
+ "X": 420.331726,
+ "Y": 193.0287
+ },
+ {
+ "StartTime": 3401,
+ "EndTime": 3401,
+ "X": 407.153748,
+ "Y": 193.072342
+ },
+ {
+ "StartTime": 3483,
+ "EndTime": 3483,
+ "X": 367.131775,
+ "Y": 193.204865
+ },
+ {
+ "StartTime": 3565,
+ "EndTime": 3565,
+ "X": 327.109772,
+ "Y": 193.337387
+ },
+ {
+ "StartTime": 3647,
+ "EndTime": 3647,
+ "X": 287.087952,
+ "Y": 193.46991
+ },
+ {
+ "StartTime": 3729,
+ "EndTime": 3729,
+ "X": 247.065948,
+ "Y": 193.602432
+ },
+ {
+ "StartTime": 3811,
+ "EndTime": 3811,
+ "X": 207.043976,
+ "Y": 193.734955
+ },
+ {
+ "StartTime": 3893,
+ "EndTime": 3893,
+ "X": 167.021988,
+ "Y": 193.867477
+ },
+ {
+ "StartTime": 3975,
+ "EndTime": 3975,
+ "X": 127,
+ "Y": 194
+ },
+ {
+ "StartTime": 4057,
+ "EndTime": 4057,
+ "X": 167.021988,
+ "Y": 193.867477
+ },
+ {
+ "StartTime": 4139,
+ "EndTime": 4139,
+ "X": 207.043976,
+ "Y": 193.734955
+ },
+ {
+ "StartTime": 4221,
+ "EndTime": 4221,
+ "X": 247.065948,
+ "Y": 193.602432
+ },
+ {
+ "StartTime": 4303,
+ "EndTime": 4303,
+ "X": 287.087952,
+ "Y": 193.46991
+ },
+ {
+ "StartTime": 4385,
+ "EndTime": 4385,
+ "X": 327.109772,
+ "Y": 193.337387
+ },
+ {
+ "StartTime": 4467,
+ "EndTime": 4467,
+ "X": 367.131775,
+ "Y": 193.204865
+ },
+ {
+ "StartTime": 4540,
+ "EndTime": 4540,
+ "X": 420.331726,
+ "Y": 193.0287
+ },
+ {
+ "StartTime": 4549,
+ "EndTime": 4549,
+ "X": 407.153748,
+ "Y": 193.072342
+ }
+ ]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider.osu
new file mode 100644
index 0000000000..64aeeb0c07
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider.osu
@@ -0,0 +1,19 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.4
+Mode: 0
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8
+SliderMultiplier:1.6
+SliderTickRate:4
+
+[TimingPoints]
+369,327.868852459016,4,2,2,32,1,0
+
+[HitObjects]
+// A slider with an un-even amount of ticks
+127,194,369,6,0,L|429:193,7,293.333333333333
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs
new file mode 100644
index 0000000000..a6491bb3f3
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs
@@ -0,0 +1,34 @@
+// 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.Scoring;
+
+namespace osu.Game.Rulesets.Osu.Scoring
+{
+ public class OsuHitWindows : HitWindows
+ {
+ private static readonly DifficultyRange[] osu_ranges =
+ {
+ new DifficultyRange(HitResult.Great, 80, 50, 20),
+ new DifficultyRange(HitResult.Good, 140, 100, 60),
+ new DifficultyRange(HitResult.Meh, 200, 150, 100),
+ new DifficultyRange(HitResult.Miss, 200, 200, 200),
+ };
+
+ public override bool IsHitResultAllowed(HitResult result)
+ {
+ switch (result)
+ {
+ case HitResult.Great:
+ case HitResult.Good:
+ case HitResult.Meh:
+ case HitResult.Miss:
+ return true;
+ }
+
+ return false;
+ }
+
+ protected override DifficultyRange[] GetRanges() => osu_ranges;
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
index cf0565c6da..affe18a30d 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Collections.Generic;
-using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
@@ -22,8 +20,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
private float hpDrainRate;
- private readonly Dictionary comboResultCounts = new Dictionary();
-
protected override void ApplyBeatmap(Beatmap beatmap)
{
base.ApplyBeatmap(beatmap);
@@ -31,22 +27,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
}
- protected override void Reset(bool storeResults)
- {
- base.Reset(storeResults);
- comboResultCounts.Clear();
- }
-
- protected override void ApplyResult(JudgementResult result)
- {
- base.ApplyResult(result);
-
- var osuResult = (OsuJudgementResult)result;
-
- if (result.Type != HitResult.None)
- comboResultCounts[osuResult.ComboType] = comboResultCounts.GetOrDefault(osuResult.ComboType) + 1;
- }
-
protected override double HealthAdjustmentFactorFor(JudgementResult result)
{
switch (result.Type)
@@ -71,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
}
}
- protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement);
+ protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement);
public override HitWindows CreateHitWindows() => new OsuHitWindows();
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs
new file mode 100644
index 0000000000..470ba3acae
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs
@@ -0,0 +1,42 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Skinning
+{
+ public class LegacyCursor : CompositeDrawable
+ {
+ public LegacyCursor()
+ {
+ Size = new Vector2(50);
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ InternalChildren = new Drawable[]
+ {
+ new NonPlayfieldSprite
+ {
+ Texture = skin.GetTexture("cursormiddle"),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new NonPlayfieldSprite
+ {
+ Texture = skin.GetTexture("cursor"),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs
new file mode 100644
index 0000000000..1885c76fcc
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs
@@ -0,0 +1,55 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Osu.UI.Cursor;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Osu.Skinning
+{
+ public class LegacyCursorTrail : CursorTrail
+ {
+ private const double disjoint_trail_time_separation = 1000 / 60.0;
+
+ private bool disjointTrail;
+ private double lastTrailTime;
+
+ public LegacyCursorTrail()
+ {
+ Blending = BlendingParameters.Additive;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ Texture = skin.GetTexture("cursortrail");
+ disjointTrail = skin.GetTexture("cursormiddle") == null;
+
+ if (Texture != null)
+ {
+ // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation.
+ Texture.ScaleAdjust *= 1.6f;
+ }
+ }
+
+ protected override double FadeDuration => disjointTrail ? 150 : 500;
+
+ protected override bool InterpolateMovements => !disjointTrail;
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ if (!disjointTrail)
+ return base.OnMouseMove(e);
+
+ if (Time.Current - lastTrailTime >= disjoint_trail_time_separation)
+ {
+ lastTrailTime = Time.Current;
+ return base.OnMouseMove(e);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs
new file mode 100644
index 0000000000..83d507f64b
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs
@@ -0,0 +1,81 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Skinning;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Skinning
+{
+ public class LegacyMainCirclePiece : CompositeDrawable
+ {
+ public LegacyMainCirclePiece()
+ {
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
+ }
+
+ private readonly IBindable state = new Bindable();
+
+ private readonly Bindable accentColour = new Bindable();
+
+ [BackgroundDependencyLoader]
+ private void load(DrawableHitObject drawableObject, ISkinSource skin)
+ {
+ Sprite hitCircleSprite;
+
+ InternalChildren = new Drawable[]
+ {
+ hitCircleSprite = new Sprite
+ {
+ Texture = skin.GetTexture("hitcircle"),
+ Colour = drawableObject.AccentColour.Value,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText
+ {
+ Font = OsuFont.Numeric.With(size: 40),
+ UseFullGlyphHeight = false,
+ }, confineMode: ConfineMode.NoScaling)
+ {
+ Text = (((IHasComboInformation)drawableObject.HitObject).IndexInCurrentCombo + 1).ToString()
+ },
+ new Sprite
+ {
+ Texture = skin.GetTexture("hitcircleoverlay"),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ };
+
+ state.BindTo(drawableObject.State);
+ state.BindValueChanged(updateState, true);
+
+ accentColour.BindTo(drawableObject.AccentColour);
+ accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true);
+ }
+
+ private void updateState(ValueChangedEvent state)
+ {
+ const double legacy_fade_duration = 240;
+
+ switch (state.NewValue)
+ {
+ case ArmedState.Hit:
+ this.FadeOut(legacy_fade_duration, Easing.Out);
+ this.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
+ break;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
new file mode 100644
index 0000000000..81c02199d0
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
@@ -0,0 +1,44 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Skinning;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Skinning
+{
+ public class LegacySliderBall : CompositeDrawable
+ {
+ private readonly Drawable animationContent;
+
+ public LegacySliderBall(Drawable animationContent)
+ {
+ this.animationContent = animationContent;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin, DrawableHitObject drawableObject)
+ {
+ animationContent.Colour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White;
+
+ InternalChildren = new[]
+ {
+ new Sprite
+ {
+ Texture = skin.GetTexture("sliderb-nd"),
+ Colour = new Color4(5, 5, 5, 255),
+ },
+ animationContent,
+ new Sprite
+ {
+ Texture = skin.GetTexture("sliderb-spec"),
+ Blending = BlendingParameters.Additive,
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/NonPlayfieldSprite.cs b/osu.Game.Rulesets.Osu/Skinning/NonPlayfieldSprite.cs
new file mode 100644
index 0000000000..55257106e2
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/NonPlayfieldSprite.cs
@@ -0,0 +1,28 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.Osu.Skinning
+{
+ ///
+ /// A sprite which is displayed within the playfield, but historically was not considered part of the playfield.
+ /// Performs scale adjustment to undo the scale applied by (osu! ruleset specifically).
+ ///
+ public class NonPlayfieldSprite : Sprite
+ {
+ public override Texture Texture
+ {
+ get => base.Texture;
+ set
+ {
+ if (value != null)
+ // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation.
+ value.ScaleAdjust *= 1.6f;
+ base.Texture = value;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
new file mode 100644
index 0000000000..479c250eab
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
@@ -0,0 +1,136 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Skinning
+{
+ public class OsuLegacySkinTransformer : ISkin
+ {
+ private readonly ISkin source;
+
+ private Lazy hasHitCircle;
+
+ ///
+ /// On osu-stable, hitcircles have 5 pixels of transparent padding on each side to allow for shadows etc.
+ /// Their hittable area is 128px, but the actual circle portion is 118px.
+ /// We must account for some gameplay elements such as slider bodies, where this padding is not present.
+ ///
+ private const float legacy_circle_radius = 64 - 5;
+
+ public OsuLegacySkinTransformer(ISkinSource source)
+ {
+ this.source = source;
+
+ source.SourceChanged += sourceChanged;
+ sourceChanged();
+ }
+
+ private void sourceChanged()
+ {
+ hasHitCircle = new Lazy(() => source.GetTexture("hitcircle") != null);
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component)
+ {
+ if (!(component is OsuSkinComponent osuComponent))
+ return null;
+
+ switch (osuComponent.Component)
+ {
+ case OsuSkinComponents.FollowPoint:
+ return this.GetAnimation(component.LookupName, true, false);
+
+ case OsuSkinComponents.SliderFollowCircle:
+ return this.GetAnimation("sliderfollowcircle", true, true);
+
+ case OsuSkinComponents.SliderBall:
+ var sliderBallContent = this.GetAnimation("sliderb", true, true, "");
+
+ if (sliderBallContent != null)
+ {
+ var size = sliderBallContent.Size;
+
+ sliderBallContent.RelativeSizeAxes = Axes.Both;
+ sliderBallContent.Size = Vector2.One;
+
+ return new LegacySliderBall(sliderBallContent)
+ {
+ Size = size
+ };
+ }
+
+ return null;
+
+ case OsuSkinComponents.HitCircle:
+ if (hasHitCircle.Value)
+ return new LegacyMainCirclePiece();
+
+ return null;
+
+ case OsuSkinComponents.Cursor:
+ if (source.GetTexture("cursor") != null)
+ return new LegacyCursor();
+
+ return null;
+
+ case OsuSkinComponents.CursorTrail:
+ if (source.GetTexture("cursortrail") != null)
+ return new LegacyCursorTrail();
+
+ return null;
+
+ case OsuSkinComponents.HitCircleText:
+ var font = GetConfig(OsuSkinConfiguration.HitCirclePrefix)?.Value ?? "default";
+ var overlap = GetConfig(OsuSkinConfiguration.HitCircleOverlap)?.Value ?? 0;
+
+ return !hasFont(font)
+ ? null
+ : new LegacySpriteText(source, font)
+ {
+ // stable applies a blanket 0.8x scale to hitcircle fonts
+ Scale = new Vector2(0.8f),
+ Spacing = new Vector2(-overlap, 0)
+ };
+ }
+
+ return null;
+ }
+
+ public Texture GetTexture(string componentName) => source.GetTexture(componentName);
+
+ public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
+
+ public IBindable GetConfig(TLookup lookup)
+ {
+ switch (lookup)
+ {
+ case OsuSkinColour colour:
+ return source.GetConfig(new SkinCustomColourLookup(colour));
+
+ case OsuSkinConfiguration osuLookup:
+ switch (osuLookup)
+ {
+ case OsuSkinConfiguration.SliderPathRadius:
+ if (hasHitCircle.Value)
+ return SkinUtils.As(new BindableFloat(legacy_circle_radius));
+
+ break;
+ }
+
+ break;
+ }
+
+ return source.GetConfig(lookup);
+ }
+
+ private bool hasFont(string fontName) => source.GetTexture($"{fontName}-0") != null;
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
new file mode 100644
index 0000000000..4e6d3ef0e4
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
@@ -0,0 +1,12 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Osu.Skinning
+{
+ public enum OsuSkinColour
+ {
+ SliderTrackOverride,
+ SliderBorder,
+ SliderBall
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
new file mode 100644
index 0000000000..e7b686d27d
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
@@ -0,0 +1,14 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Osu.Skinning
+{
+ public enum OsuSkinConfiguration
+ {
+ HitCirclePrefix,
+ HitCircleOverlap,
+ SliderBorderSize,
+ SliderPathRadius,
+ CursorExpand,
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 05eb0ffdbf..b32dfd483f 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -5,6 +5,7 @@ using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using osu.Framework.Allocation;
+using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Batches;
using osu.Framework.Graphics.OpenGL.Vertices;
@@ -20,30 +21,15 @@ using osuTK.Graphics.ES30;
namespace osu.Game.Rulesets.Osu.UI.Cursor
{
- internal class CursorTrail : Drawable, IRequireHighFrequencyMousePosition
+ public class CursorTrail : Drawable, IRequireHighFrequencyMousePosition
{
- private int currentIndex;
-
- private IShader shader;
- private Texture texture;
-
- private Vector2 size => texture.Size * Scale;
-
- private double timeOffset;
-
- private float time;
-
- public override bool IsPresent => true;
-
private const int max_sprites = 2048;
private readonly TrailPart[] parts = new TrailPart[max_sprites];
-
- private Vector2? lastPosition;
-
- private readonly InputResampler resampler = new InputResampler();
-
- protected override DrawNode CreateDrawNode() => new TrailDrawNode(this);
+ private int currentIndex;
+ private IShader shader;
+ private double timeOffset;
+ private float time;
public CursorTrail()
{
@@ -60,14 +46,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
}
}
- public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
-
[BackgroundDependencyLoader]
- private void load(ShaderManager shaders, TextureStore textures)
+ private void load(ShaderManager shaders)
{
shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE);
- texture = textures.Get(@"Cursor/cursortrail");
- Scale = new Vector2(1 / texture.ScaleAdjust);
}
protected override void LoadComplete()
@@ -76,6 +58,42 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
resetTime();
}
+ private Texture texture = Texture.WhitePixel;
+
+ public Texture Texture
+ {
+ get => texture;
+ set
+ {
+ if (texture == value)
+ return;
+
+ texture = value;
+ Invalidate(Invalidation.DrawNode);
+ }
+ }
+
+ private readonly Cached partSizeCache = new Cached();
+
+ private Vector2 partSize => partSizeCache.IsValid
+ ? partSizeCache.Value
+ : (partSizeCache.Value = new Vector2(Texture.DisplayWidth, Texture.DisplayHeight) * DrawInfo.Matrix.ExtractScale().Xy);
+
+ public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
+ {
+ if ((invalidation & (Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence)) > 0)
+ partSizeCache.Invalidate();
+
+ return base.Invalidate(invalidation, source, shallPropagate);
+ }
+
+ ///
+ /// The amount of time to fade the cursor trail pieces.
+ ///
+ protected virtual double FadeDuration => 300;
+
+ public override bool IsPresent => true;
+
protected override void Update()
{
base.Update();
@@ -84,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
const int fade_clock_reset_threshold = 1000000;
- time = (float)(Time.Current - timeOffset) / 300f;
+ time = (float)((Time.Current - timeOffset) / FadeDuration);
if (time > fade_clock_reset_threshold)
resetTime();
}
@@ -101,6 +119,16 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
timeOffset = Time.Current;
}
+ ///
+ /// Whether to interpolate mouse movements and add trail pieces at intermediate points.
+ ///
+ protected virtual bool InterpolateMovements => true;
+
+ private Vector2? lastPosition;
+ private readonly InputResampler resampler = new InputResampler();
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
+
protected override bool OnMouseMove(MouseMoveEvent e)
{
Vector2 pos = e.ScreenSpaceMousePosition;
@@ -116,33 +144,43 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
Trace.Assert(lastPosition.HasValue);
- // ReSharper disable once PossibleInvalidOperationException
- Vector2 pos1 = lastPosition.Value;
- Vector2 diff = pos2 - pos1;
- float distance = diff.Length;
- Vector2 direction = diff / distance;
-
- float interval = size.X / 2 * 0.9f;
-
- for (float d = interval; d < distance; d += interval)
+ if (InterpolateMovements)
{
- lastPosition = pos1 + direction * d;
- addPosition(lastPosition.Value);
+ // ReSharper disable once PossibleInvalidOperationException
+ Vector2 pos1 = lastPosition.Value;
+ Vector2 diff = pos2 - pos1;
+ float distance = diff.Length;
+ Vector2 direction = diff / distance;
+
+ float interval = partSize.X / 2.5f;
+
+ for (float d = interval; d < distance; d += interval)
+ {
+ lastPosition = pos1 + direction * d;
+ addPart(lastPosition.Value);
+ }
+ }
+ else
+ {
+ lastPosition = pos2;
+ addPart(lastPosition.Value);
}
}
return base.OnMouseMove(e);
}
- private void addPosition(Vector2 pos)
+ private void addPart(Vector2 screenSpacePosition)
{
- parts[currentIndex].Position = pos;
+ parts[currentIndex].Position = screenSpacePosition;
parts[currentIndex].Time = time;
++parts[currentIndex].InvalidationID;
currentIndex = (currentIndex + 1) % max_sprites;
}
+ protected override DrawNode CreateDrawNode() => new TrailDrawNode(this);
+
private struct TrailPart
{
public Vector2 Position;
@@ -177,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
shader = Source.shader;
texture = Source.texture;
- size = Source.size;
+ size = Source.partSize;
time = Source.time;
for (int i = 0; i < Source.parts.Length; ++i)
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
index 27546fa424..41a02deaca 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
+using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -18,9 +19,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
public class OsuCursor : SkinReloadableDrawable
{
+ private const float size = 28;
+
private bool cursorExpand;
- private Bindable cursorScale;
+ private Bindable cursorScale;
private Bindable autoCursorScale;
private readonly IBindable beatmap = new Bindable();
@@ -30,12 +33,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public OsuCursor()
{
Origin = Anchor.Centre;
- Size = new Vector2(28);
+
+ Size = new Vector2(size);
}
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
- cursorExpand = skin.GetValue(s => s.CursorExpand ?? true);
+ cursorExpand = skin.GetConfig(OsuSkinConfiguration.CursorExpand)?.Value ?? true;
}
[BackgroundDependencyLoader]
@@ -46,73 +50,17 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
- Child = scaleTarget = new SkinnableDrawable("cursor", _ => new CircularContainer
- {
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- BorderThickness = Size.X / 6,
- BorderColour = Color4.White,
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Shadow,
- Colour = Color4.Pink.Opacity(0.5f),
- Radius = 5,
- },
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = 0,
- AlwaysPresent = true,
- },
- new CircularContainer
- {
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- BorderThickness = Size.X / 3,
- BorderColour = Color4.White.Opacity(0.5f),
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = 0,
- AlwaysPresent = true,
- },
- },
- },
- new CircularContainer
- {
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Scale = new Vector2(0.1f),
- Masking = true,
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.White,
- },
- },
- },
- }
- }, restrictSize: false)
+ Child = scaleTarget = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling)
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
}
};
this.beatmap.BindTo(beatmap);
this.beatmap.ValueChanged += _ => calculateScale();
- cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize);
+ cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize);
cursorScale.ValueChanged += _ => calculateScale();
autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize);
@@ -123,12 +71,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private void calculateScale()
{
- float scale = (float)cursorScale.Value;
+ float scale = cursorScale.Value;
if (autoCursorScale.Value && beatmap.Value != null)
{
// if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier.
- scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY);
+ scale *= 1f - 0.7f * (1f + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY;
}
scaleTarget.Scale = new Vector2(scale);
@@ -145,5 +93,76 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
}
public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad);
+
+ private class DefaultCursor : CompositeDrawable
+ {
+ public DefaultCursor()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ InternalChildren = new Drawable[]
+ {
+ new CircularContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ BorderThickness = size / 6,
+ BorderColour = Color4.White,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Colour = Color4.Pink.Opacity(0.5f),
+ Radius = 5,
+ },
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true,
+ },
+ new CircularContainer
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ BorderThickness = size / 3,
+ BorderColour = Color4.White.Opacity(0.5f),
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true,
+ },
+ },
+ },
+ new CircularContainer
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Scale = new Vector2(0.1f),
+ Masking = true,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.White,
+ },
+ },
+ },
+ }
+ }
+ };
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
index 893c7875fa..a944ff88c6 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
@@ -6,9 +6,12 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.UI;
+using osu.Game.Skinning;
+using osuTK;
namespace osu.Game.Rulesets.Osu.UI.Cursor
{
@@ -22,17 +25,14 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private readonly Bindable showTrail = new Bindable(true);
- private readonly CursorTrail cursorTrail;
+ private readonly Drawable cursorTrail;
public OsuCursorContainer()
{
InternalChild = fadeContainer = new Container
{
RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- cursorTrail = new CursorTrail { Depth = 1 }
- }
+ Child = cursorTrail = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling)
};
}
@@ -98,5 +98,15 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint);
ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint);
}
+
+ private class DefaultCursorTrail : CursorTrail
+ {
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ Texture = textures.Get(@"Cursor/cursortrail");
+ Scale = new Vector2(1 / Texture.ScaleAdjust);
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
index d185d7d4c9..aa61fb6922 100644
--- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.UI
{
protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config;
- public DrawableOsuRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableOsuRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index 9037faf606..ea7eee8bb8 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -42,9 +42,8 @@ namespace osu.Game.Rulesets.Osu.UI
},
// Todo: This should not exist, but currently helps to reduce LOH allocations due to unbinding skin source events on judgement disposal
// Todo: Remove when hitobjects are properly pooled
- new LocalSkinOverrideContainer(null)
+ new SkinProvidingContainer(null)
{
- RelativeSizeAxes = Axes.Both,
Child = HitObjectContainer,
},
approachCircles = new ApproachCircleProxyContainer
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs
index e28ff5f460..9c8be868b0 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs
@@ -13,12 +13,15 @@ namespace osu.Game.Rulesets.Osu.UI
protected override Container Content => content;
private readonly Container content;
+ private const float playfield_size_adjust = 0.8f;
+
public OsuPlayfieldAdjustmentContainer()
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
- Size = new Vector2(0.75f);
+ // Calculated from osu!stable as 512 (default gamefield size) / 640 (default window size)
+ Size = new Vector2(playfield_size_adjust);
InternalChild = new Container
{
@@ -40,7 +43,19 @@ namespace osu.Game.Rulesets.Osu.UI
{
base.Update();
+ // The following calculation results in a constant of 1.6 when OsuPlayfieldAdjustmentContainer
+ // is consuming the full game_size. This matches the osu-stable "magic ratio".
+ //
+ // game_size = DrawSizePreservingFillContainer.TargetSize = new Vector2(1024, 768)
+ //
+ // Parent is a 4:3 aspect enforced, using height as the constricting dimension
+ // Parent.ChildSize.X = min(game_size.X, game_size.Y * (4 / 3)) * playfield_size_adjust
+ // Parent.ChildSize.X = 819.2
+ //
+ // Scale = 819.2 / 512
+ // Scale = 1.6
Scale = new Vector2(Parent.ChildSize.X / OsuPlayfield.BASE_SIZE.X);
+ // Size = 0.625
Size = Vector2.Divide(Vector2.One, Scale);
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj
index d2817b743c..3e46bb89af 100644
--- a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj
@@ -13,14 +13,6 @@
-
- libbass.a
- PreserveNewest
-
-
- libbass_fx.a
- PreserveNewest
-
Linker.xml
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index 68ae7544c2..6c1882b4e2 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -20,10 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
[NonParallelizable]
[TestCase("basic")]
[TestCase("slider-generating-drumroll")]
- public new void Test(string name)
- {
- base.Test(name);
- }
+ public void Test(string name) => base.Test(name);
protected override IEnumerable CreateConvertValue(HitObject hitObject)
{
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs
new file mode 100644
index 0000000000..f27e329e8e
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs
@@ -0,0 +1,74 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Taiko.Judgements;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ public class TestSceneSwellJudgements : PlayerTestScene
+ {
+ protected new TestPlayer Player => (TestPlayer)base.Player;
+
+ public TestSceneSwellJudgements()
+ : base(new TaikoRuleset())
+ {
+ }
+
+ [Test]
+ public void TestZeroTickTimeOffsets()
+ {
+ AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted);
+ AddAssert("all tick offsets are 0", () => Player.Results.Where(r => r.Judgement is TaikoSwellTickJudgement).All(r => r.TimeOffset == 0));
+ }
+
+ protected override bool Autoplay => true;
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
+ {
+ var beatmap = new Beatmap
+ {
+ BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo },
+ HitObjects =
+ {
+ new Swell
+ {
+ StartTime = 1000,
+ Duration = 1000,
+ }
+ }
+ };
+
+ return beatmap;
+ }
+
+ protected override Player CreatePlayer(Ruleset ruleset) => new TestPlayer();
+
+ protected class TestPlayer : Player
+ {
+ public readonly List Results = new List();
+
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+
+ public TestPlayer()
+ : base(false, false)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ ScoreProcessor.NewJudgement += r => Results.Add(r);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
index 6f9856df83..3aa461e779 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
@@ -14,13 +14,13 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Tests.Visual;
using osuTK;
-using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Tests
{
@@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) };
- ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult });
+ ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
}
private void addStrongHitJudgement(bool kiai)
@@ -159,13 +159,13 @@ namespace osu.Game.Rulesets.Taiko.Tests
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) };
- ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult });
- ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new TaikoStrongJudgement()) { Type = HitResult.Great });
+ ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
+ ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great });
}
private void addMissJudgement()
{
- ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new TaikoJudgement()) { Type = HitResult.Miss });
+ ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss });
}
private void addBarLine(bool major, double delay = scroll_time)
@@ -247,10 +247,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
: base(hitObject)
{
}
-
- protected override void UpdateState(ArmedState state)
- {
- }
}
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index 5510c3a9d9..d2a0a8fa6f 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index c8f3e18911..32d49ea39c 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -8,10 +8,12 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Difficulty.Skills;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.Scoring;
namespace osu.Game.Rulesets.Taiko.Difficulty
{
@@ -29,12 +31,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (beatmap.HitObjects.Count == 0)
return new TaikoDifficultyAttributes { Mods = mods, Skills = skills };
+ HitWindows hitWindows = new TaikoHitWindows();
+ hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+
return new TaikoDifficultyAttributes
{
StarRating = skills.Single().DifficultyValue() * star_scaling_factor,
Mods = mods,
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
- GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate,
+ GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate,
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
Skills = skills
};
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
index f8909fb98c..1a5a797f28 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
@@ -3,6 +3,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Objects;
using osuTK;
using osu.Game.Rulesets.Objects.Drawables;
@@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
///
/// A line that scrolls alongside hit objects in the playfield and visualises control points.
///
- public class DrawableBarLine : DrawableHitObject
+ public class DrawableBarLine : DrawableHitObject
{
///
/// The width of the line tracker.
@@ -53,9 +54,5 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Alpha = 0.75f
});
}
-
- protected override void UpdateState(ArmedState state)
- {
- }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs
index 4d3a1a3f8a..f5b75a781b 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs
@@ -5,6 +5,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osuTK;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
index 9b4df74a61..f4407a7b54 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
@@ -88,13 +88,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = HitResult.Miss);
}
- protected override void UpdateState(ArmedState state)
+ protected override void UpdateStateTransforms(ArmedState state)
{
switch (state)
{
case ArmedState.Hit:
case ArmedState.Miss:
- this.FadeOut(100).Expire();
+ this.Delay(HitObject.Duration).FadeOut(100).Expire();
break;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
index 9259c693d9..cef9a53deb 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = HitResult.Great);
}
- protected override void UpdateState(ArmedState state)
+ protected override void UpdateStateTransforms(ArmedState state)
{
switch (state)
{
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
index 34ae7db984..676ecd5a0b 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Diagnostics;
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
@@ -34,6 +35,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
+ Debug.Assert(HitObject.HitWindows != null);
+
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
@@ -92,56 +95,44 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Size = BaseSize * Parent.RelativeChildSize;
}
- protected override void UpdateState(ArmedState state)
+ protected override void UpdateStateTransforms(ArmedState state)
{
- // TODO: update to use new state management.
- var circlePiece = MainPiece as CirclePiece;
- circlePiece?.FlashBox.FinishTransforms();
+ Debug.Assert(HitObject.HitWindows != null);
- var offset = !AllJudged ? 0 : Time.Current - HitObject.StartTime;
-
- using (BeginDelayedSequence(HitObject.StartTime - Time.Current + offset, true))
+ switch (state)
{
- switch (State.Value)
- {
- case ArmedState.Idle:
- validActionPressed = false;
+ case ArmedState.Idle:
+ validActionPressed = false;
- UnproxyContent();
- this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
- break;
+ UnproxyContent();
+ this.Delay(HitObject.HitWindows.WindowFor(HitResult.Miss)).Expire();
+ break;
- case ArmedState.Miss:
- this.FadeOut(100)
- .Expire();
- break;
+ case ArmedState.Miss:
+ this.FadeOut(100)
+ .Expire();
+ break;
- case ArmedState.Hit:
- // If we're far enough away from the left stage, we should bring outselves in front of it
- ProxyContent();
+ case ArmedState.Hit:
+ // If we're far enough away from the left stage, we should bring outselves in front of it
+ ProxyContent();
- var flash = circlePiece?.FlashBox;
+ var flash = (MainPiece as CirclePiece)?.FlashBox;
+ flash?.FadeTo(0.9f).FadeOut(300);
- if (flash != null)
- {
- flash.FadeTo(0.9f);
- flash.FadeOut(300);
- }
+ const float gravity_time = 300;
+ const float gravity_travel_height = 200;
- const float gravity_time = 300;
- const float gravity_travel_height = 200;
+ this.ScaleTo(0.8f, gravity_time * 2, Easing.OutQuad);
- this.ScaleTo(0.8f, gravity_time * 2, Easing.OutQuad);
+ this.MoveToY(-gravity_travel_height, gravity_time, Easing.Out)
+ .Then()
+ .MoveToY(gravity_travel_height * 2, gravity_time * 2, Easing.In);
- this.MoveToY(-gravity_travel_height, gravity_time, Easing.Out)
- .Then()
- .MoveToY(gravity_travel_height * 2, gravity_time * 2, Easing.In);
+ this.FadeOut(800)
+ .Expire();
- this.FadeOut(800)
- .Expire();
-
- break;
- }
+ break;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs
index 98a2e8a721..108e42eea5 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs
@@ -18,9 +18,5 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
MainObject = mainObject;
}
-
- protected override void UpdateState(ArmedState state)
- {
- }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
index 5ec9dc61e2..094ad1230f 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
@@ -25,6 +25,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private const float target_ring_scale = 5f;
private const float inner_ring_alpha = 0.65f;
+ ///
+ /// Offset away from the start time of the swell at which the ring starts appearing.
+ ///
+ private const double ring_appear_offset = 100;
+
private readonly List ticks = new List();
private readonly Container bodyContainer;
@@ -51,7 +56,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Origin = Anchor.Centre,
Alpha = 0,
RelativeSizeAxes = Axes.Both,
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
Masking = true,
Children = new[]
{
@@ -70,7 +75,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderThickness = target_ring_thick_border,
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
Children = new Drawable[]
{
new Box
@@ -179,26 +184,34 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
}
}
- protected override void UpdateState(ArmedState state)
+ protected override void UpdateInitialTransforms()
{
- const float preempt = 100;
- const float out_transition_time = 300;
+ base.UpdateInitialTransforms();
+
+ using (BeginAbsoluteSequence(HitObject.StartTime - ring_appear_offset, true))
+ targetRing.ScaleTo(target_ring_scale, 400, Easing.OutQuint);
+ }
+
+ protected override void UpdateStateTransforms(ArmedState state)
+ {
+ const double transition_duration = 300;
switch (state)
{
case ArmedState.Idle:
- UnproxyContent();
expandingRing.FadeTo(0);
- using (BeginAbsoluteSequence(HitObject.StartTime - preempt, true))
- targetRing.ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint);
break;
case ArmedState.Miss:
case ArmedState.Hit:
- this.FadeOut(out_transition_time, Easing.Out);
- bodyContainer.ScaleTo(1.4f, out_transition_time);
+ using (BeginAbsoluteSequence(Time.Current, true))
+ {
+ this.FadeOut(transition_duration, Easing.Out);
+ bodyContainer.ScaleTo(1.4f, transition_duration);
+
+ Expire();
+ }
- Expire();
break;
}
}
@@ -212,9 +225,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
// Make the swell stop at the hit target
X = Math.Max(0, X);
- double t = Math.Min(HitObject.StartTime, Time.Current);
- if (t == HitObject.StartTime)
+ if (Time.Current >= HitObject.StartTime - ring_appear_offset)
ProxyContent();
+ else
+ UnproxyContent();
}
private bool? lastWasCentre;
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
index 41a8fd9a75..ce875ebba8 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
@@ -1,7 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Rulesets.Objects.Drawables;
+using osu.Framework.Graphics;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
@@ -15,13 +15,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
}
- public void TriggerResult(HitResult type) => ApplyResult(r => r.Type = type);
+ protected override void UpdateInitialTransforms() => this.FadeOut();
- protected override void CheckForResult(bool userTriggered, double timeOffset)
+ public void TriggerResult(HitResult type)
{
+ HitObject.StartTime = Time.Current;
+ ApplyResult(r => r.Type = type);
}
- protected override void UpdateState(ArmedState state)
+ protected override void CheckForResult(bool userTriggered, double timeOffset)
{
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index b46738c69a..423f65b2d3 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -78,10 +78,29 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public abstract bool OnPressed(TaikoAction action);
public virtual bool OnReleased(TaikoAction action) => false;
+ public override double LifetimeStart
+ {
+ get => base.LifetimeStart;
+ set
+ {
+ base.LifetimeStart = value;
+ proxiedContent.LifetimeStart = value;
+ }
+ }
+
+ public override double LifetimeEnd
+ {
+ get => base.LifetimeEnd;
+ set
+ {
+ base.LifetimeEnd = value;
+ proxiedContent.LifetimeEnd = value;
+ }
+ }
+
private class ProxiedContentContainer : Container
{
- public override double LifetimeStart => Parent?.LifetimeStart ?? base.LifetimeStart;
- public override double LifetimeEnd => Parent?.LifetimeEnd ?? base.LifetimeEnd;
+ public override bool RemoveWhenNotAlive => false;
}
}
@@ -121,12 +140,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
}
}
- protected override bool UseTransformStateManagement => false;
-
// Normal and clap samples are handled by the drum
protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
- protected override string SampleNamespace => "Taiko";
+ protected override string SampleNamespace => "taiko";
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
index b7db819717..d9c0664ecd 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
@@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
Alpha = 0,
AlwaysPresent = true
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
index 1d25735fe3..4e02c76a8b 100644
--- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
@@ -6,6 +6,7 @@ using System;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
@@ -86,5 +87,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
}
public override Judgement CreateJudgement() => new TaikoDrumRollJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs
index 8448036f76..c466ca7c8a 100644
--- a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
@@ -25,5 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
public double HitWindow => TickSpacing / 2;
public override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs
index 2a03c23934..d660149528 100644
--- a/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
@@ -9,5 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
public class StrongHitObject : TaikoHitObject
{
public override Judgement CreateJudgement() => new TaikoStrongJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs
index befa728570..f96c033dce 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs
@@ -4,6 +4,7 @@
using System;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
@@ -33,5 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
}
public override Judgement CreateJudgement() => new TaikoSwellJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs
index c2ae784b2a..68212e8f12 100644
--- a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
@@ -9,5 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
public class SwellTick : TaikoHitObject
{
public override Judgement CreateJudgement() => new TaikoSwellTickJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
index 3592d73004..6f4fbd0651 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
@@ -4,7 +4,9 @@
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
+using osu.Game.Rulesets.Taiko.Scoring;
namespace osu.Game.Rulesets.Taiko.Objects
{
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs
deleted file mode 100644
index f232919cbf..0000000000
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Scoring;
-
-namespace osu.Game.Rulesets.Taiko.Objects
-{
- public class TaikoHitWindows : HitWindows
- {
- private static readonly IReadOnlyDictionary base_ranges = new Dictionary
- {
- { HitResult.Great, (100, 70, 40) },
- { HitResult.Good, (240, 160, 100) },
- { HitResult.Miss, (270, 190, 140) },
- };
-
- public override bool IsHitResultAllowed(HitResult result)
- {
- switch (result)
- {
- case HitResult.Great:
- case HitResult.Good:
- case HitResult.Miss:
- return true;
-
- default:
- return false;
- }
- }
-
- public override void SetDifficulty(double difficulty)
- {
- Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
- Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
- Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
index 422ba748e3..299679b2c1 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
@@ -10,6 +10,7 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Taiko.Beatmaps;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Taiko.Replays
{
@@ -113,7 +114,13 @@ namespace osu.Game.Rulesets.Taiko.Replays
else
throw new InvalidOperationException("Unknown hit object type.");
- Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY));
+ var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button
+
+ bool canDelayKeyUp = nextHitObject == null || nextHitObject.StartTime > endTime + KEY_UP_DELAY;
+
+ double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject.StartTime - endTime) * 0.9;
+
+ Frames.Add(new TaikoReplayFrame(endTime + calculatedDelay));
if (i < Beatmap.HitObjects.Count - 1)
{
@@ -127,5 +134,24 @@ namespace osu.Game.Rulesets.Taiko.Replays
return Replay;
}
+
+ protected override HitObject GetNextObject(int currentIndex)
+ {
+ Type desiredType = Beatmap.HitObjects[currentIndex].GetType();
+
+ for (int i = currentIndex + 1; i < Beatmap.HitObjects.Count; i++)
+ {
+ var currentObj = Beatmap.HitObjects[i];
+
+ if (currentObj.GetType() == desiredType ||
+ // Un-press all keys before a DrumRoll or Swell
+ currentObj is DrumRoll || currentObj is Swell)
+ {
+ return Beatmap.HitObjects[i];
+ }
+ }
+
+ return null;
+ }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs
new file mode 100644
index 0000000000..9d273392ff
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs
@@ -0,0 +1,32 @@
+// 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.Scoring;
+
+namespace osu.Game.Rulesets.Taiko.Scoring
+{
+ public class TaikoHitWindows : HitWindows
+ {
+ private static readonly DifficultyRange[] taiko_ranges =
+ {
+ new DifficultyRange(HitResult.Great, 50, 35, 20),
+ new DifficultyRange(HitResult.Good, 120, 80, 50),
+ new DifficultyRange(HitResult.Miss, 135, 95, 70),
+ };
+
+ public override bool IsHitResultAllowed(HitResult result)
+ {
+ switch (result)
+ {
+ case HitResult.Great:
+ case HitResult.Good:
+ case HitResult.Miss:
+ return true;
+ }
+
+ return false;
+ }
+
+ protected override DifficultyRange[] GetRanges() => taiko_ranges;
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
index 68ddf2db19..75a27ff639 100644
--- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
@@ -3,7 +3,6 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.UI;
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index a67004e9c7..7fdb823388 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -12,7 +12,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Replays.Types;
-using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Replays;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Difficulty;
@@ -24,9 +23,11 @@ namespace osu.Game.Rulesets.Taiko
{
public class TaikoRuleset : Ruleset
{
- public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableTaikoRuleset(this, beatmap, mods);
+ public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableTaikoRuleset(this, beatmap, mods);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
+ public const string SHORT_NAME = "taiko";
+
public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
{
new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre),
@@ -107,7 +108,7 @@ namespace osu.Game.Rulesets.Taiko
case ModType.Fun:
return new Mod[]
{
- new MultiMod(new ModWindUp(), new ModWindDown())
+ new MultiMod(new ModWindUp(), new ModWindDown())
};
default:
@@ -117,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko
public override string Description => "osu!taiko";
- public override string ShortName => "taiko";
+ public override string ShortName => SHORT_NAME;
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetTaiko };
diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs
new file mode 100644
index 0000000000..e6e4bc0dd7
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Taiko
+{
+ public class TaikoSkinComponent : GameplaySkinComponent
+ {
+ public TaikoSkinComponent(TaikoSkinComponents component)
+ : base(component)
+ {
+ }
+
+ protected override string RulesetPrefix => TaikoRuleset.SHORT_NAME;
+
+ protected override string ComponentName => Component.ToString().ToLower();
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
new file mode 100644
index 0000000000..04aca534c6
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
@@ -0,0 +1,9 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Taiko
+{
+ public enum TaikoSkinComponents
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
index ec3a56e9c7..5caa9e4626 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
@@ -5,19 +5,18 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Taiko.Replays;
-using System.Linq;
using osu.Framework.Input;
using osu.Game.Configuration;
using osu.Game.Input.Handlers;
using osu.Game.Replays;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Taiko.UI
@@ -28,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.UI
protected override bool UserScrollSpeedAdjustment => false;
- public DrawableTaikoRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableTaikoRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
Direction.Value = ScrollingDirection.Left;
@@ -38,49 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI
[BackgroundDependencyLoader]
private void load()
{
- loadBarLines();
- }
-
- private void loadBarLines()
- {
- TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1];
- double lastHitTime = 1 + ((lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime);
-
- var timingPoints = Beatmap.ControlPointInfo.TimingPoints.ToList();
-
- if (timingPoints.Count == 0)
- return;
-
- int currentIndex = 0;
- int currentBeat = 0;
- double time = timingPoints[currentIndex].Time;
-
- while (time <= lastHitTime)
- {
- int nextIndex = currentIndex + 1;
-
- if (nextIndex < timingPoints.Count && time > timingPoints[nextIndex].Time)
- {
- currentIndex = nextIndex;
- time = timingPoints[currentIndex].Time;
- currentBeat = 0;
- }
-
- var currentPoint = timingPoints[currentIndex];
-
- var barLine = new BarLine
- {
- StartTime = time,
- };
-
- barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty);
-
- bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0;
- Playfield.Add(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine));
-
- time += currentPoint.BeatLength * (int)currentPoint.TimeSignature;
- currentBeat++;
- }
+ new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar)));
}
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this);
diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
index aa37ff7008..5234ae1f69 100644
--- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
+++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
@@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Taiko.UI
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Alpha = 0,
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
},
centre = new Sprite
{
@@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.Taiko.UI
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.7f),
Alpha = 0,
- Blending = BlendingMode.Additive
+ Blending = BlendingParameters.Additive
}
};
}
@@ -132,10 +132,10 @@ namespace osu.Game.Rulesets.Taiko.UI
[BackgroundDependencyLoader]
private void load(TextureStore textures, OsuColour colours)
{
- rim.Texture = textures.Get(@"Play/Taiko/taiko-drum-outer");
- rimHit.Texture = textures.Get(@"Play/Taiko/taiko-drum-outer-hit");
- centre.Texture = textures.Get(@"Play/Taiko/taiko-drum-inner");
- centreHit.Texture = textures.Get(@"Play/Taiko/taiko-drum-inner-hit");
+ rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer");
+ rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit");
+ centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner");
+ centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit");
rimHit.Colour = colours.Blue;
centreHit.Colour = colours.Pink;
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index 7427a3235d..a10f70a344 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -44,9 +44,8 @@ namespace osu.Game.Rulesets.Taiko.UI
private readonly JudgementContainer judgementContainer;
internal readonly HitTarget HitTarget;
- private readonly Container topLevelHitContainer;
-
- private readonly Container barlineContainer;
+ private readonly ProxyContainer topLevelHitContainer;
+ private readonly ProxyContainer barlineContainer;
private readonly Container overlayBackgroundContainer;
private readonly Container backgroundContainer;
@@ -97,7 +96,7 @@ namespace osu.Game.Rulesets.Taiko.UI
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
},
HitTarget = new HitTarget
{
@@ -108,7 +107,7 @@ namespace osu.Game.Rulesets.Taiko.UI
}
}
},
- barlineContainer = new Container
+ barlineContainer = new ProxyContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }
@@ -127,14 +126,14 @@ namespace osu.Game.Rulesets.Taiko.UI
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Margin = new MarginPadding { Left = HIT_TARGET_OFFSET },
- Blending = BlendingMode.Additive
+ Blending = BlendingParameters.Additive
},
judgementContainer = new JudgementContainer
{
Name = "Judgements",
RelativeSizeAxes = Axes.Y,
Margin = new MarginPadding { Left = HIT_TARGET_OFFSET },
- Blending = BlendingMode.Additive
+ Blending = BlendingParameters.Additive
},
}
},
@@ -183,7 +182,7 @@ namespace osu.Game.Rulesets.Taiko.UI
}
}
},
- topLevelHitContainer = new Container
+ topLevelHitContainer = new ProxyContainer
{
Name = "Top level hit objects",
RelativeSizeAxes = Axes.Both,
@@ -256,5 +255,15 @@ namespace osu.Game.Rulesets.Taiko.UI
break;
}
}
+
+ private class ProxyContainer : LifetimeManagementContainer
+ {
+ public new MarginPadding Padding
+ {
+ set => base.Padding = value;
+ }
+
+ public void Add(Drawable proxy) => AddInternal(proxy);
+ }
}
}
diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj
index ea5ab699f3..5c0713b895 100644
--- a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj
+++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj
@@ -13,14 +13,6 @@
-
- libbass.a
- PreserveNewest
-
-
- libbass_fx.a
- PreserveNewest
-
Linker.xml
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index d087251e7e..535320530d 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -482,5 +482,17 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(hitObjects[0].Samples[0].Bank, hitObjects[0].Samples[1].Bank);
}
}
+
+ [Test]
+ public void TestInvalidEventStillPasses()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+
+ using (var badResStream = TestResources.OpenResource("invalid-events.osu"))
+ using (var badStream = new StreamReader(badResStream))
+ {
+ Assert.DoesNotThrow(() => decoder.Decode(badStream));
+ }
+ }
}
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
index 971518909d..953763c95d 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
@@ -58,7 +58,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite));
int animationCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardAnimation));
- int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSample));
+ int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSampleInfo));
Assert.AreEqual(15, spriteCount);
Assert.AreEqual(1, animationCount);
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
index a725c58462..4859abbb8e 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
@@ -9,8 +9,8 @@ using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO.Serialization;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Resources;
using osuTK;
diff --git a/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs
new file mode 100644
index 0000000000..9fba0f1668
--- /dev/null
+++ b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs
@@ -0,0 +1,116 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Tests.Beatmaps
+{
+ [TestFixture]
+ public class SliderEventGenerationTest
+ {
+ private const double start_time = 0;
+ private const double span_duration = 1000;
+
+ [Test]
+ public void TestSingleSpan()
+ {
+ var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray();
+
+ Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
+ Assert.That(events[0].Time, Is.EqualTo(start_time));
+
+ Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[1].Time, Is.EqualTo(span_duration / 2));
+
+ Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tail));
+ Assert.That(events[3].Time, Is.EqualTo(span_duration));
+ }
+
+ [Test]
+ public void TestRepeat()
+ {
+ var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray();
+
+ Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
+ Assert.That(events[0].Time, Is.EqualTo(start_time));
+
+ Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[1].Time, Is.EqualTo(span_duration / 2));
+
+ Assert.That(events[2].Type, Is.EqualTo(SliderEventType.Repeat));
+ Assert.That(events[2].Time, Is.EqualTo(span_duration));
+
+ Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[3].Time, Is.EqualTo(span_duration + span_duration / 2));
+
+ Assert.That(events[5].Type, Is.EqualTo(SliderEventType.Tail));
+ Assert.That(events[5].Time, Is.EqualTo(2 * span_duration));
+ }
+
+ [Test]
+ public void TestNonEvenTicks()
+ {
+ var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray();
+
+ Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
+ Assert.That(events[0].Time, Is.EqualTo(start_time));
+
+ Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[1].Time, Is.EqualTo(300));
+
+ Assert.That(events[2].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[2].Time, Is.EqualTo(600));
+
+ Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[3].Time, Is.EqualTo(900));
+
+ Assert.That(events[4].Type, Is.EqualTo(SliderEventType.Repeat));
+ Assert.That(events[4].Time, Is.EqualTo(span_duration));
+
+ Assert.That(events[5].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[5].Time, Is.EqualTo(1100));
+
+ Assert.That(events[6].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[6].Time, Is.EqualTo(1400));
+
+ Assert.That(events[7].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[7].Time, Is.EqualTo(1700));
+
+ Assert.That(events[9].Type, Is.EqualTo(SliderEventType.Tail));
+ Assert.That(events[9].Time, Is.EqualTo(2 * span_duration));
+ }
+
+ [Test]
+ public void TestLegacyLastTickOffset()
+ {
+ var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray();
+
+ Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick));
+ Assert.That(events[2].Time, Is.EqualTo(900));
+ }
+
+ [Test]
+ public void TestMinimumTickDistance()
+ {
+ const double velocity = 5;
+ const double min_distance = velocity * 10;
+
+ var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray();
+
+ Assert.Multiple(() =>
+ {
+ int tickIndex = -1;
+
+ while (++tickIndex < events.Length)
+ {
+ if (events[tickIndex].Type != SliderEventType.Tick)
+ continue;
+
+ Assert.That(events[tickIndex].Time, Is.LessThan(span_duration - min_distance).Or.GreaterThan(span_duration + min_distance));
+ }
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs
index 0d6ed67767..9b4a90e9a9 100644
--- a/osu.Game.Tests/Chat/MessageFormatterTests.cs
+++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs
@@ -119,6 +119,53 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(11, result.Links[0].Length);
}
+ [Test]
+ public void TestOldFormatLinkWithBalancedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (tricky (one))[https://osu.ppy.sh]!" });
+
+ Assert.AreEqual("This is a tricky (one)!", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(12, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestOldFormatLinkWithEscapedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is (another loose bracket \\))[https://osu.ppy.sh]." });
+
+ Assert.AreEqual("This is another loose bracket ).", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(8, result.Links[0].Index);
+ Assert.AreEqual(23, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestOldFormatWithBackslashes()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This link (should end with a backslash \\)[https://osu.ppy.sh]." });
+ Assert.AreEqual("This link should end with a backslash \\.", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(29, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestOldFormatLinkWithEscapedAndBalancedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (\\)super\\(\\( tricky (one))[https://osu.ppy.sh]!" });
+
+ Assert.AreEqual("This is a )super(( tricky (one)!", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(21, result.Links[0].Length);
+ }
+
[Test]
public void TestNewFormatLink()
{
@@ -131,6 +178,42 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(11, result.Links[0].Length);
}
+ [Test]
+ public void TestNewFormatLinkWithEscapedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh nasty link with escaped brackets: \\] and \\[]" });
+
+ Assert.AreEqual("This is a nasty link with escaped brackets: ] and [", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(41, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestNewFormatLinkWithBackslashesInside()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh link \\ with \\ backslashes \\]" });
+
+ Assert.AreEqual("This is a link \\ with \\ backslashes \\", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(27, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestNewFormatLinkWithEscapedAndBalancedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh [link [with \\] too many brackets \\[ ]]]" });
+
+ Assert.AreEqual("This is a [link [with ] too many brackets [ ]]", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(36, result.Links[0].Length);
+ }
+
[Test]
public void TestMarkdownFormatLink()
{
@@ -143,6 +226,53 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(11, result.Links[0].Length);
}
+ [Test]
+ public void TestMarkdownFormatLinkWithBalancedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [tricky [one]](https://osu.ppy.sh)!" });
+
+ Assert.AreEqual("This is a tricky [one]!", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(12, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestMarkdownFormatLinkWithEscapedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is [another loose bracket \\]](https://osu.ppy.sh)." });
+
+ Assert.AreEqual("This is another loose bracket ].", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(8, result.Links[0].Index);
+ Assert.AreEqual(23, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestMarkdownFormatWithBackslashes()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This link [should end with a backslash \\](https://osu.ppy.sh)." });
+ Assert.AreEqual("This link should end with a backslash \\.", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(29, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestMarkdownFormatLinkWithEscapedAndBalancedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [\\]super\\[\\[ tricky [one]](https://osu.ppy.sh)!" });
+
+ Assert.AreEqual("This is a ]super[[ tricky [one]!", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(21, result.Links[0].Length);
+ }
+
[Test]
public void TestChannelLink()
{
diff --git a/osu.Game.Tests/Resources/invalid-events.osu b/osu.Game.Tests/Resources/invalid-events.osu
new file mode 100644
index 0000000000..df86b26dba
--- /dev/null
+++ b/osu.Game.Tests/Resources/invalid-events.osu
@@ -0,0 +1,14 @@
+osu file format v14
+
+[Events]
+bad,event,this,should,fail
+//Background and Video events
+0,0,"machinetop_background.jpg",0,0
+//Break Periods
+2,122474,140135
+//Storyboard Layer 0 (Background)
+this,is,also,bad
+//Storyboard Layer 1 (Fail)
+//Storyboard Layer 2 (Pass)
+//Storyboard Layer 3 (Foreground)
+//Storyboard Sound Samples
diff --git a/osu.Game.Tests/Resources/skin.ini b/osu.Game.Tests/Resources/skin.ini
index 0e5737b4ea..7f7f0b32a6 100644
--- a/osu.Game.Tests/Resources/skin.ini
+++ b/osu.Game.Tests/Resources/skin.ini
@@ -1,5 +1,6 @@
[General]
Name: test skin
+TestLookup: TestValue
[Colours]
Combo1 : 142,199,255
diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
index 24ef9e4535..8bd846518b 100644
--- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
+++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
@@ -41,5 +41,20 @@ namespace osu.Game.Tests.Skins
Assert.AreEqual(expectedColors[i], comboColors[i]);
}
}
+
+ [Test]
+ public void TestDecodeGeneral()
+ {
+ var decoder = new LegacySkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("skin.ini"))
+ using (var stream = new StreamReader(resStream))
+ {
+ var config = decoder.Decode(stream);
+
+ Assert.AreEqual("test skin", config.SkinInfo.Name);
+ Assert.AreEqual("TestValue", config.ConfigDictionary["TestLookup"]);
+ }
+ }
}
}
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
new file mode 100644
index 0000000000..bbcc4140a9
--- /dev/null
+++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
@@ -0,0 +1,156 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Skins
+{
+ [TestFixture]
+ public class TestSceneSkinConfigurationLookup : OsuTestScene
+ {
+ private LegacySkin source1;
+ private LegacySkin source2;
+ private SkinRequester requester;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ Add(new SkinProvidingContainer(source1 = new SkinSource())
+ .WithChild(new SkinProvidingContainer(source2 = new SkinSource())
+ .WithChild(requester = new SkinRequester())));
+ });
+
+ [Test]
+ public void TestBasicLookup()
+ {
+ AddStep("Add config values", () =>
+ {
+ source1.Configuration.ConfigDictionary["Lookup"] = "source1";
+ source2.Configuration.ConfigDictionary["Lookup"] = "source2";
+ });
+
+ AddAssert("Check lookup finds source2", () => requester.GetConfig("Lookup")?.Value == "source2");
+ }
+
+ [Test]
+ public void TestFloatLookup()
+ {
+ AddStep("Add config values", () => source1.Configuration.ConfigDictionary["FloatTest"] = "1.1");
+ AddAssert("Check float parse lookup", () => requester.GetConfig("FloatTest")?.Value == 1.1f);
+ }
+
+ [Test]
+ public void TestBoolLookup()
+ {
+ AddStep("Add config values", () => source1.Configuration.ConfigDictionary["BoolTest"] = "1");
+ AddAssert("Check bool parse lookup", () => requester.GetConfig("BoolTest")?.Value == true);
+ }
+
+ [Test]
+ public void TestEnumLookup()
+ {
+ AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Test"] = "Test2");
+ AddAssert("Check enum parse lookup", () => requester.GetConfig(LookupType.Test)?.Value == ValueType.Test2);
+ }
+
+ [Test]
+ public void TestLookupFailure()
+ {
+ AddAssert("Check lookup failure", () => requester.GetConfig("Lookup") == null);
+ }
+
+ [Test]
+ public void TestLookupNull()
+ {
+ AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Lookup"] = null);
+
+ AddAssert("Check lookup null", () =>
+ {
+ var bindable = requester.GetConfig("Lookup");
+ return bindable != null && bindable.Value == null;
+ });
+ }
+
+ [Test]
+ public void TestColourLookup()
+ {
+ AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red);
+ AddAssert("Check colour lookup", () => requester.GetConfig(new SkinCustomColourLookup("Lookup"))?.Value == Color4.Red);
+ }
+
+ [Test]
+ public void TestGlobalLookup()
+ {
+ AddAssert("Check combo colours", () => requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0);
+ }
+
+ [Test]
+ public void TestWrongColourType()
+ {
+ AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red);
+
+ AddAssert("perform incorrect lookup", () =>
+ {
+ try
+ {
+ requester.GetConfig(new SkinCustomColourLookup("Lookup"));
+ return false;
+ }
+ catch
+ {
+ return true;
+ }
+ });
+ }
+
+ public enum LookupType
+ {
+ Test
+ }
+
+ public enum ValueType
+ {
+ Test1,
+ Test2,
+ Test3
+ }
+
+ public class SkinSource : LegacySkin
+ {
+ public SkinSource()
+ : base(new SkinInfo(), null, null, string.Empty)
+ {
+ }
+ }
+
+ public class SkinRequester : Drawable, ISkin
+ {
+ private ISkinSource skin;
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ this.skin = skin;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component);
+
+ public Texture GetTexture(string componentName) => skin.GetTexture(componentName);
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
+
+ public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs
index dc4ceed59e..3061a3a542 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs
@@ -17,7 +17,6 @@ using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
-using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@@ -51,26 +50,14 @@ namespace osu.Game.Tests.Visual.Background
private DummySongSelect songSelect;
private TestPlayerLoader playerLoader;
private TestPlayer player;
- private DatabaseContextFactory factory;
private BeatmapManager manager;
private RulesetStore rulesets;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- factory = new DatabaseContextFactory(LocalStorage);
- factory.ResetDatabase();
-
- using (var usage = factory.Get())
- usage.Migrate();
-
- factory.ResetDatabase();
-
- using (var usage = factory.Get())
- usage.Migrate();
-
- Dependencies.Cache(rulesets = new RulesetStore(factory));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, audio, host, Beatmap.Default));
+ Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
Dependencies.Cache(new OsuConfigManager(LocalStorage));
manager.Import(TestResources.GetTestBeatmapForImport()).Wait();
@@ -132,14 +119,14 @@ namespace osu.Game.Tests.Visual.Background
{
performFullSetup();
createFakeStoryboard();
- AddStep("Storyboard Enabled", () =>
+ AddStep("Enable Storyboard", () =>
{
player.ReplacesBackground.Value = true;
player.StoryboardEnabled.Value = true;
});
waitForDim();
AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible);
- AddStep("Storyboard Disabled", () =>
+ AddStep("Disable Storyboard", () =>
{
player.ReplacesBackground.Value = false;
player.StoryboardEnabled.Value = false;
@@ -162,22 +149,44 @@ namespace osu.Game.Tests.Visual.Background
}
///
- /// Check if the is properly accepting user-defined visual changes at all.
+ /// Ensure is properly accepting user-defined visual changes for a background.
///
[Test]
- public void DisableUserDimTest()
+ public void DisableUserDimBackgroundTest()
{
performFullSetup();
waitForDim();
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
- AddStep("EnableUserDim disabled", () => songSelect.DimEnabled.Value = false);
+ AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false);
waitForDim();
AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
- AddStep("EnableUserDim enabled", () => songSelect.DimEnabled.Value = true);
+ AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true);
waitForDim();
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
}
+ ///
+ /// Ensure is properly accepting user-defined visual changes for a storyboard.
+ ///
+ [Test]
+ public void DisableUserDimStoryboardTest()
+ {
+ performFullSetup();
+ createFakeStoryboard();
+ AddStep("Enable Storyboard", () =>
+ {
+ player.ReplacesBackground.Value = true;
+ player.StoryboardEnabled.Value = true;
+ });
+ AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true);
+ AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f);
+ waitForDim();
+ AddAssert("Storyboard is invisible", () => !player.IsStoryboardVisible);
+ AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false);
+ waitForDim();
+ AddAssert("Storyboard is visible", () => player.IsStoryboardVisible);
+ }
+
///
/// Check if the visual settings container retains dim and blur when pausing
///
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs
index 7accbe2fa8..0ea73fb3de 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs
@@ -16,15 +16,13 @@ using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
namespace osu.Game.Tests.Visual.Editor
{
[TestFixture]
- [Cached(Type = typeof(IPlacementHandler))]
- public class TestSceneHitObjectComposer : OsuTestScene, IPlacementHandler
+ public class TestSceneHitObjectComposer : OsuTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -39,8 +37,6 @@ namespace osu.Game.Tests.Visual.Editor
typeof(HitCirclePlacementBlueprint),
};
- private HitObjectComposer composer;
-
[BackgroundDependencyLoader]
private void load()
{
@@ -67,15 +63,7 @@ namespace osu.Game.Tests.Visual.Editor
Dependencies.CacheAs(clock);
Dependencies.CacheAs(clock);
- Child = composer = new OsuHitObjectComposer(new OsuRuleset());
+ Child = new OsuHitObjectComposer(new OsuRuleset());
}
-
- public void BeginPlacement(HitObject hitObject)
- {
- }
-
- public void EndPlacement(HitObject hitObject) => composer.Add(hitObject);
-
- public void Delete(HitObject hitObject) => composer.Remove(hitObject);
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs
new file mode 100644
index 0000000000..a934d22b5d
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs
@@ -0,0 +1,119 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Rulesets.Objects;
+using System;
+using System.Collections.Generic;
+using osu.Game.Rulesets.Judgements;
+using osu.Framework.MathUtils;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Rulesets.Catch.Scoring;
+using osu.Game.Rulesets.Mania.Scoring;
+using osu.Game.Rulesets.Osu.Scoring;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Taiko.Scoring;
+using osu.Game.Screens.Play.HUD.HitErrorMeters;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneBarHitErrorMeter : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(HitErrorMeter),
+ };
+
+ private HitErrorMeter meter;
+ private HitErrorMeter meter2;
+ private HitWindows hitWindows;
+
+ public TestSceneBarHitErrorMeter()
+ {
+ recreateDisplay(new OsuHitWindows(), 5);
+
+ AddRepeatStep("New random judgement", () => newJudgement(), 40);
+
+ AddRepeatStep("New max negative", () => newJudgement(-hitWindows.WindowFor(HitResult.Meh)), 20);
+ AddRepeatStep("New max positive", () => newJudgement(hitWindows.WindowFor(HitResult.Meh)), 20);
+ AddStep("New fixed judgement (50ms)", () => newJudgement(50));
+ }
+
+ [Test]
+ public void TestOsu()
+ {
+ AddStep("OD 1", () => recreateDisplay(new OsuHitWindows(), 1));
+ AddStep("OD 10", () => recreateDisplay(new OsuHitWindows(), 10));
+ }
+
+ [Test]
+ public void TestTaiko()
+ {
+ AddStep("OD 1", () => recreateDisplay(new TaikoHitWindows(), 1));
+ AddStep("OD 10", () => recreateDisplay(new TaikoHitWindows(), 10));
+ }
+
+ [Test]
+ public void TestMania()
+ {
+ AddStep("OD 1", () => recreateDisplay(new ManiaHitWindows(), 1));
+ AddStep("OD 10", () => recreateDisplay(new ManiaHitWindows(), 10));
+ }
+
+ [Test]
+ public void TestCatch()
+ {
+ AddStep("OD 1", () => recreateDisplay(new CatchHitWindows(), 1));
+ AddStep("OD 10", () => recreateDisplay(new CatchHitWindows(), 10));
+ }
+
+ private void recreateDisplay(HitWindows hitWindows, float overallDifficulty)
+ {
+ this.hitWindows = hitWindows;
+
+ hitWindows?.SetDifficulty(overallDifficulty);
+
+ Clear();
+
+ Add(new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Direction = FillDirection.Vertical,
+ AutoSizeAxes = Axes.Both,
+ Children = new[]
+ {
+ new SpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" },
+ new SpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Good)}" },
+ new SpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" },
+ }
+ });
+
+ Add(meter = new BarHitErrorMeter(hitWindows, true)
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ });
+
+ Add(meter2 = new BarHitErrorMeter(hitWindows, false)
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ });
+ }
+
+ private void newJudgement(double offset = 0)
+ {
+ var judgement = new JudgementResult(new HitObject(), new Judgement())
+ {
+ TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset,
+ Type = HitResult.Perfect,
+ };
+
+ meter.OnNewJudgement(judgement);
+ meter2.OnNewJudgement(judgement);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs
index 3cd1b8307a..879e15c548 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs
@@ -1,8 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
+using osu.Framework.Timing;
using osu.Game.Beatmaps.Timing;
using osu.Game.Screens.Play;
@@ -11,78 +14,172 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneBreakOverlay : OsuTestScene
{
- private readonly BreakOverlay breakOverlay;
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(BreakOverlay),
+ };
+
+ private readonly TestBreakOverlay breakOverlay;
+
+ private readonly IReadOnlyList testBreaks = new List
+ {
+ new BreakPeriod
+ {
+ StartTime = 1000,
+ EndTime = 5000,
+ },
+ new BreakPeriod
+ {
+ StartTime = 6000,
+ EndTime = 13500,
+ },
+ };
public TestSceneBreakOverlay()
{
- Child = breakOverlay = new BreakOverlay(true);
-
- AddStep("2s break", () => startBreak(2000));
- AddStep("5s break", () => startBreak(5000));
- AddStep("10s break", () => startBreak(10000));
- AddStep("15s break", () => startBreak(15000));
- AddStep("2s, 2s", startMultipleBreaks);
- AddStep("0.5s, 0.7s, 1s, 2s", startAnotherMultipleBreaks);
+ Add(breakOverlay = new TestBreakOverlay(true));
}
- private void startBreak(double duration)
+ [Test]
+ public void TestShowBreaks()
{
- breakOverlay.Breaks = new List
+ setClock(false);
+
+ addShowBreakStep(2);
+ addShowBreakStep(5);
+ addShowBreakStep(15);
+ }
+
+ [Test]
+ public void TestNoEffectsBreak()
+ {
+ var shortBreak = new BreakPeriod { EndTime = 500 };
+
+ setClock(true);
+ loadBreaksStep("short break", new[] { shortBreak });
+
+ addBreakSeeks(shortBreak, false);
+ }
+
+ [Test]
+ public void TestMultipleBreaks()
+ {
+ setClock(true);
+ loadBreaksStep("multiple breaks", testBreaks);
+
+ foreach (var b in testBreaks)
+ addBreakSeeks(b, false);
+ }
+
+ [Test]
+ public void TestRewindBreaks()
+ {
+ setClock(true);
+ loadBreaksStep("multiple breaks", testBreaks);
+
+ foreach (var b in testBreaks.Reverse())
+ addBreakSeeks(b, true);
+ }
+
+ [Test]
+ public void TestSkipBreaks()
+ {
+ setClock(true);
+ loadBreaksStep("multiple breaks", testBreaks);
+
+ seekAndAssertBreak("seek to break start", testBreaks[1].StartTime, true);
+ AddAssert("is skipped to break #2", () => breakOverlay.CurrentBreakIndex == 1);
+
+ seekAndAssertBreak("seek to break middle", testBreaks[1].StartTime + testBreaks[1].Duration / 2, true);
+ seekAndAssertBreak("seek to break end", testBreaks[1].EndTime, false);
+ seekAndAssertBreak("seek to break after end", testBreaks[1].EndTime + 500, false);
+ }
+
+ private void addShowBreakStep(double seconds)
+ {
+ AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List
{
new BreakPeriod
{
StartTime = Clock.CurrentTime,
- EndTime = Clock.CurrentTime + duration,
+ EndTime = Clock.CurrentTime + seconds * 1000,
}
- };
+ });
}
- private void startMultipleBreaks()
+ private void setClock(bool useManual)
{
- double currentTime = Clock.CurrentTime;
-
- breakOverlay.Breaks = new List
- {
- new BreakPeriod
- {
- StartTime = currentTime,
- EndTime = currentTime + 2000,
- },
- new BreakPeriod
- {
- StartTime = currentTime + 4000,
- EndTime = currentTime + 6000,
- }
- };
+ AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakOverlay.SwitchClock(useManual));
}
- private void startAnotherMultipleBreaks()
+ private void loadBreaksStep(string breakDescription, IReadOnlyList breaks)
{
- double currentTime = Clock.CurrentTime;
+ AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breaks);
+ seekAndAssertBreak("seek back to 0", 0, false);
+ }
- breakOverlay.Breaks = new List
+ private void addBreakSeeks(BreakPeriod b, bool isReversed)
+ {
+ if (isReversed)
{
- new BreakPeriod // Duration is less than 650 - too short to appear
- {
- StartTime = currentTime,
- EndTime = currentTime + 500,
- },
- new BreakPeriod
- {
- StartTime = currentTime + 1500,
- EndTime = currentTime + 2200,
- },
- new BreakPeriod
- {
- StartTime = currentTime + 3200,
- EndTime = currentTime + 4200,
- },
- new BreakPeriod
- {
- StartTime = currentTime + 5200,
- EndTime = currentTime + 7200,
- }
- };
+ seekAndAssertBreak("seek to break after end", b.EndTime + 500, false);
+ seekAndAssertBreak("seek to break end", b.EndTime, false);
+ seekAndAssertBreak("seek to break middle", b.StartTime + b.Duration / 2, b.HasEffect);
+ seekAndAssertBreak("seek to break start", b.StartTime, b.HasEffect);
+ }
+ else
+ {
+ seekAndAssertBreak("seek to break start", b.StartTime, b.HasEffect);
+ seekAndAssertBreak("seek to break middle", b.StartTime + b.Duration / 2, b.HasEffect);
+ seekAndAssertBreak("seek to break end", b.EndTime, false);
+ seekAndAssertBreak("seek to break after end", b.EndTime + 500, false);
+ }
+ }
+
+ private void seekAndAssertBreak(string seekStepDescription, double time, bool shouldBeBreak)
+ {
+ AddStep(seekStepDescription, () => breakOverlay.ManualClockTime = time);
+ AddAssert($"is{(!shouldBeBreak ? " not" : string.Empty)} break time", () =>
+ {
+ breakOverlay.ProgressTime();
+ return breakOverlay.IsBreakTime.Value == shouldBeBreak;
+ });
+ }
+
+ private class TestBreakOverlay : BreakOverlay
+ {
+ private readonly FramedClock framedManualClock;
+ private readonly ManualClock manualClock;
+ private IFrameBasedClock originalClock;
+
+ public new int CurrentBreakIndex => base.CurrentBreakIndex;
+
+ public double ManualClockTime
+ {
+ get => manualClock.CurrentTime;
+ set => manualClock.CurrentTime = value;
+ }
+
+ public TestBreakOverlay(bool letterboxing)
+ : base(letterboxing)
+ {
+ framedManualClock = new FramedClock(manualClock = new ManualClock());
+ ProcessCustomClock = false;
+ }
+
+ public void ProgressTime()
+ {
+ framedManualClock.ProcessFrame();
+ Update();
+ }
+
+ public void SwitchClock(bool setManual) => Clock = setManual ? framedManualClock : originalClock;
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ originalClock = Clock;
+ }
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
new file mode 100644
index 0000000000..60ace8ea69
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
@@ -0,0 +1,305 @@
+// 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.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input;
+using osu.Framework.MathUtils;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Configuration;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneDrawableScrollingRuleset : OsuTestScene
+ {
+ ///
+ /// The amount of time visible by the "view window" of the playfield.
+ /// All hitobjects added through are spaced apart by this value, such that for a beat length of 1000,
+ /// there will be at most 2 hitobjects visible in the "view window".
+ ///
+ private const double time_range = 1000;
+
+ private readonly ManualClock testClock = new ManualClock();
+ private TestDrawableScrollingRuleset drawableRuleset;
+
+ [SetUp]
+ public void Setup() => Schedule(() => testClock.CurrentTime = 0);
+
+ [Test]
+ public void TestRelativeBeatLengthScaleSingleTimingPoint()
+ {
+ var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range / 2 });
+
+ createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
+
+ assertPosition(0, 0f);
+
+ // The single timing point is 1x speed relative to itself, such that the hitobject occurring time_range milliseconds later should appear
+ // at the bottom of the view window regardless of the timing point's beat length
+ assertPosition(1, 1f);
+ }
+
+ [Test]
+ public void TestRelativeBeatLengthScaleTimingPointBeyondEndDoesNotBecomeDominant()
+ {
+ var beatmap = createBeatmap(
+ new TimingControlPoint { BeatLength = time_range / 2 },
+ new TimingControlPoint { Time = 12000, BeatLength = time_range },
+ new TimingControlPoint { Time = 100000, BeatLength = time_range });
+
+ createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
+
+ assertPosition(0, 0f);
+ assertPosition(1, 1f);
+ }
+
+ [Test]
+ public void TestRelativeBeatLengthScaleFromSecondTimingPoint()
+ {
+ var beatmap = createBeatmap(
+ new TimingControlPoint { BeatLength = time_range },
+ new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 });
+
+ createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
+
+ // The first timing point should have a relative velocity of 2
+ assertPosition(0, 0f);
+ assertPosition(1, 0.5f);
+ assertPosition(2, 1f);
+
+ // Move to the second timing point
+ setTime(3 * time_range);
+ assertPosition(3, 0f);
+
+ // As above, this is the timing point that is 1x speed relative to itself, so the hitobject occurring time_range milliseconds later should be at the bottom of the view window
+ assertPosition(4, 1f);
+ }
+
+ [Test]
+ public void TestNonRelativeScale()
+ {
+ var beatmap = createBeatmap(
+ new TimingControlPoint { BeatLength = time_range },
+ new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 });
+
+ createTest(beatmap);
+
+ assertPosition(0, 0f);
+ assertPosition(1, 1);
+
+ // Move to the second timing point
+ setTime(3 * time_range);
+ assertPosition(3, 0f);
+
+ // For a beat length of 500, the view window of this timing point is elongated 2x (1000 / 500), such that the second hitobject is two TimeRanges away (offscreen)
+ // To bring it on-screen, half TimeRange is added to the current time, bringing the second half of the view window into view, and the hitobject should appear at the bottom
+ setTime(3 * time_range + time_range / 2);
+ assertPosition(4, 1f);
+ }
+
+ private void assertPosition(int index, float relativeY) => AddAssert($"hitobject {index} at {relativeY}",
+ () => Precision.AlmostEquals(drawableRuleset.Playfield.AllHitObjects.ElementAt(index).DrawPosition.Y, drawableRuleset.Playfield.HitObjectContainer.DrawHeight * relativeY));
+
+ private void setTime(double time)
+ {
+ AddStep($"set time = {time}", () => testClock.CurrentTime = time);
+ }
+
+ ///
+ /// Creates an , containing 10 hitobjects and user-provided timing points.
+ /// The hitobjects are spaced milliseconds apart.
+ ///
+ /// The timing points to add to the beatmap.
+ /// The .
+ private IBeatmap createBeatmap(params TimingControlPoint[] timingControlPoints)
+ {
+ var beatmap = new Beatmap { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } };
+
+ beatmap.ControlPointInfo.TimingPoints.AddRange(timingControlPoints);
+
+ for (int i = 0; i < 10; i++)
+ beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range });
+
+ return beatmap;
+ }
+
+ private void createTest(IBeatmap beatmap, Action overrideAction = null) => AddStep("create test", () =>
+ {
+ var ruleset = new TestScrollingRuleset();
+
+ drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap), Array.Empty());
+ drawableRuleset.FrameStablePlayback = false;
+
+ overrideAction?.Invoke(drawableRuleset);
+
+ Child = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Y,
+ Height = 0.75f,
+ Width = 400,
+ Masking = true,
+ Clock = new FramedClock(testClock),
+ Child = drawableRuleset
+ };
+ });
+
+ #region Ruleset
+
+ private class TestScrollingRuleset : Ruleset
+ {
+ public TestScrollingRuleset(RulesetInfo rulesetInfo = null)
+ : base(rulesetInfo)
+ {
+ }
+
+ public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException();
+
+ public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new TestDrawableScrollingRuleset(this, beatmap, mods);
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap);
+
+ public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException();
+
+ public override string Description { get; } = string.Empty;
+
+ public override string ShortName { get; } = string.Empty;
+ }
+
+ private class TestDrawableScrollingRuleset : DrawableScrollingRuleset
+ {
+ public bool RelativeScaleBeatLengthsOverride { get; set; }
+
+ protected override bool RelativeScaleBeatLengths => RelativeScaleBeatLengthsOverride;
+
+ protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
+
+ public TestDrawableScrollingRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
+ : base(ruleset, beatmap, mods)
+ {
+ TimeRange.Value = time_range;
+ }
+
+ public override DrawableHitObject CreateDrawableRepresentation(TestHitObject h) => new DrawableTestHitObject(h);
+
+ protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager();
+
+ protected override Playfield CreatePlayfield() => new TestPlayfield();
+ }
+
+ private class TestPlayfield : ScrollingPlayfield
+ {
+ public TestPlayfield()
+ {
+ AddInternal(new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.2f,
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Top = 150 },
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.X,
+ Height = 2,
+ Colour = Color4.Green
+ },
+ HitObjectContainer
+ }
+ }
+ }
+ });
+ }
+ }
+
+ private class TestBeatmapConverter : BeatmapConverter
+ {
+ public TestBeatmapConverter(IBeatmap beatmap)
+ : base(beatmap)
+ {
+ }
+
+ protected override IEnumerable ValidConversionTypes => new[] { typeof(HitObject) };
+
+ protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap)
+ {
+ yield return new TestHitObject
+ {
+ StartTime = original.StartTime,
+ EndTime = (original as IHasEndTime)?.EndTime ?? (original.StartTime + 100)
+ };
+ }
+ }
+
+ #endregion
+
+ #region HitObject
+
+ private class TestHitObject : HitObject, IHasEndTime
+ {
+ public double EndTime { get; set; }
+
+ public double Duration => EndTime - StartTime;
+ }
+
+ private class DrawableTestHitObject : DrawableHitObject
+ {
+ public DrawableTestHitObject(TestHitObject hitObject)
+ : base(hitObject)
+ {
+ Anchor = Anchor.TopCentre;
+ Origin = Anchor.TopCentre;
+
+ Size = new Vector2(100, 25);
+
+ AddRangeInternal(new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.LightPink
+ },
+ new Box
+ {
+ Origin = Anchor.CentreLeft,
+ RelativeSizeAxes = Axes.X,
+ Height = 2,
+ Colour = Color4.Red
+ }
+ });
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
new file mode 100644
index 0000000000..d57ec44f39
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
@@ -0,0 +1,52 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.UI;
+using osu.Game.Scoring;
+using osu.Game.Screens.Play;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneFailJudgement : AllPlayersTestScene
+ {
+ protected override Player CreatePlayer(Ruleset ruleset)
+ {
+ Mods.Value = Array.Empty();
+
+ var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty());
+ return new FailPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap));
+ }
+
+ protected override void AddCheckSteps()
+ {
+ AddUntilStep("wait for fail", () => Player.HasFailed);
+ AddUntilStep("wait for multiple judged objects", () => ((FailPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.Count(h => h.AllJudged) > 1);
+ AddAssert("total judgements == 1", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits == 1);
+ }
+
+ private class FailPlayer : ReplayPlayer
+ {
+ public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
+
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+
+ protected override bool PauseOnFocusLost => false;
+
+ public FailPlayer(Score score)
+ : base(score, false, false)
+ {
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ ScoreProcessor.FailConditions += (_, __) => true;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
index 4727140d99..c1635ffc83 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
@@ -3,12 +3,13 @@
using System;
using System.Collections.Generic;
-using System.ComponentModel;
using System.Linq;
+using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Screens.Play;
using osuTK;
@@ -29,57 +30,118 @@ namespace osu.Game.Tests.Visual.Gameplay
[BackgroundDependencyLoader]
private void load(OsuGameBase game)
{
- Child = globalActionContainer = new GlobalActionContainer(game)
- {
- Children = new Drawable[]
- {
- pauseOverlay = new PauseOverlay
- {
- OnResume = () => Logger.Log(@"Resume"),
- OnRetry = () => Logger.Log(@"Retry"),
- OnQuit = () => Logger.Log(@"Quit"),
- },
- failOverlay = new FailOverlay
+ Child = globalActionContainer = new GlobalActionContainer(game);
+ }
- {
- OnRetry = () => Logger.Log(@"Retry"),
- OnQuit = () => Logger.Log(@"Quit"),
- }
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ globalActionContainer.Children = new Drawable[]
+ {
+ pauseOverlay = new PauseOverlay
+ {
+ OnResume = () => Logger.Log(@"Resume"),
+ OnRetry = () => Logger.Log(@"Retry"),
+ OnQuit = () => Logger.Log(@"Quit"),
+ },
+ failOverlay = new FailOverlay
+
+ {
+ OnRetry = () => Logger.Log(@"Retry"),
+ OnQuit = () => Logger.Log(@"Quit"),
}
};
+ InputManager.MoveMouseTo(Vector2.Zero);
+ });
+
+ [Test]
+ public void TestAdjustRetryCount()
+ {
+ showOverlay();
+
var retryCount = 0;
- AddStep("Add retry", () =>
+ AddRepeatStep("Add retry", () =>
{
retryCount++;
pauseOverlay.Retries = failOverlay.Retries = retryCount;
- });
+ }, 10);
+ }
- AddToggleStep("Toggle pause overlay", t => pauseOverlay.ToggleVisibility());
- AddToggleStep("Toggle fail overlay", t => failOverlay.ToggleVisibility());
+ ///
+ /// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred.
+ ///
+ [Test]
+ public void TestEnterWithoutSelection()
+ {
+ showOverlay();
- testHideResets();
+ AddStep("Press select", () => press(GlobalAction.Select));
+ AddAssert("Overlay still open", () => pauseOverlay.State.Value == Visibility.Visible);
+ }
- testEnterWithoutSelection();
- testKeyUpFromInitial();
- testKeyDownFromInitial();
- testKeyUpWrapping();
- testKeyDownWrapping();
+ ///
+ /// Tests that pressing the up arrow from the initial state selects the last button.
+ ///
+ [Test]
+ public void TestKeyUpFromInitial()
+ {
+ showOverlay();
- testMouseSelectionAfterKeySelection();
- testKeySelectionAfterMouseSelection();
+ AddStep("Up arrow", () => press(Key.Up));
+ AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value);
+ }
- testMouseDeselectionResets();
+ ///
+ /// Tests that pressing the down arrow from the initial state selects the first button.
+ ///
+ [Test]
+ public void TestKeyDownFromInitial()
+ {
+ showOverlay();
- testClickSelection();
- testEnterKeySelection();
+ AddStep("Down arrow", () => press(Key.Down));
+ AddAssert("First button selected", () => getButton(0).Selected.Value);
+ }
+
+ ///
+ /// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly.
+ ///
+ [Test]
+ public void TestKeyUpWrapping()
+ {
+ AddStep("Show overlay", () => failOverlay.Show());
+
+ AddStep("Up arrow", () => press(Key.Up));
+ AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
+ AddStep("Up arrow", () => press(Key.Up));
+ AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
+ AddStep("Up arrow", () => press(Key.Up));
+ AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
+ }
+
+ ///
+ /// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly.
+ ///
+ [Test]
+ public void TestKeyDownWrapping()
+ {
+ AddStep("Show overlay", () => failOverlay.Show());
+
+ AddStep("Down arrow", () => press(Key.Down));
+ AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
+ AddStep("Down arrow", () => press(Key.Down));
+ AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
+ AddStep("Down arrow", () => press(Key.Down));
+ AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
}
///
/// Test that hiding the overlay after hovering a button will reset the overlay to the initial state with no buttons selected.
///
- private void testHideResets()
+ [Test]
+ public void TestHideResets()
{
AddStep("Show overlay", () => failOverlay.Show());
@@ -90,141 +152,78 @@ namespace osu.Game.Tests.Visual.Gameplay
}
///
- /// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred.
+ /// Tests that entering menu with cursor initially on button doesn't selects it immediately.
+ /// This is to allow for stable keyboard navigation.
///
- private void testEnterWithoutSelection()
+ [Test]
+ public void TestInitialButtonHover()
{
- AddStep("Show overlay", () => pauseOverlay.Show());
+ showOverlay();
- AddStep("Press select", () => press(GlobalAction.Select));
- AddAssert("Overlay still open", () => pauseOverlay.State.Value == Visibility.Visible);
+ AddStep("Hover first button", () => InputManager.MoveMouseTo(getButton(0)));
AddStep("Hide overlay", () => pauseOverlay.Hide());
- }
+ showOverlay();
- ///
- /// Tests that pressing the up arrow from the initial state selects the last button.
- ///
- private void testKeyUpFromInitial()
- {
- AddStep("Show overlay", () => pauseOverlay.Show());
+ AddAssert("First button not selected", () => !getButton(0).Selected.Value);
- AddStep("Up arrow", () => press(Key.Up));
- AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value);
+ AddStep("Move slightly", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(1)));
- AddStep("Hide overlay", () => pauseOverlay.Hide());
- }
-
- ///
- /// Tests that pressing the down arrow from the initial state selects the first button.
- ///
- private void testKeyDownFromInitial()
- {
- AddStep("Show overlay", () => pauseOverlay.Show());
-
- AddStep("Down arrow", () => press(Key.Down));
- AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value);
-
- AddStep("Hide overlay", () => pauseOverlay.Hide());
- }
-
- ///
- /// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly.
- ///
- private void testKeyUpWrapping()
- {
- AddStep("Show overlay", () => failOverlay.Show());
-
- AddStep("Up arrow", () => press(Key.Up));
- AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
- AddStep("Up arrow", () => press(Key.Up));
- AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
- AddStep("Up arrow", () => press(Key.Up));
- AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
-
- AddStep("Hide overlay", () => failOverlay.Hide());
- }
-
- ///
- /// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly.
- ///
- private void testKeyDownWrapping()
- {
- AddStep("Show overlay", () => failOverlay.Show());
-
- AddStep("Down arrow", () => press(Key.Down));
- AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
- AddStep("Down arrow", () => press(Key.Down));
- AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
- AddStep("Down arrow", () => press(Key.Down));
- AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
-
- AddStep("Hide overlay", () => failOverlay.Hide());
+ AddAssert("First button selected", () => getButton(0).Selected.Value);
}
///
/// Tests that hovering a button that was previously selected with the keyboard correctly selects the new button and deselects the previous button.
///
- private void testMouseSelectionAfterKeySelection()
+ [Test]
+ public void TestMouseSelectionAfterKeySelection()
{
- AddStep("Show overlay", () => pauseOverlay.Show());
-
- var secondButton = pauseOverlay.Buttons.Skip(1).First();
+ showOverlay();
AddStep("Down arrow", () => press(Key.Down));
- AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
- AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected.Value);
- AddAssert("Second button selected", () => secondButton.Selected.Value);
-
- AddStep("Hide overlay", () => pauseOverlay.Hide());
+ AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
+ AddAssert("First button not selected", () => !getButton(0).Selected.Value);
+ AddAssert("Second button selected", () => getButton(1).Selected.Value);
}
///
/// Tests that pressing a key after selecting a button with a hover event correctly selects a new button and deselects the previous button.
///
- private void testKeySelectionAfterMouseSelection()
+ [Test]
+ public void TestKeySelectionAfterMouseSelection()
{
AddStep("Show overlay", () =>
{
pauseOverlay.Show();
- InputManager.MoveMouseTo(Vector2.Zero);
});
- var secondButton = pauseOverlay.Buttons.Skip(1).First();
-
- AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
+ AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddStep("Up arrow", () => press(Key.Up));
- AddAssert("Second button not selected", () => !secondButton.Selected.Value);
- AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value);
-
- AddStep("Hide overlay", () => pauseOverlay.Hide());
+ AddAssert("Second button not selected", () => !getButton(1).Selected.Value);
+ AddAssert("First button selected", () => getButton(0).Selected.Value);
}
///
/// Tests that deselecting with the mouse by losing hover will reset the overlay to the initial state.
///
- private void testMouseDeselectionResets()
+ [Test]
+ public void TestMouseDeselectionResets()
{
- AddStep("Show overlay", () => pauseOverlay.Show());
+ showOverlay();
- var secondButton = pauseOverlay.Buttons.Skip(1).First();
-
- AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
+ AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero));
AddStep("Down arrow", () => press(Key.Down));
- AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value); // Initial state condition
-
- AddStep("Hide overlay", () => pauseOverlay.Hide());
+ AddAssert("First button selected", () => getButton(0).Selected.Value); // Initial state condition
}
///
/// Tests that clicking on a button correctly causes a click event for that button.
///
- private void testClickSelection()
+ [Test]
+ public void TestClickSelection()
{
- AddStep("Show overlay", () => pauseOverlay.Show());
-
- var retryButton = pauseOverlay.Buttons.Skip(1).First();
+ showOverlay();
bool triggered = false;
AddStep("Click retry button", () =>
@@ -232,7 +231,7 @@ namespace osu.Game.Tests.Visual.Gameplay
var lastAction = pauseOverlay.OnRetry;
pauseOverlay.OnRetry = () => triggered = true;
- retryButton.Click();
+ getButton(1).Click();
pauseOverlay.OnRetry = lastAction;
});
@@ -243,9 +242,10 @@ namespace osu.Game.Tests.Visual.Gameplay
///
/// Tests that pressing the enter key with a button selected correctly causes a click event for that button.
///
- private void testEnterKeySelection()
+ [Test]
+ public void TestEnterKeySelection()
{
- AddStep("Show overlay", () => pauseOverlay.Show());
+ showOverlay();
AddStep("Select second button", () =>
{
@@ -275,6 +275,10 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden);
}
+ private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show());
+
+ private DialogButton getButton(int index) => pauseOverlay.Buttons.Skip(index).First();
+
private void press(Key key)
{
InputManager.PressKey(key);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs
index d42b61ea55..0c5ead10cf 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs
@@ -17,6 +17,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{
private bool exitAction;
+ protected override double TimePerAction => 100; // required for the early exit test, since hold-to-confirm delay is 200ms
+
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
index 0a9cdc6a8e..aa80819694 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
@@ -200,10 +200,6 @@ namespace osu.Game.Tests.Visual.Gameplay
break;
}
}
-
- protected override void UpdateState(ArmedState state)
- {
- }
}
private class TestDrawableHitObject : DrawableHitObject
@@ -216,10 +212,6 @@ namespace osu.Game.Tests.Visual.Gameplay
AddInternal(new Box { Size = new Vector2(75) });
}
-
- protected override void UpdateState(ArmedState state)
- {
- }
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs
deleted file mode 100644
index c7a0df6e9f..0000000000
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs
+++ /dev/null
@@ -1,145 +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 NUnit.Framework;
-using osu.Framework.Audio.Sample;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Textures;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Skinning;
-using osuTK.Graphics;
-
-namespace osu.Game.Tests.Visual.Gameplay
-{
- public class TestSceneSkinReloadable : OsuTestScene
- {
- [Test]
- public void TestInitialLoad()
- {
- var secondarySource = new SecondarySource();
- SkinConsumer consumer = null;
-
- AddStep("setup layout", () =>
- {
- Child = new SkinSourceContainer
- {
- RelativeSizeAxes = Axes.Both,
- Child = new LocalSkinOverrideContainer(secondarySource)
- {
- RelativeSizeAxes = Axes.Both,
- Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)
- }
- };
- });
-
- AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
- AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
- }
-
- [Test]
- public void TestOverride()
- {
- var secondarySource = new SecondarySource();
-
- SkinConsumer consumer = null;
- Container target = null;
-
- AddStep("setup layout", () =>
- {
- Child = new SkinSourceContainer
- {
- RelativeSizeAxes = Axes.Both,
- Child = target = new LocalSkinOverrideContainer(secondarySource)
- {
- RelativeSizeAxes = Axes.Both,
- }
- };
- });
-
- AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)));
- AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
- AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
- }
-
- private class NamedBox : Container
- {
- public NamedBox(string name)
- {
- Children = new Drawable[]
- {
- new Box
- {
- Colour = Color4.Black,
- RelativeSizeAxes = Axes.Both,
- },
- new OsuSpriteText
- {
- Font = OsuFont.Default.With(size: 40),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Text = name
- }
- };
- }
- }
-
- private class SkinConsumer : SkinnableDrawable
- {
- public new Drawable Drawable => base.Drawable;
- public int SkinChangedCount { get; private set; }
-
- public SkinConsumer(string name, Func defaultImplementation, Func allowFallback = null, bool restrictSize = true)
- : base(name, defaultImplementation, allowFallback, restrictSize)
- {
- }
-
- protected override void SkinChanged(ISkinSource skin, bool allowFallback)
- {
- base.SkinChanged(skin, allowFallback);
- SkinChangedCount++;
- }
- }
-
- private class BaseSourceBox : NamedBox
- {
- public BaseSourceBox()
- : base("Base Source")
- {
- }
- }
-
- private class SecondarySourceBox : NamedBox
- {
- public SecondarySourceBox()
- : base("Secondary Source")
- {
- }
- }
-
- private class SecondarySource : ISkin
- {
- public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox();
-
- public Texture GetTexture(string componentName) => throw new NotImplementedException();
-
- public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
-
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
- }
-
- private class SkinSourceContainer : Container, ISkin
- {
- public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox();
-
- public Texture GetTexture(string componentName) => throw new NotImplementedException();
-
- public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
-
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
new file mode 100644
index 0000000000..b3d4820737
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
@@ -0,0 +1,346 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Globalization;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Skinning;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneSkinnableDrawable : OsuTestScene
+ {
+ [Test]
+ public void TestConfineScaleDown()
+ {
+ FillFlowContainer fill = null;
+
+ AddStep("setup layout larger source", () =>
+ {
+ Child = new SkinProvidingContainer(new SizedSource(50))
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = fill = new FillFlowContainer
+ {
+ Size = new Vector2(30),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Spacing = new Vector2(10),
+ Children = new[]
+ {
+ new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling)
+ }
+ },
+ };
+ });
+
+ AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 }));
+ AddStep("adjust scale", () => fill.Scale = new Vector2(2));
+ AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 }));
+ }
+
+ [Test]
+ public void TestConfineScaleUp()
+ {
+ FillFlowContainer fill = null;
+
+ AddStep("setup layout larger source", () =>
+ {
+ Child = new SkinProvidingContainer(new SizedSource(30))
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = fill = new FillFlowContainer
+ {
+ Size = new Vector2(50),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Spacing = new Vector2(10),
+ Children = new[]
+ {
+ new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling)
+ }
+ },
+ };
+ });
+
+ AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 }));
+ AddStep("adjust scale", () => fill.Scale = new Vector2(2));
+ AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 }));
+ }
+
+ [Test]
+ public void TestInitialLoad()
+ {
+ var secondarySource = new SecondarySource();
+ SkinConsumer consumer = null;
+
+ AddStep("setup layout", () =>
+ {
+ Child = new SkinSourceContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new SkinProvidingContainer(secondarySource)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)
+ }
+ };
+ });
+
+ AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
+ AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
+ }
+
+ [Test]
+ public void TestOverride()
+ {
+ var secondarySource = new SecondarySource();
+
+ SkinConsumer consumer = null;
+ Container target = null;
+
+ AddStep("setup layout", () =>
+ {
+ Child = new SkinSourceContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = target = new SkinProvidingContainer(secondarySource)
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ };
+ });
+
+ AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)));
+ AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
+ AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
+ }
+
+ [Test]
+ public void TestSwitchOff()
+ {
+ SkinConsumer consumer = null;
+ SwitchableSkinProvidingContainer target = null;
+
+ AddStep("setup layout", () =>
+ {
+ Child = new SkinSourceContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = target = new SwitchableSkinProvidingContainer(new SecondarySource())
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ };
+ });
+
+ AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)));
+ AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
+ AddStep("disable", () => target.Disable());
+ AddAssert("consumer using base source", () => consumer.Drawable is BaseSourceBox);
+ }
+
+ private class SwitchableSkinProvidingContainer : SkinProvidingContainer
+ {
+ private bool allow = true;
+
+ protected override bool AllowDrawableLookup(ISkinComponent component) => allow;
+
+ public void Disable()
+ {
+ allow = false;
+ TriggerSourceChanged();
+ }
+
+ public SwitchableSkinProvidingContainer(ISkin skin)
+ : base(skin)
+ {
+ }
+ }
+
+ private class ExposedSkinnableDrawable : SkinnableDrawable
+ {
+ public new Drawable Drawable => base.Drawable;
+
+ public ExposedSkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null,
+ ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ : base(new TestSkinComponent(name), defaultImplementation, allowFallback, confineMode)
+ {
+ }
+ }
+
+ private class DefaultBox : DrawWidthBox
+ {
+ public DefaultBox()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+ }
+
+ private class DrawWidthBox : Container
+ {
+ private readonly OsuSpriteText text;
+
+ public DrawWidthBox()
+ {
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Gray,
+ RelativeSizeAxes = Axes.Both,
+ },
+ text = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ };
+ }
+
+ protected override void UpdateAfterChildren()
+ {
+ base.UpdateAfterChildren();
+ text.Text = DrawWidth.ToString(CultureInfo.InvariantCulture);
+ }
+ }
+
+ private class NamedBox : Container
+ {
+ public NamedBox(string name)
+ {
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Black,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Font = OsuFont.Default.With(size: 40),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = name
+ }
+ };
+ }
+ }
+
+ private class SkinConsumer : SkinnableDrawable
+ {
+ public new Drawable Drawable => base.Drawable;
+ public int SkinChangedCount { get; private set; }
+
+ public SkinConsumer(string name, Func defaultImplementation, Func allowFallback = null)
+ : base(new TestSkinComponent(name), defaultImplementation, allowFallback)
+ {
+ }
+
+ protected override void SkinChanged(ISkinSource skin, bool allowFallback)
+ {
+ base.SkinChanged(skin, allowFallback);
+ SkinChangedCount++;
+ }
+ }
+
+ private class BaseSourceBox : NamedBox
+ {
+ public BaseSourceBox()
+ : base("Base Source")
+ {
+ }
+ }
+
+ private class SecondarySourceBox : NamedBox
+ {
+ public SecondarySourceBox()
+ : base("Secondary Source")
+ {
+ }
+ }
+
+ private class SizedSource : ISkin
+ {
+ private readonly float size;
+
+ public SizedSource(float size)
+ {
+ this.size = size;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent componentName) =>
+ componentName.LookupName == "available"
+ ? new DrawWidthBox
+ {
+ Colour = Color4.Yellow,
+ Size = new Vector2(size)
+ }
+ : null;
+
+ public Texture GetTexture(string componentName) => throw new NotImplementedException();
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
+
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
+ }
+
+ private class SecondarySource : ISkin
+ {
+ public Drawable GetDrawableComponent(ISkinComponent componentName) => new SecondarySourceBox();
+
+ public Texture GetTexture(string componentName) => throw new NotImplementedException();
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
+
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
+ }
+
+ [Cached(typeof(ISkinSource))]
+ private class SkinSourceContainer : Container, ISkinSource
+ {
+ public Drawable GetDrawableComponent(ISkinComponent componentName) => new BaseSourceBox();
+
+ public Texture GetTexture(string componentName) => throw new NotImplementedException();
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
+
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
+
+ public event Action SourceChanged;
+ }
+
+ private class TestSkinComponent : ISkinComponent
+ {
+ private readonly string name;
+
+ public TestSkinComponent(string name)
+ {
+ this.name = name;
+ }
+
+ public string ComponentGroup => string.Empty;
+
+ public string LookupName => name;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
index ead7a4b7fc..ff8437311e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
@@ -21,32 +21,38 @@ namespace osu.Game.Tests.Visual.Gameplay
private readonly Container storyboardContainer;
private DrawableStoryboard storyboard;
+ [Cached]
+ private MusicController musicController = new MusicController();
+
public TestSceneStoryboard()
{
Clock = new FramedClock();
- Add(new Container
+ AddRange(new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
+ musicController,
+ new Container
{
- new Box
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- },
- storyboardContainer = new Container
- {
- RelativeSizeAxes = Axes.Both,
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black,
+ },
+ storyboardContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
},
},
- });
-
- Add(new MusicController
- {
- Origin = Anchor.TopRight,
- Anchor = Anchor.TopRight,
- State = { Value = Visibility.Visible },
+ new NowPlayingOverlay
+ {
+ Origin = Anchor.TopRight,
+ Anchor = Anchor.TopRight,
+ State = { Value = Visibility.Visible },
+ }
});
AddStep("Restart", restart);
diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs
new file mode 100644
index 0000000000..d03d341ee4
--- /dev/null
+++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs
@@ -0,0 +1,69 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Screens;
+using osu.Game.Screens;
+using osu.Game.Screens.Menu;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual.Menus
+{
+ [TestFixture]
+ public abstract class IntroTestScene : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(StartupScreen),
+ typeof(IntroScreen),
+ typeof(OsuScreen),
+ typeof(IntroTestScene),
+ };
+
+ [Cached]
+ private OsuLogo logo;
+
+ protected IntroTestScene()
+ {
+ Drawable introStack = null;
+
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Depth = float.MaxValue,
+ Colour = Color4.Black,
+ },
+ logo = new OsuLogo
+ {
+ Alpha = 0,
+ RelativePositionAxes = Axes.Both,
+ Depth = float.MinValue,
+ Position = new Vector2(0.5f),
+ }
+ };
+
+ AddStep("restart sequence", () =>
+ {
+ logo.FinishTransforms();
+ logo.IsTracking = false;
+
+ introStack?.Expire();
+
+ Add(introStack = new OsuScreenStack(CreateScreen())
+ {
+ RelativeSizeAxes = Axes.Both,
+ });
+ });
+ }
+
+ protected abstract IScreen CreateScreen();
+ }
+}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs
index f2718b8e80..13116de320 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs
@@ -10,14 +10,9 @@ namespace osu.Game.Tests.Visual.Menus
{
public class TestSceneDisclaimer : ScreenTestScene
{
- [Cached(typeof(IAPIProvider))]
- private readonly DummyAPIAccess api = new DummyAPIAccess();
-
[BackgroundDependencyLoader]
- private void load()
+ private void load(IAPIProvider api)
{
- Add(api);
-
AddStep("load disclaimer", () => LoadScreen(new Disclaimer()));
AddStep("toggle support", () =>
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs
new file mode 100644
index 0000000000..107734cc8d
--- /dev/null
+++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Screens;
+using osu.Game.Screens.Menu;
+
+namespace osu.Game.Tests.Visual.Menus
+{
+ [TestFixture]
+ public class TestSceneIntroCircles : IntroTestScene
+ {
+ protected override IScreen CreateScreen() => new IntroCircles();
+ }
+}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs
deleted file mode 100644
index b59fb18428..0000000000
--- a/osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using System.Collections.Generic;
-using NUnit.Framework;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Timing;
-using osu.Game.Screens.Menu;
-using osuTK.Graphics;
-
-namespace osu.Game.Tests.Visual.Menus
-{
- [TestFixture]
- public class TestSceneIntroSequence : OsuTestScene
- {
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(OsuLogo),
- };
-
- public TestSceneIntroSequence()
- {
- OsuLogo logo;
-
- var rateAdjustClock = new StopwatchClock(true);
- var framedClock = new FramedClock(rateAdjustClock);
- framedClock.ProcessFrame();
-
- Add(new Container
- {
- RelativeSizeAxes = Axes.Both,
- Clock = framedClock,
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- },
- logo = new OsuLogo
- {
- Anchor = Anchor.Centre,
- }
- }
- });
-
- AddStep(@"Restart", logo.PlayIntro);
- AddSliderStep("Playback speed", 0.0, 2.0, 1, v => rateAdjustClock.Rate = v);
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs
new file mode 100644
index 0000000000..df79584167
--- /dev/null
+++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Screens;
+using osu.Game.Screens.Menu;
+
+namespace osu.Game.Tests.Visual.Menus
+{
+ [TestFixture]
+ public class TestSceneIntroTriangles : IntroTestScene
+ {
+ protected override IScreen CreateScreen() => new IntroTriangles();
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs
new file mode 100644
index 0000000000..68ad0b42b4
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs
@@ -0,0 +1,53 @@
+// Copyright (c) ppy Pty Ltd