diff --git a/Directory.Build.props b/Directory.Build.props
index 21b8b402e0..2cd40c8675 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -16,9 +16,9 @@
-
+
-
+
$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset
diff --git a/README.md b/README.md
index 59d72247f5..336bf33f7e 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,8 @@ Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the
This project is under heavy development, but is in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update.
+**IMPORTANT:** Gameplay mechanics (and other features which you may have come to know and love) are in a constant state of flux. Game balance and final quality-of-life passses come at the end of development, preceeded by experimentation and changes which may potentially **reduce playability or usability**. This is done in order to allow us to move forward as developers and designers more efficiently. If this offends you, please consider sticking to the stable releases of osu! (found on the website). We are not yet open to heated discussion over game mechanics and will not be using github as a forum for such discussions just yet.
+
We are accepting bug reports (please report with as much detail as possible and follow the existing issue templates). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project:
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
diff --git a/osu.Android.props b/osu.Android.props
index 336479c40a..07be3ab0d2 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,7 +51,7 @@
-
-
+
+
diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs
index 84f215f930..19ed7ffcf5 100644
--- a/osu.Android/OsuGameAndroid.cs
+++ b/osu.Android/OsuGameAndroid.cs
@@ -18,7 +18,8 @@ namespace osu.Android
try
{
- string versionName = packageInfo.VersionCode.ToString();
+ // todo: needs checking before play store redeploy.
+ string versionName = packageInfo.VersionName;
// undo play store version garbling
return new Version(int.Parse(versionName.Substring(0, 4)), int.Parse(versionName.Substring(4, 4)), int.Parse(versionName.Substring(8, 1)));
}
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index f05ee48914..9351e17419 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -6,15 +6,14 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
+using Microsoft.Win32;
using osu.Desktop.Overlays;
using osu.Framework.Platform;
using osu.Game;
using osuTK.Input;
-using Microsoft.Win32;
using osu.Desktop.Updater;
using osu.Framework;
using osu.Framework.Logging;
-using osu.Framework.Platform.Windows;
using osu.Framework.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Updater;
@@ -37,7 +36,11 @@ namespace osu.Desktop
try
{
if (Host is DesktopGameHost desktopHost)
- return new StableStorage(desktopHost);
+ {
+ string stablePath = getStableInstallPath();
+ if (!string.IsNullOrEmpty(stablePath))
+ return new DesktopStorage(stablePath, desktopHost);
+ }
}
catch (Exception)
{
@@ -47,6 +50,35 @@ namespace osu.Desktop
return null;
}
+ private string getStableInstallPath()
+ {
+ static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs"));
+
+ string stableInstallPath;
+
+ try
+ {
+ using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
+ stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", "");
+
+ if (checkExists(stableInstallPath))
+ return stableInstallPath;
+ }
+ catch
+ {
+ }
+
+ stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!");
+ if (checkExists(stableInstallPath))
+ return stableInstallPath;
+
+ stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu");
+ if (checkExists(stableInstallPath))
+ return stableInstallPath;
+
+ return null;
+ }
+
protected override UpdateManager CreateUpdateManager()
{
switch (RuntimeInfo.OS)
@@ -111,45 +143,5 @@ namespace osu.Desktop
Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning);
}
-
- ///
- /// A method of accessing an osu-stable install in a controlled fashion.
- ///
- private class StableStorage : WindowsStorage
- {
- protected override string LocateBasePath()
- {
- static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs"));
-
- string stableInstallPath;
-
- try
- {
- using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
- stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", "");
-
- if (checkExists(stableInstallPath))
- return stableInstallPath;
- }
- catch
- {
- }
-
- stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!");
- if (checkExists(stableInstallPath))
- return stableInstallPath;
-
- stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu");
- if (checkExists(stableInstallPath))
- return stableInstallPath;
-
- return null;
- }
-
- public StableStorage(DesktopGameHost host)
- : base(string.Empty, host)
- {
- }
- }
}
}
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index bd91bcc933..285a813d97 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -33,13 +33,11 @@ namespace osu.Desktop
if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args
{
var importer = new ArchiveImportIPCChannel(host);
- // Restore the cwd so relative paths given at the command line work correctly
- Directory.SetCurrentDirectory(cwd);
foreach (var file in args)
{
Console.WriteLine(@"Importing {0}", file);
- if (!importer.ImportAsync(Path.GetFullPath(file)).Wait(3000))
+ if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000))
throw new TimeoutException(@"IPC took too long to send");
}
diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs
index 60b47a8b3a..ade8460dd7 100644
--- a/osu.Desktop/Updater/SquirrelUpdateManager.cs
+++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs
@@ -43,7 +43,7 @@ namespace osu.Desktop.Updater
private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
{
- //should we schedule a retry on completion of this check?
+ // should we schedule a retry on completion of this check?
bool scheduleRecheck = true;
try
@@ -52,7 +52,7 @@ namespace osu.Desktop.Updater
var info = await updateManager.CheckForUpdate(!useDeltaPatching);
if (info.ReleasesToApply.Count == 0)
- //no updates available. bail and retry later.
+ // no updates available. bail and retry later.
return;
if (notification == null)
@@ -81,8 +81,8 @@ namespace osu.Desktop.Updater
{
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.
+ // could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
+ // try again without deltas.
checkForUpdateAsync(false, notification);
scheduleRecheck = false;
}
@@ -101,7 +101,7 @@ namespace osu.Desktop.Updater
{
if (scheduleRecheck)
{
- //check again in 30 minutes.
+ // check again in 30 minutes.
Scheduler.AddDelayed(() => checkForUpdateAsync(), 60000 * 30);
}
}
diff --git a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs
index 394fd75488..1d207d04c7 100644
--- a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs
+++ b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs
@@ -8,7 +8,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.IO.Archives;
-using osu.Game.Resources;
+using osu.Game.Tests.Resources;
namespace osu.Game.Benchmarks
{
@@ -18,8 +18,8 @@ namespace osu.Game.Benchmarks
public override void SetUp()
{
- using (var resources = new DllResourceStore(OsuResources.ResourceAssembly))
- using (var archive = resources.GetStream("Beatmaps/241526 Soleily - Renatus.osz"))
+ using (var resources = new DllResourceStore(typeof(TestResources).Assembly))
+ using (var archive = resources.GetStream("Resources/Archives/241526 Soleily - Renatus.osz"))
using (var reader = new ZipArchiveReader(archive))
reader.GetStream("Soleily - Renatus (Gamu) [Insane].osu").CopyTo(beatmapStream);
}
diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
index 88fe8f1150..41e726e05c 100644
--- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
+++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
@@ -13,6 +13,7 @@
+
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs
index 51fe0b035d..ee416e5a38 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
- [TestCase(4.2058561036909863d, "diffcalc-test")]
+ [TestCase(4.050601681491468d, "diffcalc-test")]
public void Test(double expected, string name)
=> base.Test(expected, name);
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs
new file mode 100644
index 0000000000..7deeec527f
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs
@@ -0,0 +1,36 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.IO.Stores;
+using osu.Game.Rulesets.Catch.Skinning;
+using osu.Game.Skinning;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ [TestFixture]
+ public class CatchSkinColourDecodingTest
+ {
+ [Test]
+ public void TestCatchSkinColourDecoding()
+ {
+ var store = new NamespacedResourceStore(new DllResourceStore(GetType().Assembly), "Resources/special-skin");
+ var rawSkin = new TestLegacySkin(new SkinInfo { Name = "special-skin" }, store);
+ var skin = new CatchLegacySkinTransformer(rawSkin);
+
+ Assert.AreEqual(new Color4(232, 185, 35, 255), skin.GetConfig(CatchSkinColour.HyperDash)?.Value);
+ Assert.AreEqual(new Color4(232, 74, 35, 255), skin.GetConfig(CatchSkinColour.HyperDashAfterImage)?.Value);
+ Assert.AreEqual(new Color4(0, 255, 255, 255), skin.GetConfig(CatchSkinColour.HyperDashFruit)?.Value);
+ }
+
+ private class TestLegacySkin : LegacySkin
+ {
+ public TestLegacySkin(SkinInfo skin, IResourceStore storage)
+ // Bypass LegacySkinResourceStore to avoid returning null for retrieving files due to bad skin info (SkinInfo.Files = null).
+ : base(skin, storage, null, "skin.ini")
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs
index 0c46b078b5..378772fea3 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs
@@ -1,21 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
-using System.Collections.Generic;
-using osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
public abstract class CatchSkinnableTestScene : SkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(CatchRuleset),
- typeof(CatchLegacySkinTransformer),
- };
-
protected override Ruleset CreateRulesetForSkinProvider() => new CatchRuleset();
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/skin.ini
new file mode 100644
index 0000000000..96d50f1451
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/skin.ini
@@ -0,0 +1,4 @@
+[CatchTheBeat]
+HyperDash: 232,185,35
+HyperDashFruit: 0,255,255
+HyperDashAfterImage: 232,74,35
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
index 024c4cefb0..27a2d5bd0a 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
@@ -1,13 +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 System;
-using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Catch.Objects.Drawables;
-using osu.Game.Rulesets.Catch.UI;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
@@ -15,17 +11,6 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestFixture]
public class TestSceneBananaShower : PlayerTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(BananaShower),
- typeof(Banana),
- typeof(DrawableBananaShower),
- typeof(DrawableBanana),
-
- typeof(CatchRuleset),
- typeof(DrawableCatchRuleset),
- };
-
public TestSceneBananaShower()
: base(new CatchRuleset())
{
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
index acc5f4e428..6eeda2c731 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -4,26 +4,18 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Catch.UI;
-using System;
-using System.Collections.Generic;
-using System.Linq;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
public class TestSceneCatcher : CatchSkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[]
- {
- typeof(CatcherArea),
- typeof(CatcherSprite)
- }).ToList();
-
[BackgroundDependencyLoader]
private void load()
{
- SetContents(() => new Catcher
+ SetContents(() => new Catcher(new Container())
{
RelativePositionAxes = Axes.None,
Anchor = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
index df5494aab0..a7094c00be 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
@@ -1,7 +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;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@@ -22,15 +21,6 @@ namespace osu.Game.Rulesets.Catch.Tests
{
public class TestSceneDrawableHitObjects : OsuTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(Catcher),
- typeof(DrawableCatchRuleset),
- typeof(DrawableFruit),
- typeof(DrawableJuiceStream),
- typeof(DrawableBanana)
- };
-
private DrawableCatchRuleset drawableRuleset;
private double playfieldTime => drawableRuleset.Playfield.Time.Current;
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
index 8c3dfef39c..62fe5dca2c 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
@@ -1,9 +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;
-using System.Collections.Generic;
-using System.Linq;
using NUnit.Framework;
using osu.Game.Rulesets.Catch.Mods;
@@ -11,8 +8,6 @@ namespace osu.Game.Rulesets.Catch.Tests
{
public class TestSceneDrawableHitObjectsHidden : TestSceneDrawableHitObjects
{
- public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(CatchModHidden) }).ToList();
-
[SetUp]
public void SetUp() => Schedule(() =>
{
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
index cd674bb754..c07e4fdad3 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
@@ -2,13 +2,10 @@
// 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.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
-using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
@@ -16,22 +13,6 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestFixture]
public class TestSceneFruitObjects : CatchSkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[]
- {
- typeof(CatchHitObject),
- typeof(Fruit),
- typeof(FruitPiece),
- typeof(Droplet),
- typeof(Banana),
- typeof(BananaShower),
- typeof(DrawableCatchHitObject),
- typeof(DrawableFruit),
- typeof(DrawableDroplet),
- typeof(DrawableBanana),
- typeof(DrawableBananaShower),
- typeof(Pulp),
- }).ToList();
-
protected override void LoadComplete()
{
base.LoadComplete();
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
index 49ff9df4d7..0a142a52f8 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
@@ -2,7 +2,6 @@
// 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.Testing;
@@ -18,11 +17,6 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestFixture]
public class TestSceneHyperDash : PlayerTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(CatcherArea),
- };
-
public TestSceneHyperDash()
: base(new CatchRuleset())
{
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
index a48ecb9b79..1e708cce4b 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
@@ -26,6 +26,48 @@ namespace osu.Game.Rulesets.Catch.Tests
[Resolved]
private SkinManager skins { get; set; }
+ [Test]
+ public void TestDefaultCatcherColour()
+ {
+ var skin = new TestSkin();
+
+ checkHyperDashCatcherColour(skin, Catcher.DEFAULT_HYPER_DASH_COLOUR);
+ }
+
+ [Test]
+ public void TestCustomCatcherColour()
+ {
+ var skin = new TestSkin
+ {
+ HyperDashColour = Color4.Goldenrod
+ };
+
+ checkHyperDashCatcherColour(skin, skin.HyperDashColour);
+ }
+
+ [Test]
+ public void TestCustomEndGlowColour()
+ {
+ var skin = new TestSkin
+ {
+ HyperDashAfterImageColour = Color4.Lime
+ };
+
+ checkHyperDashCatcherColour(skin, Catcher.DEFAULT_HYPER_DASH_COLOUR, skin.HyperDashAfterImageColour);
+ }
+
+ [Test]
+ public void TestCustomEndGlowColourPriority()
+ {
+ var skin = new TestSkin
+ {
+ HyperDashColour = Color4.Goldenrod,
+ HyperDashAfterImageColour = Color4.Lime
+ };
+
+ checkHyperDashCatcherColour(skin, skin.HyperDashColour, skin.HyperDashAfterImageColour);
+ }
+
[Test]
public void TestDefaultFruitColour()
{
@@ -68,6 +110,38 @@ namespace osu.Game.Rulesets.Catch.Tests
checkHyperDashFruitColour(skin, skin.HyperDashColour);
}
+ private void checkHyperDashCatcherColour(ISkin skin, Color4 expectedCatcherColour, Color4? expectedEndGlowColour = null)
+ {
+ CatcherArea catcherArea = null;
+ CatcherTrailDisplay trails = null;
+
+ AddStep("create hyper-dashing catcher", () =>
+ {
+ Child = setupSkinHierarchy(catcherArea = new CatcherArea
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(4f),
+ }, skin);
+
+ trails = catcherArea.OfType().Single();
+ catcherArea.MovableCatcher.SetHyperDashState(2);
+ });
+
+ AddUntilStep("catcher colour is correct", () => catcherArea.MovableCatcher.Colour == expectedCatcherColour);
+
+ AddAssert("catcher trails colours are correct", () => trails.HyperDashTrailsColour == expectedCatcherColour);
+ AddAssert("catcher end-glow colours are correct", () => trails.EndGlowSpritesColour == (expectedEndGlowColour ?? expectedCatcherColour));
+
+ AddStep("finish hyper-dashing", () =>
+ {
+ catcherArea.MovableCatcher.SetHyperDashState(1);
+ catcherArea.MovableCatcher.FinishTransforms();
+ });
+
+ AddAssert("catcher colour returned to white", () => catcherArea.MovableCatcher.Colour == Color4.White);
+ }
+
private void checkHyperDashFruitColour(ISkin skin, Color4 expectedColour)
{
DrawableFruit drawableFruit = null;
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index 90a6e609f0..0de2060e2d 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
switch (obj)
{
- case IHasCurve curveData:
+ case IHasPathWithRepeats curveData:
return new JuiceStream
{
StartTime = obj.StartTime,
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0
}.Yield();
- case IHasEndTime endTime:
+ case IHasDuration endTime:
return new BananaShower
{
StartTime = obj.StartTime,
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index d99325ff87..a317ef252d 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchDifficultyCalculator : DifficultyCalculator
{
- private const double star_scaling_factor = 0.145;
+ private const double star_scaling_factor = 0.153;
protected override int SectionLength => 750;
@@ -73,6 +73,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f;
+ // For circle sizes above 5.5, reduce the catcher width further to simulate imperfect gameplay.
+ halfCatcherWidth *= 1 - (Math.Max(0, beatmap.BeatmapInfo.BaseDifficulty.CircleSize - 5.5f) * 0.0625f);
+
return new Skill[]
{
new Movement(halfCatcherWidth),
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
index a6283eb7c4..2ee7cea645 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
@@ -4,12 +4,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
-using osu.Game.Scoring.Legacy;
namespace osu.Game.Rulesets.Catch.Difficulty
{
@@ -34,11 +34,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
mods = Score.Mods;
- fruitsHit = Score?.GetCount300() ?? Score.Statistics[HitResult.Perfect];
- ticksHit = Score?.GetCount100() ?? 0;
- tinyTicksHit = Score?.GetCount50() ?? 0;
- tinyTicksMissed = Score?.GetCountKatu() ?? 0;
- misses = Score.Statistics[HitResult.Miss];
+ fruitsHit = Score.Statistics.GetOrDefault(HitResult.Perfect);
+ ticksHit = Score.Statistics.GetOrDefault(HitResult.LargeTickHit);
+ tinyTicksHit = Score.Statistics.GetOrDefault(HitResult.SmallTickHit);
+ tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss);
+ misses = Score.Statistics.GetOrDefault(HitResult.Miss);
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))
@@ -52,8 +52,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
// Longer maps are worth more
double lengthBonus =
- 0.95 + 0.4 * Math.Min(1.0, numTotalHits / 3000.0) +
- (numTotalHits > 3000 ? Math.Log10(numTotalHits / 3000.0) * 0.5 : 0.0);
+ 0.95 + 0.3 * Math.Min(1.0, numTotalHits / 2500.0) +
+ (numTotalHits > 2500 ? Math.Log10(numTotalHits / 2500.0) * 0.475 : 0.0);
// Longer maps are worth more
value *= lengthBonus;
@@ -63,19 +63,28 @@ namespace osu.Game.Rulesets.Catch.Difficulty
// Combo scaling
if (Attributes.MaxCombo > 0)
- value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
+ value *= Math.Min(Math.Pow(Score.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
+ double approachRate = Attributes.ApproachRate;
double approachRateFactor = 1.0;
- if (Attributes.ApproachRate > 9.0)
- approachRateFactor += 0.1 * (Attributes.ApproachRate - 9.0); // 10% for each AR above 9
- else if (Attributes.ApproachRate < 8.0)
- approachRateFactor += 0.025 * (8.0 - Attributes.ApproachRate); // 2.5% for each AR below 8
+ if (approachRate > 9.0)
+ approachRateFactor += 0.1 * (approachRate - 9.0); // 10% for each AR above 9
+ if (approachRate > 10.0)
+ approachRateFactor += 0.1 * (approachRate - 10.0); // Additional 10% at AR 11, 30% total
+ else if (approachRate < 8.0)
+ approachRateFactor += 0.025 * (8.0 - approachRate); // 2.5% for each AR below 8
value *= approachRateFactor;
if (mods.Any(m => m is ModHidden))
- // Hiddens gives nothing on max approach rate, and more the lower it is
+ {
value *= 1.05 + 0.075 * (10.0 - Math.Min(10.0, Attributes.ApproachRate)); // 7.5% for each AR below 10
+ // Hiddens gives almost nothing on max approach rate, and more the lower it is
+ if (approachRate <= 10.0)
+ value *= 1.05 + 0.075 * (10.0 - approachRate); // 7.5% for each AR below 10
+ else if (approachRate > 10.0)
+ value *= 1.01 + 0.04 * (11.0 - Math.Min(11.0, approachRate)); // 5% at AR 10, 1% at AR 11
+ }
if (mods.Any(m => m is ModFlashlight))
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
@@ -91,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
return value;
}
- private float accuracy() => totalHits() == 0 ? 0 : Math.Clamp((float)totalSuccessfulHits() / totalHits(), 0, 1);
+ private double accuracy() => totalHits() == 0 ? 0 : Math.Clamp((double)totalSuccessfulHits() / totalHits(), 0, 1);
private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed;
private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit;
private int totalComboHits() => misses + ticksHit + fruitsHit;
diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs
index 24e526ed19..360af1a8c9 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs
@@ -21,10 +21,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
public readonly float LastNormalizedPosition;
///
- /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms.
+ /// Milliseconds elapsed since the start time of the previous , with a minimum of 40ms.
///
public readonly double StrainTime;
+ public readonly double ClockRate;
+
public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth)
: base(hitObject, lastObject, clockRate)
{
@@ -34,8 +36,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
NormalizedPosition = BaseObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
LastNormalizedPosition = LastObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
- // Every strain interval is hard capped at the equivalent of 600 BPM streaming speed as a safety measure
- StrainTime = Math.Max(25, DeltaTime);
+ // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
+ StrainTime = Math.Max(40, DeltaTime);
+ ClockRate = clockRate;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
index fd164907e0..5cd2f1f581 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
@@ -13,9 +13,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
{
private const float absolute_player_positioning_error = 16f;
private const float normalized_hitobject_radius = 41.0f;
- private const double direction_change_bonus = 12.5;
+ private const double direction_change_bonus = 21.0;
- protected override double SkillMultiplier => 850;
+ protected override double SkillMultiplier => 900;
protected override double StrainDecayBase => 0.2;
protected override double DecayWeight => 0.94;
@@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
private float? lastPlayerPosition;
private float lastDistanceMoved;
+ private double lastStrainTime;
public Movement(float halfCatcherWidth)
{
@@ -45,47 +46,47 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
float distanceMoved = playerPosition - lastPlayerPosition.Value;
- double distanceAddition = Math.Pow(Math.Abs(distanceMoved), 1.3) / 500;
- double sqrtStrain = Math.Sqrt(catchCurrent.StrainTime);
+ double weightedStrainTime = catchCurrent.StrainTime + 13 + (3 / catchCurrent.ClockRate);
- double bonus = 0;
+ double distanceAddition = (Math.Pow(Math.Abs(distanceMoved), 1.3) / 510);
+ double sqrtStrain = Math.Sqrt(weightedStrainTime);
- // Direction changes give an extra point!
+ double edgeDashBonus = 0;
+
+ // Direction change bonus.
if (Math.Abs(distanceMoved) > 0.1)
{
if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved))
{
- double bonusFactor = Math.Min(absolute_player_positioning_error, Math.Abs(distanceMoved)) / absolute_player_positioning_error;
+ double bonusFactor = Math.Min(50, Math.Abs(distanceMoved)) / 50;
+ double antiflowFactor = Math.Max(Math.Min(70, Math.Abs(lastDistanceMoved)) / 70, 0.38);
- distanceAddition += direction_change_bonus / sqrtStrain * bonusFactor;
-
- // Bonus for tougher direction switches and "almost" hyperdashes at this point
- if (catchCurrent.LastObject.DistanceToHyperDash <= 10 / CatchPlayfield.BASE_WIDTH)
- bonus = 0.3 * bonusFactor;
+ distanceAddition += direction_change_bonus / Math.Sqrt(lastStrainTime + 16) * bonusFactor * antiflowFactor * Math.Max(1 - Math.Pow(weightedStrainTime / 1000, 3), 0);
}
// Base bonus for every movement, giving some weight to streams.
- distanceAddition += 7.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain;
+ distanceAddition += 12.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain;
}
- // Bonus for "almost" hyperdashes at corner points
- if (catchCurrent.LastObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
+ // Bonus for edge dashes.
+ if (catchCurrent.LastObject.DistanceToHyperDash <= 20.0f / CatchPlayfield.BASE_WIDTH)
{
if (!catchCurrent.LastObject.HyperDash)
- bonus += 1.0;
+ edgeDashBonus += 5.7;
else
{
// After a hyperdash we ARE in the correct position. Always!
playerPosition = catchCurrent.NormalizedPosition;
}
- distanceAddition *= 1.0 + bonus * ((10 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10);
+ distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values
}
lastPlayerPosition = playerPosition;
lastDistanceMoved = distanceMoved;
+ lastStrainTime = catchCurrent.StrainTime;
- return distanceAddition / catchCurrent.StrainTime;
+ return distanceAddition / weightedStrainTime;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
index 16414261a5..c1d24395e4 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Catch.Mods
RelativeSizeAxes = Axes.Both;
}
- //disable keyboard controls
+ // disable keyboard controls
public bool OnPressed(CatchAction action) => true;
public void OnReleased(CatchAction action)
diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
index c3488aec11..04a995c77e 100644
--- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
@@ -1,12 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Threading;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Catch.Objects
{
- public class BananaShower : CatchHitObject, IHasEndTime
+ public class BananaShower : CatchHitObject, IHasDuration
{
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
@@ -14,13 +15,13 @@ namespace osu.Game.Rulesets.Catch.Objects
public override Judgement CreateJudgement() => new IgnoreJudgement();
- protected override void CreateNestedHitObjects()
+ protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{
- base.CreateNestedHitObjects();
- createBananas();
+ base.CreateNestedHitObjects(cancellationToken);
+ createBananas(cancellationToken);
}
- private void createBananas()
+ private void createBananas(CancellationToken cancellationToken)
{
double spacing = Duration;
while (spacing > 100)
@@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Catch.Objects
for (double i = StartTime; i <= EndTime; i += spacing)
{
+ cancellationToken.ThrowIfCancellationRequested();
+
AddNested(new Banana
{
Samples = Samples,
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index 01011645bd..2c96ee2b19 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
+using System.Threading;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@@ -13,7 +14,7 @@ using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Catch.Objects
{
- public class JuiceStream : CatchHitObject, IHasCurve
+ public class JuiceStream : CatchHitObject, IHasPathWithRepeats
{
///
/// Positional distance that results in a duration of one second, before any speed adjustments.
@@ -45,9 +46,9 @@ namespace osu.Game.Rulesets.Catch.Objects
TickDistance = scoringDistance / difficulty.SliderTickRate;
}
- protected override void CreateNestedHitObjects()
+ protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{
- base.CreateNestedHitObjects();
+ base.CreateNestedHitObjects(cancellationToken);
var dropletSamples = Samples.Select(s => new HitSampleInfo
{
@@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Catch.Objects
SliderEventDescriptor? lastEvent = null;
- foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset))
+ foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken))
{
// generate tiny droplets since the last point
if (lastEvent != null)
@@ -73,6 +74,8 @@ namespace osu.Game.Rulesets.Catch.Objects
for (double t = timeBetweenTiny; t < sinceLastTick; t += timeBetweenTiny)
{
+ cancellationToken.ThrowIfCancellationRequested();
+
AddNested(new TinyDroplet
{
StartTime = t + lastEvent.Value.Time,
@@ -112,15 +115,15 @@ namespace osu.Game.Rulesets.Catch.Objects
}
}
- public double EndTime
+ public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH;
+
+ public double Duration
{
- get => StartTime + this.SpanCount() * Path.Distance / Velocity;
+ get => this.SpanCount() * Path.Distance / Velocity;
set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
}
- public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH;
-
- public double Duration => EndTime - StartTime;
+ public double EndTime => StartTime + Duration;
private readonly SliderPath path = new SliderPath();
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
index b90b5812a6..7a33cb0577 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Replays
if (lastPosition - catcher_width_half < h.X && lastPosition + catcher_width_half > h.X)
{
- //we are already in the correct range.
+ // we are already in the correct range.
lastTime = h.StartTime;
addFrame(h.StartTime, lastPosition);
return;
@@ -72,14 +72,14 @@ namespace osu.Game.Rulesets.Catch.Replays
}
else if (dashRequired)
{
- //we do a movement in two parts - the dash part then the normal part...
+ // we do a movement in two parts - the dash part then the normal part...
double timeAtNormalSpeed = positionChange / movement_speed;
double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable;
double timeAtDashSpeed = timeWeNeedToSave / 2;
float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable);
- //dash movement
+ // dash movement
addFrame(h.StartTime - timeAvailable + 1, lastPosition, true);
addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition);
addFrame(h.StartTime, h.X);
diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
index 4a87eb95e7..954f2dfc5f 100644
--- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Skinning
{
private readonly ISkin source;
- public CatchLegacySkinTransformer(ISkinSource source)
+ public CatchLegacySkinTransformer(ISkin source)
{
this.source = source;
}
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index daf9456919..9cce46d730 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -3,26 +3,37 @@
using System;
using System.Linq;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.UI
{
- public class Catcher : Container, IKeyBindingHandler
+ public class Catcher : SkinReloadableDrawable, IKeyBindingHandler
{
+ ///
+ /// The default colour used to tint hyper-dash fruit, along with the moving catcher, its trail
+ /// and end glow/after-image during a hyper-dash.
+ ///
public static readonly Color4 DEFAULT_HYPER_DASH_COLOUR = Color4.Red;
+ ///
+ /// The duration between transitioning to hyper-dash state.
+ ///
+ public const double HYPER_DASH_TRANSITION_DURATION = 180;
+
///
/// Whether we are hyper-dashing or not.
///
@@ -35,7 +46,10 @@ namespace osu.Game.Rulesets.Catch.UI
public Container ExplodingFruitTarget;
- public Container AdditiveTarget;
+ [NotNull]
+ private readonly Container trailsTarget;
+
+ private CatcherTrailDisplay trails;
public CatcherAnimationState CurrentState { get; private set; }
@@ -44,33 +58,23 @@ namespace osu.Game.Rulesets.Catch.UI
///
private const float allowed_catch_range = 0.8f;
- protected bool Dashing
+ ///
+ /// The drawable catcher for .
+ ///
+ internal Drawable CurrentDrawableCatcher => currentCatcher.Drawable;
+
+ private bool dashing;
+
+ public bool Dashing
{
get => dashing;
- set
+ protected set
{
if (value == dashing) return;
dashing = value;
- Trail |= dashing;
- }
- }
-
- ///
- /// Activate or deactivate the trail. Will be automatically deactivated when conditions to keep the trail displayed are no longer met.
- ///
- protected bool Trail
- {
- get => trail;
- set
- {
- if (value == trail || AdditiveTarget == null) return;
-
- trail = value;
-
- if (Trail)
- beginTrail();
+ updateTrailVisibility();
}
}
@@ -87,18 +91,19 @@ namespace osu.Game.Rulesets.Catch.UI
private CatcherSprite currentCatcher;
+ private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR;
+ private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR;
+
private int currentDirection;
- private bool dashing;
-
- private bool trail;
-
private double hyperDashModifier = 1;
private int hyperDashDirection;
private float hyperDashTargetPosition;
- public Catcher(BeatmapDifficulty difficulty = null)
+ public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null)
{
+ this.trailsTarget = trailsTarget;
+
RelativePositionAxes = Axes.X;
X = 0.5f;
@@ -114,7 +119,7 @@ namespace osu.Game.Rulesets.Catch.UI
[BackgroundDependencyLoader]
private void load()
{
- Children = new Drawable[]
+ InternalChildren = new Drawable[]
{
caughtFruit = new Container
{
@@ -138,6 +143,8 @@ namespace osu.Game.Rulesets.Catch.UI
}
};
+ trailsTarget.Add(trails = new CatcherTrailDisplay(this));
+
updateCatcher();
}
@@ -185,7 +192,7 @@ namespace osu.Game.Rulesets.Catch.UI
caughtFruit.Add(fruit);
- Add(new HitExplosion(fruit)
+ AddInternal(new HitExplosion(fruit)
{
X = fruit.X,
Scale = new Vector2(fruit.HitObject.Scale)
@@ -240,8 +247,6 @@ namespace osu.Game.Rulesets.Catch.UI
/// When this catcher crosses this position, this catcher ends hyper-dashing.
public void SetHyperDashState(double modifier = 1, float targetPosition = -1)
{
- const float hyper_dash_transition_length = 180;
-
var wasHyperDashing = HyperDashing;
if (modifier <= 1 || X == targetPosition)
@@ -250,11 +255,7 @@ namespace osu.Game.Rulesets.Catch.UI
hyperDashDirection = 0;
if (wasHyperDashing)
- {
- this.FadeColour(Color4.White, hyper_dash_transition_length, Easing.OutQuint);
- this.FadeTo(1, hyper_dash_transition_length, Easing.OutQuint);
- Trail &= Dashing;
- }
+ runHyperDashStateTransition(false);
}
else
{
@@ -264,20 +265,32 @@ namespace osu.Game.Rulesets.Catch.UI
if (!wasHyperDashing)
{
- this.FadeColour(Color4.OrangeRed, hyper_dash_transition_length, Easing.OutQuint);
- this.FadeTo(0.2f, hyper_dash_transition_length, Easing.OutQuint);
- Trail = true;
-
- var hyperDashEndGlow = createAdditiveSprite();
-
- hyperDashEndGlow.MoveToOffset(new Vector2(0, -10), 1200, Easing.In);
- hyperDashEndGlow.ScaleTo(hyperDashEndGlow.Scale * 0.95f).ScaleTo(hyperDashEndGlow.Scale * 1.2f, 1200, Easing.In);
- hyperDashEndGlow.FadeOut(1200);
- hyperDashEndGlow.Expire(true);
+ trails.DisplayEndGlow();
+ runHyperDashStateTransition(true);
}
}
}
+ private void runHyperDashStateTransition(bool hyperDashing)
+ {
+ trails.HyperDashTrailsColour = hyperDashColour;
+ trails.EndGlowSpritesColour = hyperDashEndGlowColour;
+ updateTrailVisibility();
+
+ if (hyperDashing)
+ {
+ this.FadeColour(hyperDashColour, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
+ this.FadeTo(0.2f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
+ }
+ else
+ {
+ this.FadeColour(Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
+ this.FadeTo(1f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
+ }
+ }
+
+ private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing;
+
public bool OnPressed(CatchAction action)
{
switch (action)
@@ -366,6 +379,21 @@ namespace osu.Game.Rulesets.Catch.UI
});
}
+ protected override void SkinChanged(ISkinSource skin, bool allowFallback)
+ {
+ base.SkinChanged(skin, allowFallback);
+
+ hyperDashColour =
+ skin.GetConfig(CatchSkinColour.HyperDash)?.Value ??
+ DEFAULT_HYPER_DASH_COLOUR;
+
+ hyperDashEndGlowColour =
+ skin.GetConfig(CatchSkinColour.HyperDashAfterImage)?.Value ??
+ hyperDashColour;
+
+ runHyperDashStateTransition(HyperDashing);
+ }
+
protected override void Update()
{
base.Update();
@@ -411,22 +439,6 @@ namespace osu.Game.Rulesets.Catch.UI
(currentCatcher.Drawable as IFramedAnimation)?.GotoFrame(0);
}
- private void beginTrail()
- {
- if (!dashing && !HyperDashing)
- {
- Trail = false;
- return;
- }
-
- var additive = createAdditiveSprite();
-
- additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
- additive.Expire(true);
-
- Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
- }
-
private void updateState(CatcherAnimationState state)
{
if (CurrentState == state)
@@ -436,25 +448,6 @@ namespace osu.Game.Rulesets.Catch.UI
updateCatcher();
}
- private CatcherTrailSprite createAdditiveSprite()
- {
- var tex = (currentCatcher.Drawable as TextureAnimation)?.CurrentFrame ?? ((Sprite)currentCatcher.Drawable).Texture;
-
- var sprite = new CatcherTrailSprite(tex)
- {
- Anchor = Anchor,
- Scale = Scale,
- Colour = HyperDashing ? Color4.Red : Color4.White,
- Blending = BlendingParameters.Additive,
- RelativePositionAxes = RelativePositionAxes,
- Position = Position
- };
-
- AdditiveTarget?.Add(sprite);
-
- return sprite;
- }
-
private void removeFromPlateWithTransform(DrawableHitObject fruit, Action action)
{
if (ExplodingFruitTarget != null)
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index e0d9ff759d..37d177b936 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -33,10 +33,7 @@ namespace osu.Game.Rulesets.Catch.UI
{
RelativeSizeAxes = Axes.X;
Height = CATCHER_SIZE;
- Child = MovableCatcher = new Catcher(difficulty)
- {
- AdditiveTarget = this,
- };
+ Child = MovableCatcher = new Catcher(this, difficulty);
}
public static float GetCatcherSize(BeatmapDifficulty difficulty)
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs
new file mode 100644
index 0000000000..bab3cb748b
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs
@@ -0,0 +1,135 @@
+// 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 JetBrains.Annotations;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Animations;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ ///
+ /// Represents a component responsible for displaying
+ /// the appropriate catcher trails when requested to.
+ ///
+ public class CatcherTrailDisplay : CompositeDrawable
+ {
+ private readonly Catcher catcher;
+
+ private readonly Container dashTrails;
+ private readonly Container hyperDashTrails;
+ private readonly Container endGlowSprites;
+
+ private Color4 hyperDashTrailsColour;
+
+ public Color4 HyperDashTrailsColour
+ {
+ get => hyperDashTrailsColour;
+ set
+ {
+ if (hyperDashTrailsColour == value)
+ return;
+
+ hyperDashTrailsColour = value;
+ hyperDashTrails.FadeColour(hyperDashTrailsColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
+ }
+ }
+
+ private Color4 endGlowSpritesColour;
+
+ public Color4 EndGlowSpritesColour
+ {
+ get => endGlowSpritesColour;
+ set
+ {
+ if (endGlowSpritesColour == value)
+ return;
+
+ endGlowSpritesColour = value;
+ endGlowSprites.FadeColour(endGlowSpritesColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
+ }
+ }
+
+ private bool trail;
+
+ ///
+ /// Whether to start displaying trails following the catcher.
+ ///
+ public bool DisplayTrail
+ {
+ get => trail;
+ set
+ {
+ if (trail == value)
+ return;
+
+ trail = value;
+
+ if (trail)
+ displayTrail();
+ }
+ }
+
+ public CatcherTrailDisplay([NotNull] Catcher catcher)
+ {
+ this.catcher = catcher ?? throw new ArgumentNullException(nameof(catcher));
+
+ RelativeSizeAxes = Axes.Both;
+
+ InternalChildren = new[]
+ {
+ dashTrails = new Container { RelativeSizeAxes = Axes.Both },
+ hyperDashTrails = new Container { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
+ endGlowSprites = new Container { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
+ };
+ }
+
+ ///
+ /// Displays a single end-glow catcher sprite.
+ ///
+ public void DisplayEndGlow()
+ {
+ var endGlow = createTrailSprite(endGlowSprites);
+
+ endGlow.MoveToOffset(new Vector2(0, -10), 1200, Easing.In);
+ endGlow.ScaleTo(endGlow.Scale * 0.95f).ScaleTo(endGlow.Scale * 1.2f, 1200, Easing.In);
+ endGlow.FadeOut(1200);
+ endGlow.Expire(true);
+ }
+
+ private void displayTrail()
+ {
+ if (!DisplayTrail)
+ return;
+
+ var sprite = createTrailSprite(catcher.HyperDashing ? hyperDashTrails : dashTrails);
+
+ sprite.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
+ sprite.Expire(true);
+
+ Scheduler.AddDelayed(displayTrail, catcher.HyperDashing ? 25 : 50);
+ }
+
+ private CatcherTrailSprite createTrailSprite(Container target)
+ {
+ var texture = (catcher.CurrentDrawableCatcher as TextureAnimation)?.CurrentFrame ?? ((Sprite)catcher.CurrentDrawableCatcher).Texture;
+
+ var sprite = new CatcherTrailSprite(texture)
+ {
+ Anchor = catcher.Anchor,
+ Scale = catcher.Scale,
+ Blending = BlendingParameters.Additive,
+ RelativePositionAxes = catcher.RelativePositionAxes,
+ Position = catcher.Position
+ };
+
+ target.Add(sprite);
+
+ return sprite;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs
index aac77c9c1c..0fe4a3c669 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs
@@ -7,20 +7,18 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
-using osu.Game.Rulesets.Mania.Edit;
+using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
-using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests
{
- [Cached(Type = typeof(IManiaHitObjectComposer))]
- public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene, IManiaHitObjectComposer
+ public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene
{
private readonly Column column;
@@ -43,12 +41,18 @@ namespace osu.Game.Rulesets.Mania.Tests
});
}
+ protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint)
+ {
+ var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
+ var pos = column.ScreenSpacePositionAtTime(time);
+
+ return new SnapResult(pos, time, column);
+ }
+
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };
protected override void AddHitObject(DrawableHitObject hitObject) => column.Add((DrawableManiaHitObject)hitObject);
- public Column ColumnAt(Vector2 screenSpacePosition) => column;
-
- public int TotalColumns => 1;
+ public ManiaPlayfield Playfield => null;
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs
index b598893e8c..149f6582ab 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs
@@ -4,25 +4,20 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Timing;
-using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Tests.Visual;
-using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests
{
- [Cached(Type = typeof(IManiaHitObjectComposer))]
- public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene, IManiaHitObjectComposer
+ public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene
{
[Cached(Type = typeof(IAdjustableClock))]
private readonly IAdjustableClock clock = new StopwatchClock();
- private readonly Column column;
-
protected ManiaSelectionBlueprintTestScene()
{
- Add(column = new Column(0)
+ Add(new Column(0)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -31,8 +26,6 @@ namespace osu.Game.Rulesets.Mania.Tests
});
}
- public Column ColumnAt(Vector2 screenSpacePosition) => column;
-
- public int TotalColumns => 1;
+ public ManiaPlayfield Playfield => null;
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-left.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-left.png
new file mode 100644
index 0000000000..03ca371c4e
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-left.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-right.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-right.png
new file mode 100644
index 0000000000..45b7be0255
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-right.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs
index a3c1d518c5..1d84a2dfcb 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs
@@ -1,15 +1,12 @@
// 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.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Rulesets.UI.Scrolling.Algorithms;
using osu.Game.Tests.Visual;
@@ -27,13 +24,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
[Cached(Type = typeof(IScrollingInfo))]
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(ManiaRuleset),
- typeof(ManiaLegacySkinTransformer),
- typeof(ManiaSettingsSubsection)
- };
-
protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset();
protected ManiaSkinnableTestScene()
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
index 6ab8a68176..497b80950a 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
@@ -2,7 +2,6 @@
// 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;
@@ -15,12 +14,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public class TestSceneDrawableJudgement : ManiaSkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(DrawableJudgement),
- typeof(DrawableManiaJudgement)
- };
-
public TestSceneDrawableJudgement()
{
foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1))
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs
index 5f046574ba..a692c0b697 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs
@@ -1,15 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
-using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Skinning;
@@ -21,12 +18,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
[TestFixture]
public class TestSceneHitExplosion : ManiaSkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(DrawableNote),
- typeof(DrawableManiaHitObject),
- };
-
public TestSceneHitExplosion()
{
int runcount = 0;
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs
index c8f901285a..7e80419944 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs
@@ -1,12 +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 System;
-using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Skinning;
using osuTK;
@@ -15,12 +12,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public class TestSceneKeyArea : ManiaSkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(DefaultKeyArea),
- typeof(LegacyKeyArea)
- };
-
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs
index a8fc68188a..87c84cf89c 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs
@@ -1,12 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
-using System.Collections.Generic;
-using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Skinning;
@@ -14,12 +10,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public class TestSceneStageBackground : ManiaSkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[]
- {
- typeof(DefaultStageBackground),
- typeof(LegacyStageBackground),
- }).ToList();
-
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs
index d436445b59..4e99068ed5 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs
@@ -1,23 +1,14 @@
// 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.Allocation;
using osu.Framework.Graphics;
-using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public class TestSceneStageForeground : ManiaSkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[]
- {
- typeof(LegacyStageForeground),
- }).ToList();
-
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs
index 5e06002f41..d9b1ad22fa 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs
@@ -12,7 +12,6 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
-using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
@@ -24,15 +23,6 @@ namespace osu.Game.Rulesets.Mania.Tests
[TestFixture]
public class TestSceneColumn : ManiaInputTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(Column),
- typeof(ColumnBackground),
- typeof(ColumnHitObjectArea),
- typeof(DefaultKeyArea),
- typeof(DefaultHitTarget)
- };
-
[Cached(typeof(IReadOnlyList))]
private IReadOnlyList mods { get; set; } = Array.Empty();
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs
new file mode 100644
index 0000000000..639be0bc11
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs
@@ -0,0 +1,124 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Edit;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Screens.Edit;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public class TestSceneManiaBeatSnapGrid : EditorClockTestScene
+ {
+ [Cached(typeof(IScrollingInfo))]
+ private ScrollingTestContainer.TestScrollingInfo scrollingInfo = new ScrollingTestContainer.TestScrollingInfo();
+
+ [Cached(typeof(EditorBeatmap))]
+ private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition()));
+
+ private readonly ManiaBeatSnapGrid beatSnapGrid;
+
+ public TestSceneManiaBeatSnapGrid()
+ {
+ editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 200 });
+ editorBeatmap.ControlPointInfo.Add(10000, new TimingControlPoint { BeatLength = 200 });
+
+ BeatDivisor.Value = 3;
+
+ // Some sane defaults
+ scrollingInfo.Algorithm.Algorithm = ScrollVisualisationMethod.Constant;
+ scrollingInfo.Direction.Value = ScrollingDirection.Up;
+ scrollingInfo.TimeRange.Value = 1000;
+
+ Children = new Drawable[]
+ {
+ Playfield = new ManiaPlayfield(new List
+ {
+ new StageDefinition { Columns = 4 },
+ new StageDefinition { Columns = 3 }
+ })
+ {
+ Clock = new FramedClock(new StopwatchClock())
+ },
+ new TestHitObjectComposer(Playfield)
+ {
+ Child = beatSnapGrid = new ManiaBeatSnapGrid()
+ }
+ };
+ }
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ // We're providing a constant scroll algorithm.
+ float relativePosition = Playfield.Stages[0].HitObjectContainer.ToLocalSpace(e.ScreenSpaceMousePosition).Y / Playfield.Stages[0].HitObjectContainer.DrawHeight;
+ double timeValue = scrollingInfo.TimeRange.Value * relativePosition;
+
+ beatSnapGrid.SelectionTimeRange = (timeValue, timeValue);
+
+ return true;
+ }
+
+ public ManiaPlayfield Playfield { get; }
+ }
+
+ public class TestHitObjectComposer : HitObjectComposer
+ {
+ public override Playfield Playfield { get; }
+ public override IEnumerable HitObjects => Enumerable.Empty();
+ public override bool CursorInPlacementArea => false;
+
+ public TestHitObjectComposer(Playfield playfield)
+ {
+ Playfield = playfield;
+ }
+
+ public Drawable Child
+ {
+ set => InternalChild = value;
+ }
+
+ public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public override float GetBeatSnapDistanceAt(double referenceTime)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public override float DurationToDistance(double referenceTime, double duration)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public override double DistanceToDuration(double referenceTime, float distance)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public override double GetSnappedDurationFromDistance(double referenceTime, float distance)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public override float GetSnappedDistanceFromDistance(double referenceTime, float distance)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs
index 286e3f6e50..1a3fa29d4a 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.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;
-using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -10,10 +8,13 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
+using osu.Framework.Utils;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Edit;
+using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
@@ -26,11 +27,6 @@ namespace osu.Game.Rulesets.Mania.Tests
{
public class TestSceneManiaHitObjectComposer : EditorClockTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(ManiaBlueprintContainer)
- };
-
private TestComposer composer;
[SetUp]
@@ -46,11 +42,15 @@ namespace osu.Game.Rulesets.Mania.Tests
public void TestDragOffscreenSelectionVerticallyUpScroll()
{
DrawableHitObject lastObject = null;
+ double originalTime = 0;
Vector2 originalPosition = Vector2.Zero;
+ setScrollStep(ScrollingDirection.Up);
+
AddStep("seek to last object", () =>
{
lastObject = this.ChildrenOfType().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
+ originalTime = lastObject.HitObject.StartTime;
Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
});
@@ -66,26 +66,28 @@ namespace osu.Game.Rulesets.Mania.Tests
AddStep("move mouse downwards", () =>
{
- InputManager.MoveMouseTo(lastObject, new Vector2(0, 20));
+ InputManager.MoveMouseTo(lastObject, new Vector2(0, lastObject.ScreenSpaceDrawQuad.Height * 4));
InputManager.ReleaseButton(MouseButton.Left);
});
AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0));
AddAssert("hitobjects moved downwards", () => lastObject.DrawPosition.Y - originalPosition.Y > 0);
- AddAssert("hitobjects not moved too far", () => lastObject.DrawPosition.Y - originalPosition.Y < 50);
+ AddAssert("hitobject has moved time", () => lastObject.HitObject.StartTime == originalTime + 125);
}
[Test]
public void TestDragOffscreenSelectionVerticallyDownScroll()
{
DrawableHitObject lastObject = null;
+ double originalTime = 0;
Vector2 originalPosition = Vector2.Zero;
- AddStep("set down scroll", () => ((Bindable)composer.Composer.ScrollingInfo.Direction).Value = ScrollingDirection.Down);
+ setScrollStep(ScrollingDirection.Down);
AddStep("seek to last object", () =>
{
lastObject = this.ChildrenOfType().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
+ originalTime = lastObject.HitObject.StartTime;
Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
});
@@ -101,13 +103,13 @@ namespace osu.Game.Rulesets.Mania.Tests
AddStep("move mouse upwards", () =>
{
- InputManager.MoveMouseTo(lastObject, new Vector2(0, -20));
+ InputManager.MoveMouseTo(lastObject, new Vector2(0, -lastObject.ScreenSpaceDrawQuad.Height * 4));
InputManager.ReleaseButton(MouseButton.Left);
});
AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0));
AddAssert("hitobjects moved upwards", () => originalPosition.Y - lastObject.DrawPosition.Y > 0);
- AddAssert("hitobjects not moved too far", () => originalPosition.Y - lastObject.DrawPosition.Y < 50);
+ AddAssert("hitobject has moved time", () => lastObject.HitObject.StartTime == originalTime + 125);
}
[Test]
@@ -116,6 +118,8 @@ namespace osu.Game.Rulesets.Mania.Tests
DrawableHitObject lastObject = null;
Vector2 originalPosition = Vector2.Zero;
+ setScrollStep(ScrollingDirection.Down);
+
AddStep("seek to last object", () =>
{
lastObject = this.ChildrenOfType().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
@@ -147,6 +151,46 @@ namespace osu.Game.Rulesets.Mania.Tests
AddAssert("hitobjects not moved vertically", () => lastObject.DrawPosition.Y - originalPosition.Y <= DefaultNotePiece.NOTE_HEIGHT);
}
+ [Test]
+ public void TestDragHoldNoteSelectionVertically()
+ {
+ setScrollStep(ScrollingDirection.Down);
+
+ AddStep("setup beatmap", () =>
+ {
+ composer.EditorBeatmap.Clear();
+ composer.EditorBeatmap.Add(new HoldNote
+ {
+ Column = 1,
+ EndTime = 200
+ });
+ });
+
+ DrawableHoldNote holdNote = null;
+
+ AddStep("grab hold note", () =>
+ {
+ holdNote = this.ChildrenOfType().Single();
+ InputManager.MoveMouseTo(holdNote);
+ InputManager.PressButton(MouseButton.Left);
+ });
+
+ AddStep("move drag upwards", () =>
+ {
+ InputManager.MoveMouseTo(holdNote, new Vector2(0, -100));
+ InputManager.ReleaseButton(MouseButton.Left);
+ });
+
+ AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft));
+ AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft));
+
+ AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition);
+ AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition);
+ }
+
+ private void setScrollStep(ScrollingDirection direction)
+ => AddStep($"set scroll direction = {direction}", () => ((Bindable)composer.Composer.ScrollingInfo.Direction).Value = direction);
+
private class TestComposer : CompositeDrawable
{
[Cached(typeof(EditorBeatmap))]
@@ -167,7 +211,7 @@ namespace osu.Game.Rulesets.Mania.Tests
};
for (int i = 0; i < 10; i++)
- EditorBeatmap.Add(new Note { StartTime = 100 * i });
+ EditorBeatmap.Add(new Note { StartTime = 125 * i });
}
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
index 8dae5e6d84..dd5fd93710 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.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;
-using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -30,12 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests
[TestFixture]
public class TestSceneNotes : OsuTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(DrawableNote),
- typeof(DrawableHoldNote)
- };
-
[BackgroundDependencyLoader]
private void load()
{
@@ -164,7 +156,7 @@ namespace osu.Game.Rulesets.Mania.Tests
foreach (var obj in content.OfType())
{
- if (!(obj.HitObject is IHasEndTime endTime))
+ if (!(obj.HitObject is IHasDuration endTime))
continue;
foreach (var nested in obj.NestedHitObjects)
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 1c8116754f..32abf5e7f9 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
}
else
{
- float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count;
+ float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasDuration) / beatmap.HitObjects.Count;
if (percentSliderOrSpinner < 0.2)
TargetColumns = 7;
else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5)
@@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
break;
}
- case IHasEndTime endTimeData:
+ case IHasDuration endTimeData:
{
conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap);
@@ -231,7 +231,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
var pattern = new Pattern();
- if (HitObject is IHasEndTime endTimeData)
+ if (HitObject is IHasDuration endTimeData)
{
pattern.Add(new HoldNote
{
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index d8d5b67c0e..1bd796511b 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -474,7 +474,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
///
private IList sampleInfoListAt(double time)
{
- if (!(HitObject is IHasCurve curveData))
+ if (!(HitObject is IHasPathWithRepeats curveData))
return HitObject.Samples;
double segmentTime = (EndTime - HitObject.StartTime) / spanCount;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
index 907bed0d65..d5286a3779 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap)
: base(random, hitObject, beatmap, new Pattern(), originalBeatmap)
{
- endTime = (HitObject as IHasEndTime)?.EndTime ?? 0;
+ endTime = (HitObject as IHasDuration)?.EndTime ?? 0;
}
public override IEnumerable Generate()
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
index 3f7a2baedd..91383c5548 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
@@ -37,12 +38,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
mods = Score.Mods;
scaledScore = Score.TotalScore;
- countPerfect = Score.Statistics[HitResult.Perfect];
- countGreat = Score.Statistics[HitResult.Great];
- countGood = Score.Statistics[HitResult.Good];
- countOk = Score.Statistics[HitResult.Ok];
- countMeh = Score.Statistics[HitResult.Meh];
- countMiss = Score.Statistics[HitResult.Miss];
+ countPerfect = Score.Statistics.GetOrDefault(HitResult.Perfect);
+ countGreat = Score.Statistics.GetOrDefault(HitResult.Great);
+ countGood = Score.Statistics.GetOrDefault(HitResult.Good);
+ countOk = Score.Statistics.GetOrDefault(HitResult.Ok);
+ countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
+ countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
if (mods.Any(m => !m.Ranked))
return 0;
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
index b3dd392202..b5ec1e1a2a 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
@@ -2,11 +2,15 @@
// 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.Input.Events;
+using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
+using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
@@ -16,6 +20,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
private readonly EditNotePiece headPiece;
private readonly EditNotePiece tailPiece;
+ [Resolved]
+ private IScrollingInfo scrollingInfo { get; set; }
+
public HoldNotePlacementBlueprint()
: base(new HoldNote())
{
@@ -35,8 +42,21 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
if (Column != null)
{
- headPiece.Y = PositionAt(HitObject.StartTime);
- tailPiece.Y = PositionAt(HitObject.EndTime);
+ headPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.StartTime)).Y;
+ tailPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.EndTime)).Y;
+
+ switch (scrollingInfo.Direction.Value)
+ {
+ case ScrollingDirection.Down:
+ headPiece.Y -= headPiece.DrawHeight / 2;
+ tailPiece.Y -= tailPiece.DrawHeight / 2;
+ break;
+
+ case ScrollingDirection.Up:
+ headPiece.Y += headPiece.DrawHeight / 2;
+ tailPiece.Y += tailPiece.DrawHeight / 2;
+ break;
+ }
}
var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y));
@@ -49,29 +69,37 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
protected override void OnMouseUp(MouseUpEvent e)
{
+ if (e.Button != MouseButton.Left)
+ return;
+
base.OnMouseUp(e);
EndPlacement(true);
}
private double originalStartTime;
- public override void UpdatePosition(Vector2 screenSpacePosition)
+ public override void UpdatePosition(SnapResult result)
{
- base.UpdatePosition(screenSpacePosition);
+ base.UpdatePosition(result);
if (PlacementActive)
{
- var endTime = TimeAt(screenSpacePosition);
-
- HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime;
- HitObject.Duration = Math.Abs(endTime - originalStartTime);
+ if (result.Time is double endTime)
+ {
+ HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime;
+ HitObject.Duration = Math.Abs(endTime - originalStartTime);
+ }
}
else
{
- headPiece.Width = tailPiece.Width = SnappedWidth;
- headPiece.X = tailPiece.X = SnappedMousePosition.X;
+ if (result.Playfield != null)
+ {
+ headPiece.Width = tailPiece.Width = result.Playfield.DrawWidth;
+ headPiece.X = tailPiece.X = ToLocalSpace(result.ScreenSpacePosition).X;
+ }
- originalStartTime = HitObject.StartTime = TimeAt(screenSpacePosition);
+ if (result.Time is double startTime)
+ originalStartTime = HitObject.StartTime = startTime;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
index 43d43ef252..1737c4d2e5 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
@@ -77,6 +77,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
public override Quad SelectionQuad => ScreenSpaceDrawQuad;
- public override Vector2 SelectionPoint => DrawableObject.Head.ScreenSpaceDrawQuad.Centre;
+ public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.Head.ScreenSpaceDrawQuad.Centre;
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
index 400abb6380..27a279e044 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
@@ -1,42 +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.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI;
-using osu.Game.Rulesets.UI.Scrolling;
-using osuTK;
+using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
- public abstract class ManiaPlacementBlueprint : PlacementBlueprint,
- IRequireHighFrequencyMousePosition // the playfield could be moving behind us
+ public abstract class ManiaPlacementBlueprint : PlacementBlueprint
where T : ManiaHitObject
{
protected new T HitObject => (T)base.HitObject;
- protected Column Column;
+ private Column column;
- ///
- /// The current mouse position, snapped to the closest column.
- ///
- protected Vector2 SnappedMousePosition { get; private set; }
+ public Column Column
+ {
+ get => column;
+ set
+ {
+ if (value == column)
+ return;
- ///
- /// The width of the closest column to the current mouse position.
- ///
- protected float SnappedWidth { get; private set; }
-
- [Resolved]
- private IManiaHitObjectComposer composer { get; set; }
-
- [Resolved]
- private IScrollingInfo scrollingInfo { get; set; }
+ column = value;
+ HitObject.Column = column.Index;
+ }
+ }
protected ManiaPlacementBlueprint(T hitObject)
: base(hitObject)
@@ -46,106 +38,22 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
protected override bool OnMouseDown(MouseDownEvent e)
{
- if (Column == null)
- return base.OnMouseDown(e);
+ if (e.Button != MouseButton.Left)
+ return false;
- HitObject.Column = Column.Index;
- BeginPlacement(TimeAt(e.ScreenSpaceMousePosition), true);
+ if (Column == null)
+ return false;
+
+ BeginPlacement(true);
return true;
}
- public override void UpdatePosition(Vector2 screenSpacePosition)
+ public override void UpdatePosition(SnapResult result)
{
+ base.UpdatePosition(result);
+
if (!PlacementActive)
- Column = ColumnAt(screenSpacePosition);
-
- if (Column == null) return;
-
- SnappedWidth = Column.DrawWidth;
-
- // Snap to the column
- var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0)));
- SnappedMousePosition = new Vector2(parentPos.X, Parent.ToLocalSpace(screenSpacePosition).Y);
- }
-
- protected double TimeAt(Vector2 screenSpacePosition)
- {
- if (Column == null)
- return 0;
-
- var hitObjectContainer = Column.HitObjectContainer;
-
- // If we're scrolling downwards, a position of 0 is actually further away from the hit target
- // so we need to flip the vertical coordinate in the hitobject container's space
- var hitObjectPos = mouseToHitObjectPosition(Column.HitObjectContainer.ToLocalSpace(screenSpacePosition)).Y;
- if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
- hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos;
-
- return scrollingInfo.Algorithm.TimeAt(hitObjectPos,
- EditorClock.CurrentTime,
- scrollingInfo.TimeRange.Value,
- hitObjectContainer.DrawHeight);
- }
-
- protected float PositionAt(double time)
- {
- var pos = scrollingInfo.Algorithm.PositionAt(time,
- EditorClock.CurrentTime,
- scrollingInfo.TimeRange.Value,
- Column.HitObjectContainer.DrawHeight);
-
- if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
- pos = Column.HitObjectContainer.DrawHeight - pos;
-
- return hitObjectToMousePosition(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent)).Y;
- }
-
- protected Column ColumnAt(Vector2 screenSpacePosition)
- => composer.ColumnAt(screenSpacePosition);
-
- ///
- /// Converts a mouse position to a hitobject position.
- ///
- ///
- /// Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction.
- ///
- /// The mouse position.
- /// The resulting hitobject position, acnhored at the top or bottom of the blueprint depending on the scroll direction.
- private Vector2 mouseToHitObjectPosition(Vector2 mousePosition)
- {
- switch (scrollingInfo.Direction.Value)
- {
- case ScrollingDirection.Up:
- mousePosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2;
- break;
-
- case ScrollingDirection.Down:
- mousePosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2;
- break;
- }
-
- return mousePosition;
- }
-
- ///
- /// Converts a hitobject position to a mouse position.
- ///
- /// The hitobject position.
- /// The resulting mouse position, anchored at the centre of the hitobject.
- private Vector2 hitObjectToMousePosition(Vector2 hitObjectPosition)
- {
- switch (scrollingInfo.Direction.Value)
- {
- case ScrollingDirection.Up:
- hitObjectPosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2;
- break;
-
- case ScrollingDirection.Down:
- hitObjectPosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2;
- break;
- }
-
- return hitObjectPosition;
+ Column = result.Playfield as Column;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs
index b8574b804e..384f49d9b2 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs
@@ -11,17 +11,14 @@ using osuTK;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
- public class ManiaSelectionBlueprint : OverlaySelectionBlueprint
+ public abstract class ManiaSelectionBlueprint : OverlaySelectionBlueprint
{
public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject;
[Resolved]
private IScrollingInfo scrollingInfo { get; set; }
- [Resolved]
- private IManiaHitObjectComposer composer { get; set; }
-
- public ManiaSelectionBlueprint(DrawableHitObject drawableObject)
+ protected ManiaSelectionBlueprint(DrawableHitObject drawableObject)
: base(drawableObject)
{
RelativeSizeAxes = Axes.None;
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs
index 2b7b383dbe..684004b558 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs
@@ -3,33 +3,41 @@
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects;
+using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
public class NotePlacementBlueprint : ManiaPlacementBlueprint
{
+ private readonly EditNotePiece piece;
+
public NotePlacementBlueprint()
: base(new Note())
{
- Origin = Anchor.Centre;
+ RelativeSizeAxes = Axes.Both;
- AutoSizeAxes = Axes.Y;
-
- InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X };
+ InternalChild = piece = new EditNotePiece { Origin = Anchor.Centre };
}
- protected override void Update()
+ public override void UpdatePosition(SnapResult result)
{
- base.Update();
+ base.UpdatePosition(result);
- Width = SnappedWidth;
- Position = SnappedMousePosition;
+ if (result.Playfield != null)
+ {
+ piece.Width = result.Playfield.DrawWidth;
+ piece.Position = ToLocalSpace(result.ScreenSpacePosition);
+ }
}
protected override bool OnMouseDown(MouseDownEvent e)
{
+ if (e.Button != MouseButton.Left)
+ return false;
+
base.OnMouseDown(e);
// Place the note immediately.
diff --git a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs
deleted file mode 100644
index f64bab1fae..0000000000
--- a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs
+++ /dev/null
@@ -1,15 +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.Rulesets.Mania.UI;
-using osuTK;
-
-namespace osu.Game.Rulesets.Mania.Edit
-{
- public interface IManiaHitObjectComposer
- {
- Column ColumnAt(Vector2 screenSpacePosition);
-
- int TotalColumns { get; }
- }
-}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs
new file mode 100644
index 0000000000..2028cae9a5
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs
@@ -0,0 +1,215 @@
+// 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.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Caching;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Screens.Edit;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Edit
+{
+ ///
+ /// A grid which displays coloured beat divisor lines in proximity to the selection or placement cursor.
+ ///
+ public class ManiaBeatSnapGrid : Component
+ {
+ private const double visible_range = 750;
+
+ ///
+ /// The range of time values of the current selection.
+ ///
+ public (double start, double end)? SelectionTimeRange
+ {
+ set
+ {
+ if (value == selectionTimeRange)
+ return;
+
+ selectionTimeRange = value;
+ lineCache.Invalidate();
+ }
+ }
+
+ [Resolved]
+ private EditorBeatmap beatmap { get; set; }
+
+ [Resolved]
+ private IScrollingInfo scrollingInfo { get; set; }
+
+ [Resolved]
+ private Bindable working { get; set; }
+
+ [Resolved]
+ private OsuColour colours { get; set; }
+
+ [Resolved]
+ private BindableBeatDivisor beatDivisor { get; set; }
+
+ private readonly List grids = new List();
+
+ private readonly Cached lineCache = new Cached();
+
+ private (double start, double end)? selectionTimeRange;
+
+ [BackgroundDependencyLoader]
+ private void load(HitObjectComposer composer)
+ {
+ foreach (var stage in ((ManiaPlayfield)composer.Playfield).Stages)
+ {
+ foreach (var column in stage.Columns)
+ {
+ var lineContainer = new ScrollingHitObjectContainer();
+
+ grids.Add(lineContainer);
+ column.UnderlayElements.Add(lineContainer);
+ }
+ }
+
+ beatDivisor.BindValueChanged(_ => createLines(), true);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (!lineCache.IsValid)
+ {
+ lineCache.Validate();
+ createLines();
+ }
+ }
+
+ private readonly Stack availableLines = new Stack();
+
+ private void createLines()
+ {
+ foreach (var grid in grids)
+ {
+ foreach (var line in grid.Objects.OfType())
+ availableLines.Push(line);
+
+ grid.Clear(false);
+ }
+
+ if (selectionTimeRange == null)
+ return;
+
+ var range = selectionTimeRange.Value;
+
+ var timingPoint = beatmap.ControlPointInfo.TimingPointAt(range.start - visible_range);
+
+ double time = timingPoint.Time;
+ int beat = 0;
+
+ // progress time until in the visible range.
+ while (time < range.start - visible_range)
+ {
+ time += timingPoint.BeatLength / beatDivisor.Value;
+ beat++;
+ }
+
+ while (time < range.end + visible_range)
+ {
+ var nextTimingPoint = beatmap.ControlPointInfo.TimingPointAt(time);
+
+ // switch to the next timing point if we have reached it.
+ if (nextTimingPoint.Time > timingPoint.Time)
+ {
+ beat = 0;
+ time = nextTimingPoint.Time;
+ timingPoint = nextTimingPoint;
+ }
+
+ Color4 colour = BindableBeatDivisor.GetColourFor(
+ BindableBeatDivisor.GetDivisorForBeatIndex(Math.Max(1, beat), beatDivisor.Value), colours);
+
+ foreach (var grid in grids)
+ {
+ if (!availableLines.TryPop(out var line))
+ line = new DrawableGridLine();
+
+ line.HitObject.StartTime = time;
+ line.Colour = colour;
+
+ grid.Add(line);
+ }
+
+ beat++;
+ time += timingPoint.BeatLength / beatDivisor.Value;
+ }
+
+ foreach (var grid in grids)
+ {
+ // required to update ScrollingHitObjectContainer's cache.
+ grid.UpdateSubTree();
+
+ foreach (var line in grid.Objects.OfType())
+ {
+ time = line.HitObject.StartTime;
+
+ if (time >= range.start && time <= range.end)
+ line.Alpha = 1;
+ else
+ {
+ double timeSeparation = time < range.start ? range.start - time : time - range.end;
+ line.Alpha = (float)Math.Max(0, 1 - timeSeparation / visible_range);
+ }
+ }
+ }
+ }
+
+ private class DrawableGridLine : DrawableHitObject
+ {
+ [Resolved]
+ private IScrollingInfo scrollingInfo { get; set; }
+
+ private readonly IBindable direction = new Bindable();
+
+ public DrawableGridLine()
+ : base(new HitObject())
+ {
+ RelativeSizeAxes = Axes.X;
+ Height = 2;
+
+ AddInternal(new Box { RelativeSizeAxes = Axes.Both });
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ Origin = Anchor = direction.NewValue == ScrollingDirection.Up
+ ? Anchor.TopLeft
+ : Anchor.BottomLeft;
+ }
+
+ protected override void UpdateInitialTransforms()
+ {
+ // don't perform any fading – we are handling that ourselves.
+ }
+
+ protected override void UpdateStateTransforms(ArmedState state)
+ {
+ LifetimeEnd = HitObject.StartTime + visible_range;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
index dfa933baad..7e2469a794 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
@@ -6,9 +6,14 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mania.Objects;
using System.Collections.Generic;
+using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Input;
+using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit.Compose.Components;
@@ -16,56 +21,64 @@ using osuTK;
namespace osu.Game.Rulesets.Mania.Edit
{
- [Cached(Type = typeof(IManiaHitObjectComposer))]
- public class ManiaHitObjectComposer : HitObjectComposer, IManiaHitObjectComposer
+ public class ManiaHitObjectComposer : HitObjectComposer
{
private DrawableManiaEditRuleset drawableRuleset;
+ private ManiaBeatSnapGrid beatSnapGrid;
+ private InputManager inputManager;
public ManiaHitObjectComposer(Ruleset ruleset)
: base(ruleset)
{
}
- ///
- /// Retrieves the column that intersects a screen-space position.
- ///
- /// The screen-space position.
- /// The column which intersects with .
- public Column ColumnAt(Vector2 screenSpacePosition) => drawableRuleset.GetColumnByPosition(screenSpacePosition);
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AddInternal(beatSnapGrid = new ManiaBeatSnapGrid());
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ inputManager = GetContainingInputManager();
+ }
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
- public ManiaPlayfield Playfield => ((ManiaPlayfield)drawableRuleset.Playfield);
+ public new ManiaPlayfield Playfield => ((ManiaPlayfield)drawableRuleset.Playfield);
public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo;
- public int TotalColumns => Playfield.TotalColumns;
+ protected override Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) =>
+ Playfield.GetColumnByPosition(screenSpacePosition);
- public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time)
+ public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
{
- var hoc = Playfield.GetColumn(0).HitObjectContainer;
+ var result = base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
- float targetPosition = hoc.ToLocalSpace(ToScreenSpace(position)).Y;
-
- if (drawableRuleset.ScrollingInfo.Direction.Value == ScrollingDirection.Down)
+ switch (ScrollingInfo.Direction.Value)
{
- // We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time.
- // The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position,
- // so when scrolling downwards the coordinates need to be flipped.
- targetPosition = hoc.DrawHeight - targetPosition;
+ case ScrollingDirection.Down:
+ result.ScreenSpacePosition -= new Vector2(0, getNoteHeight() / 2);
+ break;
+
+ case ScrollingDirection.Up:
+ result.ScreenSpacePosition += new Vector2(0, getNoteHeight() / 2);
+ break;
}
- double targetTime = drawableRuleset.ScrollingInfo.Algorithm.TimeAt(targetPosition,
- EditorClock.CurrentTime,
- drawableRuleset.ScrollingInfo.TimeRange.Value,
- hoc.DrawHeight);
-
- return base.GetSnappedPosition(position, targetTime);
+ return result;
}
+ private float getNoteHeight() =>
+ Playfield.GetColumn(0).ToScreenSpace(new Vector2(DefaultNotePiece.NOTE_HEIGHT)).Y -
+ Playfield.GetColumn(0).ToScreenSpace(Vector2.Zero).Y;
+
protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
{
drawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods);
@@ -76,12 +89,36 @@ namespace osu.Game.Rulesets.Mania.Edit
return drawableRuleset;
}
- protected override ComposeBlueprintContainer CreateBlueprintContainer() => new ManiaBlueprintContainer(drawableRuleset.Playfield.AllHitObjects);
+ protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects)
+ => new ManiaBlueprintContainer(hitObjects);
protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
{
new NoteCompositionTool(),
new HoldNoteCompositionTool()
};
+
+ protected override void UpdateAfterChildren()
+ {
+ base.UpdateAfterChildren();
+
+ if (BlueprintContainer.CurrentTool is SelectTool)
+ {
+ if (EditorBeatmap.SelectedHitObjects.Any())
+ {
+ beatSnapGrid.SelectionTimeRange = (EditorBeatmap.SelectedHitObjects.Min(h => h.StartTime), EditorBeatmap.SelectedHitObjects.Max(h => h.GetEndTime()));
+ }
+ else
+ beatSnapGrid.SelectionTimeRange = null;
+ }
+ else
+ {
+ var result = SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position);
+ if (result.Time is double time)
+ beatSnapGrid.SelectionTimeRange = (time, time);
+ else
+ beatSnapGrid.SelectionTimeRange = null;
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
index 55245198c8..65f40d7d0a 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
@@ -4,6 +4,7 @@
using System;
using System.Linq;
using osu.Framework.Allocation;
+using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.UI.Scrolling;
@@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Edit
private IScrollingInfo scrollingInfo { get; set; }
[Resolved]
- private IManiaHitObjectComposer composer { get; set; }
+ private HitObjectComposer composer { get; set; }
public override bool HandleMovement(MoveSelectionEvent moveEvent)
{
@@ -31,7 +32,9 @@ namespace osu.Game.Rulesets.Mania.Edit
private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent)
{
- var currentColumn = composer.ColumnAt(moveEvent.ScreenSpacePosition);
+ var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield;
+
+ var currentColumn = maniaPlayfield.GetColumnByPosition(moveEvent.ScreenSpacePosition);
if (currentColumn == null)
return;
@@ -50,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.Edit
maxColumn = obj.Column;
}
- columnDelta = Math.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn);
+ columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn);
foreach (var obj in SelectedHitObjects.OfType())
obj.Column += columnDelta;
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index eea2c31260..a100c9a58e 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Threading;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Objects
///
/// Represents a hit object which requires pressing, holding, and releasing a key.
///
- public class HoldNote : ManiaHitObject, IHasEndTime
+ public class HoldNote : ManiaHitObject, IHasDuration
{
public double EndTime
{
@@ -91,11 +92,11 @@ namespace osu.Game.Rulesets.Mania.Objects
tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate;
}
- protected override void CreateNestedHitObjects()
+ protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{
- base.CreateNestedHitObjects();
+ base.CreateNestedHitObjects(cancellationToken);
- createTicks();
+ createTicks(cancellationToken);
AddNested(Head = new Note
{
@@ -112,13 +113,15 @@ namespace osu.Game.Rulesets.Mania.Objects
});
}
- private void createTicks()
+ private void createTicks(CancellationToken cancellationToken)
{
if (tickSpacing == 0)
return;
for (double t = StartTime + tickSpacing; t <= EndTime - tickSpacing; t += tickSpacing)
{
+ cancellationToken.ThrowIfCancellationRequested();
+
AddNested(new HoldNoteTick
{
StartTime = t,
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
index 549f0f9214..289f8a00ef 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
@@ -7,5 +7,20 @@ namespace osu.Game.Rulesets.Mania.Scoring
{
public class ManiaHitWindows : HitWindows
{
+ public override bool IsHitResultAllowed(HitResult result)
+ {
+ switch (result)
+ {
+ case HitResult.Perfect:
+ case HitResult.Great:
+ case HitResult.Good:
+ case HitResult.Ok:
+ case HitResult.Meh:
+ case HitResult.Miss:
+ return true;
+ }
+
+ return false;
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs
index 7680526ac4..f177284399 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs
@@ -52,10 +52,10 @@ namespace osu.Game.Rulesets.Mania.Skinning
base.Update();
if (leftSprite?.Height > 0)
- leftSprite.Scale = new Vector2(DrawHeight / leftSprite.Height);
+ leftSprite.Scale = new Vector2(1, DrawHeight / leftSprite.Height);
if (rightSprite?.Height > 0)
- rightSprite.Scale = new Vector2(DrawHeight / rightSprite.Height);
+ rightSprite.Scale = new Vector2(1, DrawHeight / rightSprite.Height);
}
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index 506a07f26b..511d6c8623 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -37,6 +37,8 @@ namespace osu.Game.Rulesets.Mania.UI
internal readonly Container TopLevelContainer;
+ public Container UnderlayElements => hitObjectArea.UnderlayElements;
+
public Column(int index)
{
Index = index;
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
index cb79bf7f43..b365ae45a9 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
@@ -12,6 +12,9 @@ namespace osu.Game.Rulesets.Mania.UI.Components
public class ColumnHitObjectArea : HitObjectArea
{
public readonly Container Explosions;
+
+ public readonly Container UnderlayElements;
+
private readonly Drawable hitTarget;
public ColumnHitObjectArea(int columnIndex, HitObjectContainer hitObjectContainer)
@@ -19,6 +22,11 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
AddRangeInternal(new[]
{
+ UnderlayElements = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Depth = 2,
+ },
hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget, columnIndex), _ => new DefaultHitTarget())
{
RelativeSizeAxes = Axes.X,
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
index f3f843f366..94b5ee9486 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
@@ -23,7 +23,6 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
-using osuTK;
namespace osu.Game.Rulesets.Mania.UI
{
@@ -108,13 +107,6 @@ namespace osu.Game.Rulesets.Mania.UI
private void updateTimeRange() => TimeRange.Value = configTimeRange.Value * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value;
- ///
- /// Retrieves the column that intersects a screen-space position.
- ///
- /// The screen-space position.
- /// The column which intersects with .
- public Column GetColumnByPosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition);
-
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer();
protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages);
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
index 1af7d06998..271e432e8d 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
@@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.UI
[Cached]
public class ManiaPlayfield : ScrollingPlayfield
{
+ public IReadOnlyList Stages => stages;
+
private readonly List stages = new List();
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos));
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs
index 90ebbd9f04..a0a38fc47b 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs
@@ -1,21 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
-using System.Collections.Generic;
-using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
public abstract class OsuSkinnableTestScene : SkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(OsuRuleset),
- typeof(OsuLegacySkinTransformer),
- };
-
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
index f867630df6..c81edf4e07 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
@@ -2,7 +2,6 @@
// 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;
@@ -15,12 +14,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneDrawableJudgement : OsuSkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[]
- {
- typeof(DrawableJudgement),
- typeof(DrawableOsuJudgement)
- }).ToList();
-
public TestSceneDrawableJudgement()
{
foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1))
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
index 22dacc6f5e..38c2bb9b95 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
@@ -2,16 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
-using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing.Input;
using osu.Game.Configuration;
-using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.UI.Cursor;
-using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osuTK;
@@ -20,16 +16,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture]
public class TestSceneGameplayCursor : OsuSkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[]
- {
- typeof(GameplayCursorContainer),
- typeof(OsuCursorContainer),
- typeof(OsuCursor),
- typeof(LegacyCursor),
- typeof(LegacyCursorTrail),
- typeof(CursorTrail)
- }).ToList();
-
[Cached]
private GameplayBeatmap gameplayBeatmap;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
index e117729f01..37df0d6e37 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
@@ -8,8 +8,6 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osuTK;
-using System.Collections.Generic;
-using System;
using osu.Game.Rulesets.Mods;
using System.Linq;
using NUnit.Framework;
@@ -20,11 +18,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture]
public class TestSceneHitCircle : OsuSkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(DrawableHitCircle)
- };
-
private int depthIndex;
public TestSceneHitCircle()
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs
index 21ebce8c23..45125204b6 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs
@@ -1,9 +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;
-using System.Collections.Generic;
-using System.Linq;
using NUnit.Framework;
using osu.Game.Rulesets.Osu.Mods;
@@ -12,8 +9,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture]
public class TestSceneHitCircleHidden : TestSceneHitCircle
{
- public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
-
[SetUp]
public void SetUp() => Schedule(() =>
{
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
index 0ae49790cd..0d0be2953b 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
@@ -2,7 +2,6 @@
// 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;
@@ -16,7 +15,6 @@ using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
-using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Graphics;
@@ -28,18 +26,13 @@ namespace osu.Game.Rulesets.Osu.Tests
private const double beat_length = 100;
private static readonly Vector2 grid_position = new Vector2(512, 384);
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(CircularDistanceSnapGrid)
- };
-
[Cached(typeof(EditorBeatmap))]
private readonly EditorBeatmap editorBeatmap;
[Cached]
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
- [Cached(typeof(IDistanceSnapProvider))]
+ [Cached(typeof(IPositionSnapProvider))]
private readonly SnapProvider snapProvider = new SnapProvider();
private TestOsuDistanceSnapGrid grid;
@@ -179,9 +172,9 @@ namespace osu.Game.Rulesets.Osu.Tests
}
}
- private class SnapProvider : IDistanceSnapProvider
+ private class SnapProvider : IPositionSnapProvider
{
- public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time);
+ public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs
index a6c3be7e5a..c3b4d2625e 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs
@@ -399,7 +399,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public TestSlider()
{
- DefaultsApplied += () =>
+ DefaultsApplied += _ =>
{
HeadCircle.HitWindows = new TestHitWindows();
TailCircle.HitWindows = new TestHitWindows();
diff --git a/osu.Game.Rulesets.Osu.Tests/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/TestScenePathControlPointVisualiser.cs
index cbe14ff4d2..21fa283b6d 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestScenePathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestScenePathControlPointVisualiser.cs
@@ -1,10 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
-using System.Collections.Generic;
using System.Linq;
-using Humanizer;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
@@ -19,13 +16,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public class TestScenePathControlPointVisualiser : OsuTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(StringHumanizeExtensions),
- typeof(PathControlPointPiece),
- typeof(PathControlPointConnectionPiece)
- };
-
private Slider slider;
private PathControlPointVisualiser visualiser;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs
index f4809b0c9b..a7967c407a 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.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;
-using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
@@ -14,11 +12,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneResumeOverlay : OsuManualInputManagerTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(OsuResumeOverlay),
- };
-
public TestSceneResumeOverlay()
{
ManualOsuInputManager osuInputManager;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
index eb6130c8a6..a9404f665a 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
@@ -1,7 +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;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -21,29 +20,12 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
-using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
public class TestSceneSlider : OsuSkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(Slider),
- typeof(SliderTick),
- typeof(SliderTailCircle),
- typeof(SliderBall),
- typeof(SliderBody),
- typeof(SnakingSliderBody),
- typeof(DrawableSlider),
- typeof(DrawableSliderTick),
- typeof(DrawableSliderTail),
- typeof(DrawableSliderHead),
- typeof(DrawableSliderRepeat),
- typeof(DrawableOsuHitObject)
- };
-
private Container content;
protected override Container Content
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs
index d0ee1bddb5..b2bd727c6a 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs
@@ -1,9 +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;
-using System.Collections.Generic;
-using System.Linq;
using NUnit.Framework;
using osu.Game.Rulesets.Osu.Mods;
@@ -12,8 +9,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture]
public class TestSceneSliderHidden : TestSceneSlider
{
- public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
-
[SetUp]
public void SetUp() => Schedule(() =>
{
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
index b0c2e56c3e..b543b6fa94 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
@@ -1,7 +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;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@@ -13,8 +12,6 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
@@ -27,17 +24,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneSliderInput : RateAdjustedBeatmapTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(SliderBall),
- typeof(DrawableSlider),
- typeof(DrawableSliderTick),
- typeof(DrawableSliderRepeat),
- typeof(DrawableOsuHitObject),
- typeof(DrawableSliderHead),
- typeof(DrawableSliderTail),
- };
-
private const double time_before_slider = 250;
private const double time_slider_start = 1500;
private const double time_during_slide_1 = 2500;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs
index 5dd2bd18a8..d5be538d94 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.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;
-using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
@@ -22,16 +20,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneSliderSelectionBlueprint : SelectionBlueprintTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(SliderSelectionBlueprint),
- typeof(SliderCircleSelectionBlueprint),
- typeof(SliderBodyPiece),
- typeof(SliderCircle),
- typeof(PathControlPointVisualiser),
- typeof(PathControlPointPiece)
- };
-
private Slider slider;
private DrawableSlider drawableObject;
private TestSliderBlueprint blueprint;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
index f53b64c729..65bed071cd 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.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;
-using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
@@ -12,7 +10,6 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
@@ -20,13 +17,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture]
public class TestSceneSpinner : OsuTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(SpinnerDisc),
- typeof(DrawableSpinner),
- typeof(DrawableOsuHitObject)
- };
-
private readonly Container content;
protected override Container Content => content;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs
index dd863deed2..91b6a05fe3 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs
@@ -1,9 +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;
-using System.Collections.Generic;
-using System.Linq;
using NUnit.Framework;
using osu.Game.Rulesets.Osu.Mods;
@@ -12,8 +9,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture]
public class TestSceneSpinnerHidden : TestSceneSpinner
{
- public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
-
[SetUp]
public void SetUp() => Schedule(() =>
{
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs
index d777ca3610..011463ab14 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs
@@ -1,14 +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 osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
-using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
@@ -18,12 +15,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneSpinnerSelectionBlueprint : SelectionBlueprintTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(SpinnerSelectionBlueprint),
- typeof(SpinnerPiece)
- };
-
public TestSceneSpinnerSelectionBlueprint()
{
var spinner = new Spinner
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs
index e406f9ddff..d1210db6b1 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.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;
-using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
@@ -12,7 +10,6 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
@@ -20,14 +17,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture]
public class TestSceneSpinnerSpunOut : OsuTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(SpinnerDisc),
- typeof(DrawableSpinner),
- typeof(DrawableOsuHitObject),
- typeof(OsuModSpunOut)
- };
-
[SetUp]
public void SetUp() => Schedule(() =>
{
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
index 147d74c929..fcad356a1c 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
switch (original)
{
- case IHasCurve curveData:
+ case IHasPathWithRepeats curveData:
return new Slider
{
StartTime = original.StartTime,
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / beatmap.ControlPointInfo.DifficultyPointAt(original.StartTime).SpeedMultiplier : 1
}.Yield();
- case IHasEndTime endTimeData:
+ case IHasDuration endTimeData:
return new Spinner
{
StartTime = original.StartTime,
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
index 3a829f72fa..f51f04bf87 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
@@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
if (objectN.StartTime - endTime > stackThreshold)
- //We are no longer within stacking range of the next object.
+ // We are no longer within stacking range of the next object.
break;
if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance
@@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
}
}
- //Reverse pass for stack calculation.
+ // Reverse pass for stack calculation.
int extendedStartIndex = startIndex;
for (int i = extendedEndIndex; i > startIndex; i--)
@@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
double endTime = objectN.GetEndTime();
if (objectI.StartTime - endTime > stackThreshold)
- //We are no longer within stacking range of the previous object.
+ // We are no longer within stacking range of the previous object.
break;
// HitObjects before the specified update range haven't been reset yet
@@ -145,20 +145,20 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
for (int j = n + 1; j <= i; j++)
{
- //For each object which was declared under this slider, we will offset it to appear *below* the slider end (rather than above).
+ // For each object which was declared under this slider, we will offset it to appear *below* the slider end (rather than above).
OsuHitObject objectJ = beatmap.HitObjects[j];
if (Vector2Extensions.Distance(objectN.EndPosition, objectJ.Position) < stack_distance)
objectJ.StackHeight -= offset;
}
- //We have hit a slider. We should restart calculation using this as the new base.
- //Breaking here will mean that the slider still has StackCount of 0, so will be handled in the i-outer-loop.
+ // We have hit a slider. We should restart calculation using this as the new base.
+ // Breaking here will mean that the slider still has StackCount of 0, so will be handled in the i-outer-loop.
break;
}
if (Vector2Extensions.Distance(objectN.Position, objectI.Position) < stack_distance)
{
- //Keep processing as if there are no sliders. If we come across a slider, this gets cancelled out.
+ // Keep processing as if there are no sliders. If we come across a slider, this gets cancelled out.
//NOTE: Sliders with start positions stacking are a special case that is also handled here.
objectN.StackHeight = objectI.StackHeight + 1;
@@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
if (objectN is Spinner) continue;
if (objectI.StartTime - objectN.StartTime > stackThreshold)
- //We are no longer within stacking range of the previous object.
+ // We are no longer within stacking range of the previous object.
break;
if (Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance)
@@ -221,7 +221,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
}
else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, position2) < stack_distance)
{
- //Case for sliders - bump notes down and right, rather than up and left.
+ // Case for sliders - bump notes down and right, rather than up and left.
sliderStack++;
beatmap.HitObjects[j].StackHeight -= sliderStack;
startTime = beatmap.HitObjects[j].GetEndTime();
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index ce8ecf02ac..6f4c0f9cfa 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
@@ -45,10 +46,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
mods = Score.Mods;
accuracy = Score.Accuracy;
scoreMaxCombo = Score.MaxCombo;
- countGreat = Score.Statistics[HitResult.Great];
- countGood = Score.Statistics[HitResult.Good];
- countMeh = Score.Statistics[HitResult.Meh];
- countMiss = Score.Statistics[HitResult.Miss];
+ countGreat = Score.Statistics.GetOrDefault(HitResult.Great);
+ countGood = Score.Statistics.GetOrDefault(HitResult.Good);
+ countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
+ countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))
@@ -180,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
int amountHitObjectsWithAccuracy = countHitCircles;
if (amountHitObjectsWithAccuracy > 0)
- betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countGood * 2 + countMeh) / (amountHitObjectsWithAccuracy * 6);
+ betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countGood * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6);
else
betterAccuracyPercentage = 0;
@@ -203,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return accuracyValue;
}
- private double totalHits => countGreat + countGood + countMeh + countMiss;
- private double totalSuccessfulHits => countGreat + countGood + countMeh;
+ private int totalHits => countGreat + countGood + countMeh + countMiss;
+ private int totalSuccessfulHits => countGreat + countGood + countMeh;
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
index dad199715e..3dbbdcc5d0 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
@@ -5,7 +5,6 @@ using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
-using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
@@ -40,6 +39,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
return base.OnMouseDown(e);
}
- public override void UpdatePosition(Vector2 screenSpacePosition) => HitObject.Position = ToLocalSpace(screenSpacePosition);
+ public override void UpdatePosition(SnapResult result)
+ {
+ base.UpdatePosition(result);
+ HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs
index b0e13808a5..8dd550bb96 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs
@@ -12,6 +12,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
protected new T HitObject => (T)DrawableObject.HitObject;
+ protected override bool AlwaysShowWhenSelected => true;
+
protected OsuSelectionBlueprint(DrawableHitObject drawableObject)
: base(drawableObject)
{
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
index d0c1eb5317..c06904c0c2 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private IEditorChangeHandler changeHandler { get; set; }
[Resolved(CanBeNull = true)]
- private IDistanceSnapProvider snapProvider { get; set; }
+ private IPositionSnapProvider snapProvider { get; set; }
[Resolved]
private OsuColour colours { get; set; }
@@ -162,11 +162,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
if (ControlPoint == slider.Path.ControlPoints[0])
{
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
- (Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime);
- Vector2 movementDelta = snappedPosition - slider.Position;
+ var result = snapProvider?.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition);
+
+ Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? e.ScreenSpaceMousePosition) - slider.Position;
slider.Position += movementDelta;
- slider.StartTime = snappedTime;
+ slider.StartTime = result?.Time ?? slider.StartTime;
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
index ac30f5a762..4b99cc23ed 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
@@ -67,13 +67,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
inputManager = GetContainingInputManager();
}
- public override void UpdatePosition(Vector2 screenSpacePosition)
+ public override void UpdatePosition(SnapResult result)
{
+ base.UpdatePosition(result);
+
switch (state)
{
case PlacementState.Initial:
BeginPlacement();
- HitObject.Position = ToLocalSpace(screenSpacePosition);
+ HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
break;
case PlacementState.Body:
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index b7074b7ee5..6633136673 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)),
};
- public override Vector2 SelectionPoint => ((DrawableSlider)DrawableObject).HeadCircle.ScreenSpaceDrawQuad.Centre;
+ public override Vector2 ScreenSpaceSelectionPoint => ((DrawableSlider)DrawableObject).HeadCircle.ScreenSpaceDrawQuad.Centre;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos);
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs
index 74b563d922..cc4ed0eccf 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs
@@ -8,7 +8,6 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
-using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
@@ -60,9 +59,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
return true;
}
-
- public override void UpdatePosition(Vector2 screenSpacePosition)
- {
- }
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index cdf78a5902..37019a7a05 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -4,14 +4,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Caching;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Compose.Components;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Edit
{
@@ -32,9 +38,81 @@ namespace osu.Game.Rulesets.Osu.Edit
new SpinnerCompositionTool()
};
- protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(HitObjects);
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both });
- protected override DistanceSnapGrid CreateDistanceSnapGrid(IEnumerable selectedHitObjects)
+ EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid();
+ EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid();
+ }
+
+ protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects)
+ => new OsuBlueprintContainer(hitObjects);
+
+ private DistanceSnapGrid distanceSnapGrid;
+ private Container distanceSnapGridContainer;
+
+ private readonly Cached distanceSnapGridCache = new Cached();
+ private double? lastDistanceSnapGridTime;
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (!(BlueprintContainer.CurrentTool is SelectTool))
+ {
+ if (EditorClock.CurrentTime != lastDistanceSnapGridTime)
+ {
+ distanceSnapGridCache.Invalidate();
+ lastDistanceSnapGridTime = EditorClock.CurrentTime;
+ }
+
+ if (!distanceSnapGridCache.IsValid)
+ updateDistanceSnapGrid();
+ }
+ }
+
+ public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
+ {
+ if (distanceSnapGrid == null)
+ return base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
+
+ (Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition));
+
+ return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, PlayfieldAtScreenSpacePosition(screenSpacePosition));
+ }
+
+ private void updateDistanceSnapGrid()
+ {
+ distanceSnapGridContainer.Clear();
+ distanceSnapGridCache.Invalidate();
+
+ switch (BlueprintContainer.CurrentTool)
+ {
+ case SelectTool _:
+ if (!EditorBeatmap.SelectedHitObjects.Any())
+ return;
+
+ distanceSnapGrid = createDistanceSnapGrid(EditorBeatmap.SelectedHitObjects);
+ break;
+
+ default:
+ if (!CursorInPlacementArea)
+ return;
+
+ distanceSnapGrid = createDistanceSnapGrid(Enumerable.Empty());
+ break;
+ }
+
+ if (distanceSnapGrid != null)
+ {
+ distanceSnapGridContainer.Add(distanceSnapGrid);
+ distanceSnapGridCache.Validate();
+ }
+ }
+
+ private DistanceSnapGrid createDistanceSnapGrid(IEnumerable selectedHitObjects)
{
if (BlueprintContainer.CurrentTool is SpinnerCompositionTool)
return null;
@@ -42,7 +120,8 @@ namespace osu.Game.Rulesets.Osu.Edit
var objects = selectedHitObjects.ToList();
if (objects.Count == 0)
- return createGrid(h => h.StartTime <= EditorClock.CurrentTime);
+ // use accurate time value to give more instantaneous feedback to the user.
+ return createGrid(h => h.StartTime <= EditorClock.CurrentTimeAccurate);
double minTime = objects.Min(h => h.StartTime);
return createGrid(h => h.StartTime < minTime, objects.Count + 1);
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
index fe46876050..d75f4c70d7 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
@@ -24,7 +24,8 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) };
- public bool AllowFail => false;
+ public bool PerformFail() => false;
+
public bool RestartOnFail => false;
private OsuInputManager inputManager;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
index cf6677a55d..e0577dd464 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
@@ -28,8 +28,11 @@ namespace osu.Game.Rulesets.Osu.Mods
slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
- foreach (var point in slider.Path.ControlPoints)
+ var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray();
+ foreach (var point in controlPoints)
point.Position.Value = new Vector2(point.Position.Value.X, -point.Position.Value.Y);
+
+ slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index 7b1941b7f9..5d191119b9 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
@@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Mods
break;
// already hit or beyond the hittable end time.
- if (h.IsHit || (h.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime))
+ if (h.IsHit || (h.HitObject is IHasDuration hasEnd && time > hasEnd.EndTime))
continue;
switch (h)
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
index 44dba7715a..5e80d08667 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Mods
Vector2 originalPosition = drawable.Position;
Vector2 appearOffset = new Vector2(MathF.Cos(theta), MathF.Sin(theta)) * appearDistance;
- //the - 1 and + 1 prevents the hit objects to appear in the wrong position.
+ // the - 1 and + 1 prevents the hit objects to appear in the wrong position.
double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1;
double moveDuration = hitObject.TimePreempt + 1;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
index 297a0fea79..3cad52faeb 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
@@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Mods
}
// Keep wiggling sliders and spinners for their duration
- if (!(osuObject is IHasEndTime endTime))
+ if (!(osuObject is IHasDuration endTime))
return;
amountWiggles = (int)(endTime.Duration / wiggle_duration);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
index 6f09bbcd57..8a0ef22c4a 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
@@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
private void bindEvents(DrawableOsuHitObject drawableObject)
{
drawableObject.HitObject.PositionBindable.BindValueChanged(_ => scheduleRefresh());
- drawableObject.HitObject.DefaultsApplied += scheduleRefresh;
+ drawableObject.HitObject.DefaultsApplied += _ => scheduleRefresh();
}
private void scheduleRefresh()
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs
index e364c96426..cb3787a493 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs
@@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
using (BeginDelayedSequence(flash_in, true))
{
- //after the flash, we can hide some elements that were behind it
+ // after the flash, we can hide some elements that were behind it
ring.FadeOut();
circle.FadeOut();
number.FadeOut();
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index e5d6c20738..705e88040f 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -6,6 +6,7 @@ using osu.Game.Rulesets.Objects.Types;
using System.Collections.Generic;
using osu.Game.Rulesets.Objects;
using System.Linq;
+using System.Threading;
using osu.Framework.Caching;
using osu.Game.Audio;
using osu.Game.Beatmaps;
@@ -16,16 +17,16 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
- public class Slider : OsuHitObject, IHasCurve
+ public class Slider : OsuHitObject, IHasPathWithRepeats
{
- public double EndTime
+ public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
+
+ public double Duration
{
- get => StartTime + this.SpanCount() * Path.Distance / Velocity;
+ get => EndTime - StartTime;
set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
}
- public double Duration => EndTime - StartTime;
-
private readonly Cached endPositionCache = new Cached();
public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1);
@@ -133,12 +134,12 @@ namespace osu.Game.Rulesets.Osu.Objects
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
}
- protected override void CreateNestedHitObjects()
+ protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{
- base.CreateNestedHitObjects();
+ base.CreateNestedHitObjects(cancellationToken);
foreach (var e in
- SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset))
+ SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken))
{
switch (e.Type)
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 0b8d03d118..418375c090 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -11,7 +11,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
- public class Spinner : OsuHitObject, IHasEndTime
+ public class Spinner : OsuHitObject, IHasDuration
{
public double EndTime
{
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
index 1de7d488f3..79a6ea7e92 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
@@ -8,7 +8,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Scoring
{
- internal class OsuScoreProcessor : ScoreProcessor
+ public class OsuScoreProcessor : ScoreProcessor
{
protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement);
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 37df5ec540..9bcb3abc63 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -237,6 +237,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y + size.Y / 2),
TexturePosition = textureRect.BottomLeft,
+ TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.BottomLeft.Linear,
Time = part.Time
});
@@ -245,6 +246,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y + size.Y / 2),
TexturePosition = textureRect.BottomRight,
+ TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.BottomRight.Linear,
Time = part.Time
});
@@ -253,6 +255,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y - size.Y / 2),
TexturePosition = textureRect.TopRight,
+ TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.TopRight.Linear,
Time = part.Time
});
@@ -261,6 +264,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y - size.Y / 2),
TexturePosition = textureRect.TopLeft,
+ TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.TopLeft.Linear,
Time = part.Time
});
@@ -290,6 +294,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
[VertexMember(2, VertexAttribPointerType.Float)]
public Vector2 TexturePosition;
+ [VertexMember(4, VertexAttribPointerType.Float)]
+ public Vector4 TextureRect;
+
[VertexMember(1, VertexAttribPointerType.Float)]
public float Time;
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-slider-fail@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-slider-fail@2x.png
new file mode 100644
index 0000000000..ac0fef8626
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-slider-fail@2x.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-slider@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-slider@2x.png
new file mode 100644
index 0000000000..cca9310322
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-slider@2x.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-slider-fail@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-slider-fail@2x.png
new file mode 100644
index 0000000000..2d9974a701
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-slider-fail@2x.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-slider@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-slider@2x.png
new file mode 100644
index 0000000000..07b2f167e0
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-slider@2x.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-slider-fail.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-slider-fail.png
new file mode 100644
index 0000000000..78c6ef6e21
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-slider-fail.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-slider.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-slider.png
new file mode 100644
index 0000000000..b824e4585b
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-slider.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TaikoSkinnableTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TaikoSkinnableTestScene.cs
index 161154b1a7..69250a14e1 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TaikoSkinnableTestScene.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TaikoSkinnableTestScene.cs
@@ -1,21 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
-using System.Collections.Generic;
-using osu.Game.Rulesets.Taiko.Skinning;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{
public abstract class TaikoSkinnableTestScene : SkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(TaikoRuleset),
- typeof(TaikoLegacySkinTransformer),
- };
-
protected override Ruleset CreateRulesetForSkinProvider() => new TaikoRuleset();
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs
index 70493aa69a..f6aec20d53 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs
@@ -1,9 +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;
-using System.Collections.Generic;
-using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -12,7 +9,6 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
-using osu.Game.Rulesets.Taiko.Skinning;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
@@ -22,13 +18,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
[TestFixture]
public class TestSceneDrawableBarLine : TaikoSkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[]
- {
- typeof(DrawableBarLine),
- typeof(LegacyBarLine),
- typeof(BarLine),
- }).ToList();
-
[Cached(typeof(IScrollingInfo))]
private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo
{
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs
index 554894bf68..44646e5fc9 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs
@@ -1,9 +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;
-using System.Collections.Generic;
-using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -11,7 +8,6 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
-using osu.Game.Rulesets.Taiko.Skinning;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
@@ -20,13 +16,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
[TestFixture]
public class TestSceneDrawableDrumRoll : TaikoSkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[]
- {
- typeof(DrawableDrumRoll),
- typeof(DrawableDrumRollTick),
- typeof(LegacyDrumRoll),
- }).ToList();
-
[Cached(typeof(IScrollingInfo))]
private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo
{
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs
index 6a3c98a514..9930d97d31 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs
@@ -1,9 +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;
-using System.Collections.Generic;
-using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -11,20 +8,12 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
-using osu.Game.Rulesets.Taiko.Skinning;
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{
[TestFixture]
public class TestSceneDrawableHit : TaikoSkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[]
- {
- typeof(DrawableHit),
- typeof(LegacyHit),
- typeof(LegacyCirclePiece),
- }).ToList();
-
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs
new file mode 100644
index 0000000000..d200c44a02
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs
@@ -0,0 +1,216 @@
+// 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 Humanizer;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Graphics;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Taiko.Judgements;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.Scoring;
+using osu.Game.Rulesets.Taiko.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests.Skinning
+{
+ [TestFixture]
+ public class TestSceneDrawableTaikoMascot : TaikoSkinnableTestScene
+ {
+ [Cached(typeof(IScrollingInfo))]
+ private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo
+ {
+ Direction = { Value = ScrollingDirection.Left },
+ TimeRange = { Value = 5000 },
+ };
+
+ private TaikoScoreProcessor scoreProcessor;
+
+ private IEnumerable mascots => this.ChildrenOfType();
+ private IEnumerable playfields => this.ChildrenOfType();
+
+ [SetUp]
+ public void SetUp()
+ {
+ scoreProcessor = new TaikoScoreProcessor();
+ }
+
+ [Test]
+ public void TestStateAnimations()
+ {
+ AddStep("set beatmap", () => setBeatmap());
+
+ AddStep("clear state", () => SetContents(() => new TaikoMascotAnimation(TaikoMascotAnimationState.Clear)));
+ AddStep("idle state", () => SetContents(() => new TaikoMascotAnimation(TaikoMascotAnimationState.Idle)));
+ AddStep("kiai state", () => SetContents(() => new TaikoMascotAnimation(TaikoMascotAnimationState.Kiai)));
+ AddStep("fail state", () => SetContents(() => new TaikoMascotAnimation(TaikoMascotAnimationState.Fail)));
+ }
+
+ [Test]
+ public void TestInitialState()
+ {
+ AddStep("create mascot", () => SetContents(() => new DrawableTaikoMascot { RelativeSizeAxes = Axes.Both }));
+
+ AddAssert("mascot initially idle", () => allMascotsIn(TaikoMascotAnimationState.Idle));
+ }
+
+ [Test]
+ public void TestClearStateTransition()
+ {
+ AddStep("set beatmap", () => setBeatmap());
+
+ AddStep("create mascot", () => SetContents(() => new DrawableTaikoMascot { RelativeSizeAxes = Axes.Both }));
+
+ AddStep("set clear state", () => mascots.ForEach(mascot => mascot.State.Value = TaikoMascotAnimationState.Clear));
+ AddStep("miss", () => mascots.ForEach(mascot => mascot.LastResult.Value = new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }));
+ AddAssert("skins with animations remain in clear state", () => someMascotsIn(TaikoMascotAnimationState.Clear));
+ AddUntilStep("state reverts to fail", () => allMascotsIn(TaikoMascotAnimationState.Fail));
+
+ AddStep("set clear state again", () => mascots.ForEach(mascot => mascot.State.Value = TaikoMascotAnimationState.Clear));
+ AddAssert("skins with animations change to clear", () => someMascotsIn(TaikoMascotAnimationState.Clear));
+ }
+
+ [Test]
+ public void TestIdleState()
+ {
+ AddStep("set beatmap", () => setBeatmap());
+
+ createDrawableRuleset();
+
+ assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle);
+ assertStateAfterResult(new JudgementResult(new StrongHitObject(), new TaikoStrongJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Idle);
+ }
+
+ [Test]
+ public void TestKiaiState()
+ {
+ AddStep("set beatmap", () => setBeatmap(true));
+
+ createDrawableRuleset();
+
+ assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Kiai);
+ assertStateAfterResult(new JudgementResult(new Hit(), new TaikoStrongJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Kiai);
+ assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Fail);
+ }
+
+ [Test]
+ public void TestMissState()
+ {
+ AddStep("set beatmap", () => setBeatmap());
+
+ createDrawableRuleset();
+
+ assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle);
+ assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Fail);
+ assertStateAfterResult(new JudgementResult(new DrumRoll(), new TaikoDrumRollJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Fail);
+ assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Idle);
+ }
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestClearStateOnComboMilestone(bool kiai)
+ {
+ AddStep("set beatmap", () => setBeatmap(kiai));
+
+ createDrawableRuleset();
+
+ AddRepeatStep("reach 49 combo", () => applyNewResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }), 49);
+
+ assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Clear);
+ }
+
+ [TestCase(true, TaikoMascotAnimationState.Kiai)]
+ [TestCase(false, TaikoMascotAnimationState.Idle)]
+ public void TestClearStateOnClearedSwell(bool kiai, TaikoMascotAnimationState expectedStateAfterClear)
+ {
+ AddStep("set beatmap", () => setBeatmap(kiai));
+
+ createDrawableRuleset();
+
+ assertStateAfterResult(new JudgementResult(new Swell(), new TaikoSwellJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Clear);
+ AddUntilStep($"state reverts to {expectedStateAfterClear.ToString().ToLower()}", () => allMascotsIn(expectedStateAfterClear));
+ }
+
+ private void setBeatmap(bool kiai = false)
+ {
+ var controlPointInfo = new ControlPointInfo();
+ controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 90 });
+
+ if (kiai)
+ controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
+
+ Beatmap.Value = CreateWorkingBeatmap(new Beatmap
+ {
+ HitObjects = new List { new Hit { Type = HitType.Centre } },
+ BeatmapInfo = new BeatmapInfo
+ {
+ BaseDifficulty = new BeatmapDifficulty(),
+ Metadata = new BeatmapMetadata
+ {
+ Artist = "Unknown",
+ Title = "Sample Beatmap",
+ AuthorString = "Craftplacer",
+ },
+ Ruleset = new TaikoRuleset().RulesetInfo
+ },
+ ControlPointInfo = controlPointInfo
+ });
+
+ scoreProcessor.ApplyBeatmap(Beatmap.Value.Beatmap);
+ }
+
+ private void createDrawableRuleset()
+ {
+ AddUntilStep("wait for beatmap to be loaded", () => Beatmap.Value.Track.IsLoaded);
+
+ AddStep("create drawable ruleset", () =>
+ {
+ Beatmap.Value.Track.Start();
+
+ SetContents(() =>
+ {
+ var ruleset = new TaikoRuleset();
+ return new DrawableTaikoRuleset(ruleset, Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo));
+ });
+ });
+ }
+
+ private void assertStateAfterResult(JudgementResult judgementResult, TaikoMascotAnimationState expectedState)
+ {
+ AddStep($"{judgementResult.Type.ToString().ToLower()} result for {judgementResult.Judgement.GetType().Name.Humanize(LetterCasing.LowerCase)}",
+ () => applyNewResult(judgementResult));
+
+ AddAssert($"state is {expectedState.ToString().ToLower()}", () => allMascotsIn(expectedState));
+ }
+
+ private void applyNewResult(JudgementResult judgementResult)
+ {
+ scoreProcessor.ApplyResult(judgementResult);
+
+ foreach (var playfield in playfields)
+ {
+ var hit = new DrawableTestHit(new Hit(), judgementResult.Type);
+ Add(hit);
+
+ playfield.OnNewResult(hit, judgementResult);
+ }
+
+ foreach (var mascot in mascots)
+ {
+ mascot.LastResult.Value = judgementResult;
+ }
+ }
+
+ private bool allMascotsIn(TaikoMascotAnimationState state) => mascots.All(d => d.State.Value == state);
+ private bool someMascotsIn(TaikoMascotAnimationState state) => mascots.Any(d => d.State.Value == state);
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs
index 791c438c94..2b5efec7f9 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs
@@ -1,9 +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;
-using System.Collections.Generic;
-using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -11,7 +8,6 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
-using osu.Game.Rulesets.Taiko.Skinning;
using osu.Game.Rulesets.Taiko.UI;
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
@@ -19,13 +15,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
[TestFixture]
public class TestSceneHitExplosion : TaikoSkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[]
- {
- typeof(HitExplosion),
- typeof(LegacyHitExplosion),
- typeof(DefaultHitExplosion),
- }).ToList();
-
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs
index 412027ca61..fa6c9da174 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs
@@ -1,15 +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.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Rulesets.Taiko.Skinning;
using osu.Game.Rulesets.Taiko.UI;
using osuTK;
@@ -18,12 +14,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
[TestFixture]
public class TestSceneInputDrum : TaikoSkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[]
- {
- typeof(InputDrum),
- typeof(LegacyInputDrum),
- }).ToList();
-
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs
index ae5dd1e622..7b7e2c43d1 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs
@@ -2,15 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
-using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.Beatmaps;
-using osu.Game.Rulesets.Taiko.Skinning;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
@@ -19,13 +16,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{
public class TestSceneTaikoPlayfield : TaikoSkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[]
- {
- typeof(TaikoHitTarget),
- typeof(TaikoLegacyHitTarget),
- typeof(PlayfieldBackgroundRight),
- }).ToList();
-
[Cached(typeof(IScrollingInfo))]
private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo
{
@@ -51,6 +41,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
+ Height = 0.6f,
}));
AddRepeatStep("change height", () => this.ChildrenOfType().ForEach(p => p.Height = Math.Max(0.2f, (p.Height + 0.2f) % 1f)), 50);
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoScroller.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoScroller.cs
new file mode 100644
index 0000000000..16ef5b968d
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoScroller.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.Extensions.IEnumerableExtensions;
+using osu.Framework.Testing;
+using osu.Framework.Timing;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Taiko.Skinning;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Taiko.Tests.Skinning
+{
+ public class TestSceneTaikoScroller : TaikoSkinnableTestScene
+ {
+ private readonly ManualClock clock = new ManualClock();
+
+ private bool reversed;
+
+ public TestSceneTaikoScroller()
+ {
+ AddStep("Load scroller", () => SetContents(() =>
+ new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty())
+ {
+ Clock = new FramedClock(clock),
+ Height = 0.4f,
+ }));
+
+ AddToggleStep("Toggle passing", passing => this.ChildrenOfType().ForEach(s => s.LastResult.Value =
+ new JudgementResult(null, new Judgement()) { Type = passing ? HitResult.Perfect : HitResult.Miss }));
+
+ AddToggleStep("toggle playback direction", reversed => this.reversed = reversed);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ clock.CurrentTime += (reversed ? -1 : 1) * Clock.ElapsedFrameTime;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index 8c26ca70ac..f7729138ff 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
[NonParallelizable]
[TestCase("basic")]
[TestCase("slider-generating-drumroll")]
+ [TestCase("sample-to-type-conversions")]
public void Test(string name) => base.Test(name);
protected override IEnumerable CreateConvertValue(HitObject hitObject)
@@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
public struct ConvertValue : IEquatable
{
///
- /// A sane value to account for osu!stable using ints everwhere.
+ /// A sane value to account for osu!stable using ints everywhere.
///
private const float conversion_lenience = 2;
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs
new file mode 100644
index 0000000000..089a7ad00b
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs
@@ -0,0 +1,17 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ [TestFixture]
+ public class TestSceneEditor : EditorTestScene
+ {
+ public TestSceneEditor()
+ : base(new TaikoRuleset())
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs
new file mode 100644
index 0000000000..d541aa8de8
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs
@@ -0,0 +1,49 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using osu.Framework.Testing;
+using osu.Game.Audio;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Taiko.Objects.Drawables;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ ///
+ /// Taiko has some interesting rules for legacy mappings.
+ ///
+ [HeadlessTest]
+ public class TestSceneSampleOutput : PlayerTestScene
+ {
+ public TestSceneSampleOutput()
+ : base(new TaikoRuleset())
+ {
+ }
+
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+ AddAssert("has correct samples", () =>
+ {
+ var names = Player.DrawableRuleset.Playfield.AllHitObjects.OfType().Select(h => string.Join(',', h.GetSamples().Select(s => s.Name)));
+
+ var expected = new[]
+ {
+ string.Empty,
+ string.Empty,
+ string.Empty,
+ string.Empty,
+ HitSampleInfo.HIT_FINISH,
+ HitSampleInfo.HIT_WHISTLE,
+ HitSampleInfo.HIT_WHISTLE,
+ HitSampleInfo.HIT_WHISTLE,
+ };
+
+ return names.SequenceEqual(expected);
+ });
+ }
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TaikoBeatmapConversionTest().GetBeatmap("sample-to-type-conversions");
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs
new file mode 100644
index 0000000000..34d5fdf857
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.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 NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Taiko.Beatmaps;
+using osu.Game.Rulesets.Taiko.Edit;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Screens.Edit;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ public class TestSceneTaikoHitObjectComposer : EditorClockTestScene
+ {
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ BeatDivisor.Value = 8;
+ Clock.Seek(0);
+
+ Child = new TestComposer { RelativeSizeAxes = Axes.Both };
+ });
+
+ [Test]
+ public void BasicTest()
+ {
+ }
+
+ private class TestComposer : CompositeDrawable
+ {
+ [Cached(typeof(EditorBeatmap))]
+ [Cached(typeof(IBeatSnapProvider))]
+ public readonly EditorBeatmap EditorBeatmap;
+
+ public TestComposer()
+ {
+ InternalChildren = new Drawable[]
+ {
+ EditorBeatmap = new EditorBeatmap(new TaikoBeatmap())
+ {
+ BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo }
+ },
+ new TaikoHitObjectComposer(new TaikoRuleset())
+ };
+
+ for (int i = 0; i < 10; i++)
+ EditorBeatmap.Add(new Hit { StartTime = 125 * i });
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index caf645d5a2..78550ed270 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
{
- List> allSamples = obj is IHasCurve curveData ? curveData.NodeSamples : new List>(new[] { samples });
+ List> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List>(new[] { samples });
int i = 0;
@@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
break;
}
- case IHasEndTime endTimeData:
+ case IHasDuration endTimeData:
{
double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier;
@@ -167,13 +167,15 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
default:
{
- bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
+ bool isRimDefinition(HitSampleInfo s) => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE;
+
+ bool isRim = samples.Any(isRimDefinition);
yield return new Hit
{
StartTime = obj.StartTime,
Type = isRim ? HitType.Rim : HitType.Centre,
- Samples = obj.Samples,
+ Samples = samples,
IsStrong = strong
};
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 3a0fb64622..bc147b53ac 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
@@ -31,10 +32,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
public override double Calculate(Dictionary categoryDifficulty = null)
{
mods = Score.Mods;
- countGreat = Score.Statistics[HitResult.Great];
- countGood = Score.Statistics[HitResult.Good];
- countMeh = Score.Statistics[HitResult.Meh];
- countMiss = Score.Statistics[HitResult.Miss];
+ countGreat = Score.Statistics.GetOrDefault(HitResult.Great);
+ countGood = Score.Statistics.GetOrDefault(HitResult.Good);
+ countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
+ countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))
diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs
new file mode 100644
index 0000000000..eb07ce7635
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.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 osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
+{
+ public class DrumRollPlacementBlueprint : TaikoSpanPlacementBlueprint
+ {
+ public DrumRollPlacementBlueprint()
+ : base(new DrumRoll())
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs
new file mode 100644
index 0000000000..b02e3aa9ba
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.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 osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
+{
+ public class HitPiece : CompositeDrawable
+ {
+ public HitPiece()
+ {
+ Origin = Anchor.Centre;
+
+ InternalChild = new CircularContainer
+ {
+ Masking = true,
+ BorderThickness = 10,
+ BorderColour = Color4.Yellow,
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ AlwaysPresent = true,
+ Alpha = 0,
+ RelativeSizeAxes = Axes.Both
+ }
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs
new file mode 100644
index 0000000000..c5191ab241
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.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 osu.Framework.Input.Events;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.UI;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
+{
+ public class HitPlacementBlueprint : PlacementBlueprint
+ {
+ private readonly HitPiece piece;
+
+ private static Hit hit;
+
+ public HitPlacementBlueprint()
+ : base(hit = new Hit())
+ {
+ InternalChild = piece = new HitPiece
+ {
+ Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT)
+ };
+ }
+
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ switch (e.Button)
+ {
+ case MouseButton.Left:
+ hit.Type = HitType.Centre;
+ EndPlacement(true);
+ return true;
+
+ case MouseButton.Right:
+ hit.Type = HitType.Rim;
+ EndPlacement(true);
+ return true;
+ }
+
+ return false;
+ }
+
+ public override void UpdatePosition(SnapResult result)
+ {
+ piece.Position = ToLocalSpace(result.ScreenSpacePosition);
+ base.UpdatePosition(result);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs
new file mode 100644
index 0000000000..6b651fd739
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs
@@ -0,0 +1,40 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
+{
+ public class LengthPiece : CompositeDrawable
+ {
+ public LengthPiece()
+ {
+ Origin = Anchor.CentreLeft;
+
+ InternalChild = new Container
+ {
+ Masking = true,
+ Colour = Color4.Yellow,
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 8,
+ },
+ new Box
+ {
+ Origin = Anchor.BottomLeft,
+ Anchor = Anchor.BottomLeft,
+ RelativeSizeAxes = Axes.X,
+ Height = 8,
+ }
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs
new file mode 100644
index 0000000000..95fa82a0f2
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.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 osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
+{
+ public class SwellPlacementBlueprint : TaikoSpanPlacementBlueprint
+ {
+ public SwellPlacementBlueprint()
+ : base(new Swell())
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs
new file mode 100644
index 0000000000..62f69122cc
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs
@@ -0,0 +1,40 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Objects.Drawables;
+using osuTK;
+
+namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
+{
+ public class TaikoSelectionBlueprint : OverlaySelectionBlueprint
+ {
+ public TaikoSelectionBlueprint(DrawableHitObject hitObject)
+ : base(hitObject)
+ {
+ RelativeSizeAxes = Axes.None;
+
+ AddInternal(new HitPiece
+ {
+ RelativeSizeAxes = Axes.Both,
+ Origin = Anchor.TopLeft
+ });
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ // Move the rectangle to cover the hitobjects
+ var topLeft = new Vector2(float.MaxValue, float.MaxValue);
+ var bottomRight = new Vector2(float.MinValue, float.MinValue);
+
+ topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.TopLeft));
+ bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.BottomRight));
+
+ Size = bottomRight - topLeft;
+ Position = topLeft;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs
new file mode 100644
index 0000000000..468d980b23
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs
@@ -0,0 +1,110 @@
+// 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.Graphics;
+using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.UI;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
+{
+ public class TaikoSpanPlacementBlueprint : PlacementBlueprint
+ {
+ private readonly HitPiece headPiece;
+ private readonly HitPiece tailPiece;
+
+ private readonly LengthPiece lengthPiece;
+
+ private readonly IHasDuration spanPlacementObject;
+
+ public TaikoSpanPlacementBlueprint(HitObject hitObject)
+ : base(hitObject)
+ {
+ spanPlacementObject = hitObject as IHasDuration;
+
+ RelativeSizeAxes = Axes.Both;
+
+ InternalChildren = new Drawable[]
+ {
+ headPiece = new HitPiece
+ {
+ Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT)
+ },
+ lengthPiece = new LengthPiece
+ {
+ Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT
+ },
+ tailPiece = new HitPiece
+ {
+ Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT)
+ }
+ };
+ }
+
+ private double originalStartTime;
+ private Vector2 originalPosition;
+
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ if (e.Button != MouseButton.Left)
+ return false;
+
+ BeginPlacement(true);
+ return true;
+ }
+
+ protected override void OnMouseUp(MouseUpEvent e)
+ {
+ if (e.Button != MouseButton.Left)
+ return;
+
+ base.OnMouseUp(e);
+ EndPlacement(true);
+ }
+
+ public override void UpdatePosition(SnapResult result)
+ {
+ base.UpdatePosition(result);
+
+ if (PlacementActive)
+ {
+ if (result.Time is double dragTime)
+ {
+ if (dragTime < originalStartTime)
+ {
+ HitObject.StartTime = dragTime;
+ spanPlacementObject.Duration = Math.Abs(dragTime - originalStartTime);
+ headPiece.Position = ToLocalSpace(result.ScreenSpacePosition);
+ tailPiece.Position = originalPosition;
+ }
+ else
+ {
+ HitObject.StartTime = originalStartTime;
+ spanPlacementObject.Duration = Math.Abs(dragTime - originalStartTime);
+ tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition);
+ headPiece.Position = originalPosition;
+ }
+
+ lengthPiece.X = headPiece.X;
+ lengthPiece.Width = tailPiece.X - headPiece.X;
+ }
+ }
+ else
+ {
+ lengthPiece.Position = headPiece.Position = tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition);
+
+ if (result.Time is double startTime)
+ {
+ originalStartTime = HitObject.StartTime = startTime;
+ originalPosition = ToLocalSpace(result.ScreenSpacePosition);
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs
new file mode 100644
index 0000000000..bf77c76670
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs
@@ -0,0 +1,20 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Edit.Tools;
+using osu.Game.Rulesets.Taiko.Edit.Blueprints;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Edit
+{
+ public class DrumRollCompositionTool : HitObjectCompositionTool
+ {
+ public DrumRollCompositionTool()
+ : base(nameof(DrumRoll))
+ {
+ }
+
+ public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint();
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs
new file mode 100644
index 0000000000..e877cf6240
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs
@@ -0,0 +1,20 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Edit.Tools;
+using osu.Game.Rulesets.Taiko.Edit.Blueprints;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Edit
+{
+ public class HitCompositionTool : HitObjectCompositionTool
+ {
+ public HitCompositionTool()
+ : base(nameof(Hit))
+ {
+ }
+
+ public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint();
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs
new file mode 100644
index 0000000000..a6191fcedc
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs
@@ -0,0 +1,20 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Edit.Tools;
+using osu.Game.Rulesets.Taiko.Edit.Blueprints;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Edit
+{
+ public class SwellCompositionTool : HitObjectCompositionTool
+ {
+ public SwellCompositionTool()
+ : base(nameof(Swell))
+ {
+ }
+
+ public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint();
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs
new file mode 100644
index 0000000000..35227b3c64
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs
@@ -0,0 +1,24 @@
+// 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.Rulesets.Edit;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Taiko.Edit.Blueprints;
+using osu.Game.Screens.Edit.Compose.Components;
+
+namespace osu.Game.Rulesets.Taiko.Edit
+{
+ public class TaikoBlueprintContainer : ComposeBlueprintContainer
+ {
+ public TaikoBlueprintContainer(IEnumerable hitObjects)
+ : base(hitObjects)
+ {
+ }
+
+ protected override SelectionHandler CreateSelectionHandler() => new TaikoSelectionHandler();
+
+ public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) =>
+ new TaikoSelectionBlueprint(hitObject);
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs
new file mode 100644
index 0000000000..cdc9672a8e
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs
@@ -0,0 +1,30 @@
+// 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.Rulesets.Edit;
+using osu.Game.Rulesets.Edit.Tools;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Screens.Edit.Compose.Components;
+
+namespace osu.Game.Rulesets.Taiko.Edit
+{
+ public class TaikoHitObjectComposer : HitObjectComposer
+ {
+ public TaikoHitObjectComposer(TaikoRuleset ruleset)
+ : base(ruleset)
+ {
+ }
+
+ protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
+ {
+ new HitCompositionTool(),
+ new DrumRollCompositionTool(),
+ new SwellCompositionTool()
+ };
+
+ protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects)
+ => new TaikoBlueprintContainer(hitObjects);
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
new file mode 100644
index 0000000000..eebf6980fe
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
@@ -0,0 +1,80 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Screens.Edit.Compose.Components;
+
+namespace osu.Game.Rulesets.Taiko.Edit
+{
+ public class TaikoSelectionHandler : SelectionHandler
+ {
+ protected override IEnumerable