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 a406cdf08a..7ea1f3140b 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,7 +51,7 @@
-
-
+
+
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/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/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
index e7ce680365..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.95f + 0.3f * Math.Min(1.0f, numTotalHits / 2500.0f) +
- (numTotalHits > 2500 ? (float)Math.Log10(numTotalHits / 2500.0f) * 0.475f : 0.0f);
+ 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;
@@ -65,14 +65,14 @@ namespace osu.Game.Rulesets.Catch.Difficulty
if (Attributes.MaxCombo > 0)
value *= Math.Min(Math.Pow(Score.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
- float approachRate = (float)Attributes.ApproachRate;
- float approachRateFactor = 1.0f;
- if (approachRate > 9.0f)
- approachRateFactor += 0.1f * (approachRate - 9.0f); // 10% for each AR above 9
- if (approachRate > 10.0f)
- approachRateFactor += 0.1f * (approachRate - 10.0f); // Additional 10% at AR 11, 30% total
- else if (approachRate < 8.0f)
- approachRateFactor += 0.025f * (8.0f - approachRate); // 2.5% for each AR below 8
+ double approachRate = Attributes.ApproachRate;
+ double approachRateFactor = 1.0;
+ 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;
@@ -80,10 +80,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
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.0f)
- value *= 1.05f + 0.075f * (10.0f - approachRate); // 7.5% for each AR below 10
- else if (approachRate > 10.0f)
- value *= 1.01f + 0.04f * (11.0f - Math.Min(11.0f, approachRate)); // 5% at AR 10, 1% at AR 11
+ 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))
@@ -100,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/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/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 48159c817d..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;
@@ -29,11 +27,6 @@ namespace osu.Game.Rulesets.Mania.Tests
{
public class TestSceneManiaHitObjectComposer : EditorClockTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(ManiaBlueprintContainer)
- };
-
private TestComposer composer;
[SetUp]
@@ -49,6 +42,7 @@ namespace osu.Game.Rulesets.Mania.Tests
public void TestDragOffscreenSelectionVerticallyUpScroll()
{
DrawableHitObject lastObject = null;
+ double originalTime = 0;
Vector2 originalPosition = Vector2.Zero;
setScrollStep(ScrollingDirection.Up);
@@ -56,6 +50,7 @@ namespace osu.Game.Rulesets.Mania.Tests
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);
});
@@ -71,19 +66,20 @@ 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;
setScrollStep(ScrollingDirection.Down);
@@ -91,6 +87,7 @@ namespace osu.Game.Rulesets.Mania.Tests
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);
});
@@ -106,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]
@@ -214,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/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/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/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/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.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/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/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/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
index d2671eadda..81b969eaf3 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
@@ -2,9 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Graphics;
+using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
@@ -47,6 +49,42 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
? new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit), _ => new CentreHitCirclePiece(), confineMode: ConfineMode.ScaleToFit)
: new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit), _ => new RimHitCirclePiece(), confineMode: ConfineMode.ScaleToFit);
+ public override IEnumerable GetSamples()
+ {
+ // normal and claps are always handled by the drum (see DrumSampleMapping).
+ // in addition, whistles are excluded as they are an alternative rim marker.
+
+ var samples = HitObject.Samples.Where(s =>
+ s.Name != HitSampleInfo.HIT_NORMAL
+ && s.Name != HitSampleInfo.HIT_CLAP
+ && s.Name != HitSampleInfo.HIT_WHISTLE);
+
+ if (HitObject.Type == HitType.Rim && HitObject.IsStrong)
+ {
+ // strong + rim always maps to whistle.
+ // TODO: this should really be in the legacy decoder, but can't be because legacy encoding parity would be broken.
+ // when we add a taiko editor, this is probably not going to play nice.
+
+ var corrected = samples.ToList();
+
+ for (var i = 0; i < corrected.Count; i++)
+ {
+ var s = corrected[i];
+
+ if (s.Name != HitSampleInfo.HIT_FINISH)
+ continue;
+
+ var sClone = s.Clone();
+ sClone.Name = HitSampleInfo.HIT_WHISTLE;
+ corrected[i] = sClone;
+ }
+
+ return corrected;
+ }
+
+ return samples;
+ }
+
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
Debug.Assert(HitObject.HitWindows != null);
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
index 32f7acadc8..7294587b10 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
@@ -237,7 +237,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
case ArmedState.Miss:
case ArmedState.Hit:
- using (BeginAbsoluteSequence(Time.Current, true))
+ using (BeginDelayedSequence(HitObject.Duration, true))
{
this.FadeOut(transition_duration, Easing.Out);
bodyContainer.ScaleTo(1.4f, transition_duration);
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index 1be04f1760..3ab09d4cbe 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -165,8 +165,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return base.CreateNestedHitObject(hitObject);
}
- // Normal and clap samples are handled by the drum
- protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
+ // Most osu!taiko hitsounds are managed by the drum (see DrumSampleMapping).
+ public override IEnumerable GetSamples() => Enumerable.Empty();
protected abstract SkinnableDrawable CreateMainPiece();
diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
index dc2f277e58..5f52160be1 100644
--- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
@@ -3,8 +3,7 @@
using osu.Game.Rulesets.Objects.Types;
using System;
-using System.Collections.Generic;
-using osu.Game.Audio;
+using System.Threading;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
@@ -16,7 +15,7 @@ using osuTK;
namespace osu.Game.Rulesets.Taiko.Objects
{
- public class DrumRoll : TaikoHitObject, IHasCurve
+ public class DrumRoll : TaikoHitObject, IHasPath
{
///
/// Drum roll distance that results in a duration of 1 speed-adjusted beat length.
@@ -73,17 +72,17 @@ namespace osu.Game.Rulesets.Taiko.Objects
overallDifficulty = difficulty.OverallDifficulty;
}
- protected override void CreateNestedHitObjects()
+ protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{
- createTicks();
+ createTicks(cancellationToken);
RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * overallDifficulty);
RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * overallDifficulty);
- base.CreateNestedHitObjects();
+ base.CreateNestedHitObjects(cancellationToken);
}
- private void createTicks()
+ private void createTicks(CancellationToken cancellationToken)
{
if (tickSpacing == 0)
return;
@@ -92,6 +91,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
for (double t = StartTime; t < EndTime + tickSpacing / 2; t += tickSpacing)
{
+ cancellationToken.ThrowIfCancellationRequested();
+
AddNested(new DrumRollTick
{
FirstTick = first,
@@ -112,11 +113,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
double IHasDistance.Distance => Duration * Velocity;
- int IHasRepeats.RepeatCount { get => 0; set { } }
-
- List> IHasRepeats.NodeSamples => new List>();
-
- SliderPath IHasCurve.Path
+ SliderPath IHasPath.Path
=> new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER);
#endregion
diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs
index 2f06066a16..8a63a89951 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Threading;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
@@ -9,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
{
- public class Swell : TaikoHitObject, IHasEndTime
+ public class Swell : TaikoHitObject, IHasDuration
{
public double EndTime
{
@@ -29,12 +30,15 @@ namespace osu.Game.Rulesets.Taiko.Objects
set => throw new NotSupportedException($"{nameof(Swell)} cannot be a strong hitobject.");
}
- protected override void CreateNestedHitObjects()
+ protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{
- base.CreateNestedHitObjects();
+ base.CreateNestedHitObjects(cancellationToken);
for (int i = 0; i < RequiredHits; i++)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
AddNested(new SwellTick());
+ }
}
public override Judgement CreateJudgement() => new TaikoSwellJudgement();
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
index c41727557b..206bfcfdb2 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Threading;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
@@ -32,9 +33,9 @@ namespace osu.Game.Rulesets.Taiko.Objects
///
public virtual bool IsStrong { get; set; }
- protected override void CreateNestedHitObjects()
+ protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{
- base.CreateNestedHitObjects();
+ base.CreateNestedHitObjects(cancellationToken);
if (IsStrong)
AddNested(new StrongHitObject { StartTime = this.GetEndTime() });
diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions-expected-conversion.json b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions-expected-conversion.json
new file mode 100644
index 0000000000..47ca6aef68
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions-expected-conversion.json
@@ -0,0 +1,116 @@
+{
+ "Mappings": [
+ {
+ "StartTime": 110.0,
+ "Objects": [
+ {
+ "StartTime": 110.0,
+ "EndTime": 110.0,
+ "IsRim": false,
+ "IsCentre": true,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ }
+ ]
+ },
+ {
+ "StartTime": 538.0,
+ "Objects": [
+ {
+ "StartTime": 538.0,
+ "EndTime": 538.0,
+ "IsRim": true,
+ "IsCentre": false,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ }
+ ]
+ },
+ {
+ "StartTime": 967.0,
+ "Objects": [
+ {
+ "StartTime": 967.0,
+ "EndTime": 967.0,
+ "IsRim": true,
+ "IsCentre": false,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ }
+ ]
+ },
+ {
+ "StartTime": 1395.0,
+ "Objects": [
+ {
+ "StartTime": 1395.0,
+ "EndTime": 1395.0,
+ "IsRim": true,
+ "IsCentre": false,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ }
+ ]
+ },
+ {
+ "StartTime": 1824.0,
+ "Objects": [
+ {
+ "StartTime": 1824.0,
+ "EndTime": 1824.0,
+ "IsRim": false,
+ "IsCentre": true,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": true
+ }
+ ]
+ },
+ {
+ "StartTime": 2252.0,
+ "Objects": [
+ {
+ "StartTime": 2252.0,
+ "EndTime": 2252.0,
+ "IsRim": true,
+ "IsCentre": false,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": true
+ }
+ ]
+ },
+ {
+ "StartTime": 2681.0,
+ "Objects": [
+ {
+ "StartTime": 2681.0,
+ "EndTime": 2681.0,
+ "IsRim": true,
+ "IsCentre": false,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": true
+ }
+ ]
+ },
+ {
+ "StartTime": 3110.0,
+ "Objects": [
+ {
+ "StartTime": 3110.0,
+ "EndTime": 3110.0,
+ "IsRim": true,
+ "IsCentre": false,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": true
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions.osu b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions.osu
new file mode 100644
index 0000000000..a3537e7149
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions.osu
@@ -0,0 +1,62 @@
+osu file format v14
+
+[General]
+AudioFilename: audio.mp3
+AudioLeadIn: 0
+PreviewTime: -1
+Countdown: 0
+SampleSet: Normal
+StackLeniency: 0.5
+Mode: 1
+LetterboxInBreaks: 0
+WidescreenStoryboard: 1
+
+[Editor]
+Bookmarks: 110,13824,54967,82395,109824
+DistanceSpacing: 0.1
+BeatDivisor: 4
+GridSize: 32
+TimelineZoom: 3.099999
+
+[Metadata]
+Title:test
+TitleUnicode:test
+Artist:sample conversion
+ArtistUnicode:sample conversion
+Creator:banchobot
+Version:sample test
+Source:
+Tags:
+BeatmapID:0
+BeatmapSetID:-1
+
+[Difficulty]
+HPDrainRate:6
+CircleSize:2
+OverallDifficulty:6
+ApproachRate:7
+SliderMultiplier:1.4
+SliderTickRate:4
+
+[Events]
+//Background and Video events
+//Break Periods
+//Storyboard Layer 0 (Background)
+//Storyboard Layer 1 (Fail)
+//Storyboard Layer 2 (Pass)
+//Storyboard Layer 3 (Foreground)
+//Storyboard Layer 4 (Overlay)
+//Storyboard Sound Samples
+
+[TimingPoints]
+110,428.571428571429,4,1,0,100,1,0
+
+[HitObjects]
+256,192,110,5,0,0:0:0:0:
+256,192,538,1,8,0:0:0:0:
+256,192,967,1,2,0:0:0:0:
+256,192,1395,1,10,0:0:0:0:
+256,192,1824,1,4,0:0:0:0:
+256,192,2252,1,12,0:0:0:0:
+256,192,2681,1,6,0:0:0:0:
+256,192,3110,1,14,0:0:0:0:
diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs
new file mode 100644
index 0000000000..03813e0a99
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs
@@ -0,0 +1,152 @@
+// 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.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Screens.Play;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Taiko.Skinning
+{
+ public class LegacyTaikoScroller : CompositeDrawable
+ {
+ public Bindable LastResult = new Bindable();
+
+ public LegacyTaikoScroller()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader(true)]
+ private void load(GameplayBeatmap gameplayBeatmap)
+ {
+ if (gameplayBeatmap != null)
+ ((IBindable)LastResult).BindTo(gameplayBeatmap.LastJudgementResult);
+ }
+
+ private bool passing;
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ LastResult.BindValueChanged(result =>
+ {
+ var r = result.NewValue;
+
+ // always ignore hitobjects that don't affect combo (drumroll ticks etc.)
+ if (r?.Judgement.AffectsCombo == false)
+ return;
+
+ passing = r == null || r.Type > HitResult.Miss;
+
+ foreach (var sprite in InternalChildren.OfType())
+ sprite.Passing = passing;
+ }, true);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ // store X before checking wide enough so if we perform layout there is no positional discrepancy.
+ float currentX = (InternalChildren?.FirstOrDefault()?.X ?? 0) - (float)Clock.ElapsedFrameTime * 0.1f;
+
+ // ensure we have enough sprites
+ if (!InternalChildren.Any()
+ || InternalChildren.First().ScreenSpaceDrawQuad.Width * InternalChildren.Count < ScreenSpaceDrawQuad.Width * 2)
+ AddInternal(new ScrollerSprite { Passing = passing });
+
+ var first = InternalChildren.First();
+ var last = InternalChildren.Last();
+
+ foreach (var sprite in InternalChildren)
+ {
+ // add the x coordinates and perform re-layout on all sprites as spacing may change with gameplay scale.
+ sprite.X = currentX;
+ currentX += sprite.DrawWidth;
+ }
+
+ if (first.ScreenSpaceDrawQuad.TopLeft.X >= ScreenSpaceDrawQuad.TopLeft.X)
+ {
+ foreach (var internalChild in InternalChildren)
+ internalChild.X -= first.DrawWidth;
+ }
+
+ if (last.ScreenSpaceDrawQuad.TopRight.X <= ScreenSpaceDrawQuad.TopRight.X)
+ {
+ foreach (var internalChild in InternalChildren)
+ internalChild.X += first.DrawWidth;
+ }
+ }
+
+ private class ScrollerSprite : CompositeDrawable
+ {
+ private Sprite passingSprite;
+ private Sprite failingSprite;
+
+ private bool passing = true;
+
+ public bool Passing
+ {
+ get => passing;
+ set
+ {
+ if (value == passing)
+ return;
+
+ passing = value;
+
+ if (IsLoaded)
+ updatePassing();
+ }
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ AutoSizeAxes = Axes.X;
+ RelativeSizeAxes = Axes.Y;
+
+ FillMode = FillMode.Fit;
+
+ InternalChildren = new Drawable[]
+ {
+ passingSprite = new Sprite { Texture = skin.GetTexture("taiko-slider") },
+ failingSprite = new Sprite { Texture = skin.GetTexture("taiko-slider-fail"), Alpha = 0 },
+ };
+
+ updatePassing();
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ foreach (var c in InternalChildren)
+ c.Scale = new Vector2(DrawHeight / c.Height);
+ }
+
+ private void updatePassing()
+ {
+ if (passing)
+ {
+ passingSprite.Show();
+ failingSprite.FadeOut(200);
+ }
+ else
+ {
+ failingSprite.FadeIn(200);
+ passingSprite.Delay(200).FadeOut();
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
index 5dfc7ec0df..6e9a37eb93 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
@@ -8,6 +8,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
+using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.Skinning
@@ -85,6 +86,18 @@ namespace osu.Game.Rulesets.Taiko.Skinning
return new LegacyHitExplosion(sprite);
return null;
+
+ case TaikoSkinComponents.Scroller:
+ if (GetTexture("taiko-slider") != null)
+ return new LegacyTaikoScroller();
+
+ return null;
+
+ case TaikoSkinComponents.Mascot:
+ if (GetTexture("pippidonclear0") != null)
+ return new DrawableTaikoMascot();
+
+ return null;
}
return source.GetDrawableComponent(component);
diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
index fd091f97d0..ac4fb51661 100644
--- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
@@ -18,5 +18,7 @@ namespace osu.Game.Rulesets.Taiko
TaikoExplosionMiss,
TaikoExplosionGood,
TaikoExplosionGreat,
+ Scroller,
+ Mascot,
}
}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs
new file mode 100644
index 0000000000..407ab30e12
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs
@@ -0,0 +1,123 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Framework.Allocation;
+using osu.Framework.Audio.Track;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Graphics.Containers;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Taiko.Judgements;
+using osu.Game.Screens.Play;
+
+namespace osu.Game.Rulesets.Taiko.UI
+{
+ public class DrawableTaikoMascot : BeatSyncedContainer
+ {
+ public readonly Bindable State;
+ public readonly Bindable LastResult;
+
+ private readonly Dictionary animations;
+ private TaikoMascotAnimation currentAnimation;
+
+ private bool lastObjectHit = true;
+ private bool kiaiMode;
+
+ public DrawableTaikoMascot(TaikoMascotAnimationState startingState = TaikoMascotAnimationState.Idle)
+ {
+ Origin = Anchor = Anchor.BottomLeft;
+
+ State = new Bindable(startingState);
+ LastResult = new Bindable();
+
+ animations = new Dictionary();
+ }
+
+ [BackgroundDependencyLoader(true)]
+ private void load(TextureStore textures, GameplayBeatmap gameplayBeatmap)
+ {
+ InternalChildren = new[]
+ {
+ animations[TaikoMascotAnimationState.Idle] = new TaikoMascotAnimation(TaikoMascotAnimationState.Idle),
+ animations[TaikoMascotAnimationState.Clear] = new TaikoMascotAnimation(TaikoMascotAnimationState.Clear),
+ animations[TaikoMascotAnimationState.Kiai] = new TaikoMascotAnimation(TaikoMascotAnimationState.Kiai),
+ animations[TaikoMascotAnimationState.Fail] = new TaikoMascotAnimation(TaikoMascotAnimationState.Fail),
+ };
+
+ if (gameplayBeatmap != null)
+ ((IBindable)LastResult).BindTo(gameplayBeatmap.LastJudgementResult);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ animations.Values.ForEach(animation => animation.Hide());
+
+ State.BindValueChanged(mascotStateChanged, true);
+ LastResult.BindValueChanged(onNewResult);
+ }
+
+ private void onNewResult(ValueChangedEvent resultChangedEvent)
+ {
+ var result = resultChangedEvent.NewValue;
+ if (result == null)
+ return;
+
+ // TODO: missing support for clear/fail state transition at end of beatmap gameplay
+
+ if (triggerComboClear(result) || triggerSwellClear(result))
+ {
+ State.Value = TaikoMascotAnimationState.Clear;
+ // always consider a clear equivalent to a hit to avoid clear -> miss transitions
+ lastObjectHit = true;
+ }
+
+ if (!result.Judgement.AffectsCombo)
+ return;
+
+ lastObjectHit = result.IsHit;
+ }
+
+ protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
+ {
+ kiaiMode = effectPoint.KiaiMode;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+ State.Value = getNextState();
+ }
+
+ private TaikoMascotAnimationState getNextState()
+ {
+ // don't change state if current animation is still playing (and we haven't rewound before it).
+ // used for clear state - others are manually animated on new beats.
+ if (currentAnimation?.Completed == false && currentAnimation.DisplayTime <= Time.Current)
+ return State.Value;
+
+ if (!lastObjectHit)
+ return TaikoMascotAnimationState.Fail;
+
+ return kiaiMode ? TaikoMascotAnimationState.Kiai : TaikoMascotAnimationState.Idle;
+ }
+
+ private void mascotStateChanged(ValueChangedEvent state)
+ {
+ currentAnimation?.Hide();
+ currentAnimation = animations[state.NewValue];
+ currentAnimation.Show();
+ }
+
+ private bool triggerComboClear(JudgementResult judgementResult)
+ => (judgementResult.ComboAtJudgement + 1) % 50 == 0 && judgementResult.Judgement.AffectsCombo && judgementResult.IsHit;
+
+ private bool triggerSwellClear(JudgementResult judgementResult)
+ => judgementResult.Judgement is TaikoSwellJudgement && judgementResult.IsHit;
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
index a6a00fe242..e6aacf34dc 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Objects;
@@ -16,11 +17,15 @@ using osu.Game.Replays;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Skinning;
+using osuTK;
namespace osu.Game.Rulesets.Taiko.UI
{
public class DrawableTaikoRuleset : DrawableScrollingRuleset
{
+ private SkinnableDrawable scroller;
+
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
protected override bool UserScrollSpeedAdjustment => false;
@@ -36,6 +41,20 @@ namespace osu.Game.Rulesets.Taiko.UI
private void load()
{
new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar)));
+
+ FrameStableComponents.Add(scroller = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty())
+ {
+ RelativeSizeAxes = Axes.X,
+ Depth = float.MaxValue
+ });
+ }
+
+ protected override void UpdateAfterChildren()
+ {
+ base.UpdateAfterChildren();
+
+ var playfieldScreen = Playfield.ScreenSpaceDrawQuad;
+ scroller.Height = ToLocalSpace(playfieldScreen.TopLeft + new Vector2(0, playfieldScreen.Height / 20)).Y;
}
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer();
diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
index 38026517d9..06ccd45cb8 100644
--- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
+++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
@@ -12,6 +12,7 @@ using osu.Framework.Input.Bindings;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Audio;
+using osu.Game.Screens.Play;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.UI
@@ -145,6 +146,9 @@ namespace osu.Game.Rulesets.Taiko.UI
centreHit.Colour = colours.Pink;
}
+ [Resolved(canBeNull: true)]
+ private GameplayClock gameplayClock { get; set; }
+
public bool OnPressed(TaikoAction action)
{
Drawable target = null;
@@ -157,14 +161,16 @@ namespace osu.Game.Rulesets.Taiko.UI
target = centreHit;
back = centre;
- drumSample.Centre?.Play();
+ if (gameplayClock?.IsSeeking != true)
+ drumSample.Centre?.Play();
}
else if (action == RimAction)
{
target = rimHit;
back = rim;
- drumSample.Rim?.Play();
+ if (gameplayClock?.IsSeeking != true)
+ drumSample.Rim?.Play();
}
if (target != null)
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs
new file mode 100644
index 0000000000..cce2be7758
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs
@@ -0,0 +1,133 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Audio.Track;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Animations;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Graphics.Containers;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Taiko.UI
+{
+ public sealed class TaikoMascotAnimation : BeatSyncedContainer
+ {
+ private readonly TextureAnimation textureAnimation;
+
+ private int currentFrame;
+
+ public double DisplayTime;
+
+ public TaikoMascotAnimation(TaikoMascotAnimationState state)
+ {
+ InternalChild = textureAnimation = createTextureAnimation(state).With(animation =>
+ {
+ animation.Origin = animation.Anchor = Anchor.BottomLeft;
+ animation.Scale = new Vector2(0.51f); // close enough to stable
+ });
+
+ RelativeSizeAxes = Axes.Both;
+ Origin = Anchor = Anchor.BottomLeft;
+
+ // needs to be always present to prevent the animation clock consuming time spent when not present.
+ AlwaysPresent = true;
+ }
+
+ public bool Completed => !textureAnimation.IsPlaying || textureAnimation.PlaybackPosition >= textureAnimation.Duration;
+
+ public override void Show()
+ {
+ base.Show();
+ DisplayTime = Time.Current;
+ textureAnimation.Seek(0);
+ }
+
+ protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
+ {
+ // assume that if the animation is playing on its own, it's independent from the beat and doesn't need to be touched.
+ if (textureAnimation.FrameCount == 0 || textureAnimation.IsPlaying)
+ return;
+
+ textureAnimation.GotoFrame(currentFrame);
+ currentFrame = (currentFrame + 1) % textureAnimation.FrameCount;
+ }
+
+ private static TextureAnimation createTextureAnimation(TaikoMascotAnimationState state)
+ {
+ switch (state)
+ {
+ case TaikoMascotAnimationState.Clear:
+ return new ClearMascotTextureAnimation();
+
+ case TaikoMascotAnimationState.Idle:
+ case TaikoMascotAnimationState.Kiai:
+ case TaikoMascotAnimationState.Fail:
+ return new ManualMascotTextureAnimation(state);
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(state), $"Mascot animations for state {state} are not supported");
+ }
+ }
+
+ private class ManualMascotTextureAnimation : TextureAnimation
+ {
+ private readonly TaikoMascotAnimationState state;
+
+ public ManualMascotTextureAnimation(TaikoMascotAnimationState state)
+ {
+ this.state = state;
+
+ IsPlaying = false;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ for (int frameIndex = 0; true; frameIndex++)
+ {
+ var texture = getAnimationFrame(skin, state, frameIndex);
+
+ if (texture == null)
+ break;
+
+ AddFrame(texture);
+ }
+ }
+ }
+
+ private class ClearMascotTextureAnimation : TextureAnimation
+ {
+ private const float clear_animation_speed = 1000 / 10f;
+
+ private static readonly int[] clear_animation_sequence = { 0, 1, 2, 3, 4, 5, 6, 5, 6, 5, 4, 3, 2, 1, 0 };
+
+ public ClearMascotTextureAnimation()
+ {
+ DefaultFrameLength = clear_animation_speed;
+ Loop = false;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ foreach (var frameIndex in clear_animation_sequence)
+ {
+ var texture = getAnimationFrame(skin, TaikoMascotAnimationState.Clear, frameIndex);
+
+ if (texture == null)
+ // as per https://osu.ppy.sh/help/wiki/Skinning/osu!taiko#pippidon
+ break;
+
+ AddFrame(texture);
+ }
+ }
+ }
+
+ private static Texture getAnimationFrame(ISkin skin, TaikoMascotAnimationState state, int frameIndex)
+ => skin.GetTexture($"pippidon{state.ToString().ToLower()}{frameIndex}");
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimationState.cs b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimationState.cs
new file mode 100644
index 0000000000..02bf245b7b
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimationState.cs
@@ -0,0 +1,13 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Taiko.UI
+{
+ public enum TaikoMascotAnimationState
+ {
+ Idle,
+ Clear,
+ Kiai,
+ Fail
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index 5c763cb332..dabdfe6f44 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -15,6 +15,7 @@ using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Skinning;
+using osuTK;
namespace osu.Game.Rulesets.Taiko.UI
{
@@ -32,6 +33,7 @@ namespace osu.Game.Rulesets.Taiko.UI
private JudgementContainer judgementContainer;
private ScrollingHitObjectContainer drumRollHitContainer;
internal Drawable HitTarget;
+ private SkinnableDrawable mascot;
private ProxyContainer topLevelHitContainer;
private ProxyContainer barlineContainer;
@@ -125,12 +127,20 @@ namespace osu.Game.Rulesets.Taiko.UI
},
}
},
+ mascot = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Mascot), _ => Empty())
+ {
+ Origin = Anchor.BottomLeft,
+ Anchor = Anchor.TopLeft,
+ RelativePositionAxes = Axes.Y,
+ RelativeSizeAxes = Axes.None,
+ Y = 0.2f
+ },
topLevelHitContainer = new ProxyContainer
{
Name = "Top level hit objects",
RelativeSizeAxes = Axes.Both,
},
- drumRollHitContainer.CreateProxy()
+ drumRollHitContainer.CreateProxy(),
};
}
@@ -142,6 +152,8 @@ namespace osu.Game.Rulesets.Taiko.UI
// This is basically allowing for correct alignment as relative pieces move around them.
rightArea.Padding = new MarginPadding { Left = leftArea.DrawWidth };
hitTargetOffsetContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 };
+
+ mascot.Scale = new Vector2(DrawHeight / DEFAULT_HEIGHT);
}
public override void Add(DrawableHitObject h)
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs
index 980f5ea340..1041456020 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs
@@ -13,18 +13,16 @@ namespace osu.Game.Rulesets.Taiko.UI
private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768;
private const float default_aspect = 16f / 9f;
- public TaikoPlayfieldAdjustmentContainer()
- {
- Anchor = Anchor.CentreLeft;
- Origin = Anchor.CentreLeft;
- }
-
protected override void Update()
{
base.Update();
float aspectAdjust = Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect;
Size = new Vector2(1, default_relative_height * aspectAdjust);
+
+ // Position the taiko playfield exactly one playfield from the top of the screen.
+ RelativePositionAxes = Axes.Y;
+ Y = Size.Y;
}
}
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index acb30a6277..dab923d75b 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -365,7 +365,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
var hitObjects = decoder.Decode(stream).HitObjects;
- var curveData = hitObjects[0] as IHasCurve;
+ var curveData = hitObjects[0] as IHasPathWithRepeats;
var positionData = hitObjects[0] as IHasPosition;
Assert.IsNotNull(positionData);
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
index bcc873b0b7..30331e98d2 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
@@ -6,6 +6,7 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Text;
using NUnit.Framework;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
@@ -28,14 +29,15 @@ namespace osu.Game.Tests.Beatmaps.Formats
private static IEnumerable allBeatmaps => TestResources.GetStore().GetAvailableResources().Where(res => res.EndsWith(".osu"));
[TestCaseSource(nameof(allBeatmaps))]
- public void TestBeatmap(string name)
+ public void TestEncodeDecodeStability(string name)
{
- var decoded = decode(name, out var encoded);
+ var decoded = decodeFromLegacy(TestResources.GetStore().GetStream(name));
+ var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded));
sort(decoded);
- sort(encoded);
+ sort(decodedAfterEncode);
- Assert.That(encoded.Serialize(), Is.EqualTo(decoded.Serialize()));
+ Assert.That(decodedAfterEncode.Serialize(), Is.EqualTo(decoded.Serialize()));
}
private void sort(IBeatmap beatmap)
@@ -48,27 +50,22 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
- private IBeatmap decode(string filename, out IBeatmap encoded)
+ private IBeatmap decodeFromLegacy(Stream stream)
{
- using (var stream = TestResources.GetStore().GetStream(filename))
- using (var sr = new LineBufferedReader(stream))
- {
- var legacyDecoded = convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr));
+ using (var reader = new LineBufferedReader(stream))
+ return convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader));
+ }
- using (var ms = new MemoryStream())
- using (var sw = new StreamWriter(ms))
- using (var sr2 = new LineBufferedReader(ms, true))
- {
- new LegacyBeatmapEncoder(legacyDecoded).Encode(sw);
+ private Stream encodeToLegacy(IBeatmap beatmap)
+ {
+ var stream = new MemoryStream();
- sw.Flush();
- ms.Position = 0;
+ using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
+ new LegacyBeatmapEncoder(beatmap).Encode(writer);
- encoded = convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr2));
+ stream.Position = 0;
- return legacyDecoded;
- }
- }
+ return stream;
}
private IBeatmap convert(IBeatmap beatmap)
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
index 2fdeadca02..9ebedb3c80 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var storyboard = decoder.Decode(stream);
Assert.IsTrue(storyboard.HasDrawable);
- Assert.AreEqual(5, storyboard.Layers.Count());
+ Assert.AreEqual(6, storyboard.Layers.Count());
StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3);
Assert.IsNotNull(background);
@@ -56,6 +56,13 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsTrue(foreground.VisibleWhenPassing);
Assert.AreEqual("Foreground", foreground.Name);
+ StoryboardLayer overlay = storyboard.Layers.FirstOrDefault(l => l.Depth == int.MinValue);
+ Assert.IsNotNull(overlay);
+ Assert.IsEmpty(overlay.Elements);
+ Assert.IsTrue(overlay.VisibleWhenFailing);
+ Assert.IsTrue(overlay.VisibleWhenPassing);
+ Assert.AreEqual("Overlay", overlay.Name);
+
int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite));
int animationCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardAnimation));
int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSampleInfo));
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
index b034e66616..b4c78ce273 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
@@ -95,7 +95,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
var beatmap = decodeAsJson(normal);
- var curveData = beatmap.HitObjects[0] as IHasCurve;
+ var curveData = beatmap.HitObjects[0] as IHasPathWithRepeats;
var positionData = beatmap.HitObjects[0] as IHasPosition;
Assert.IsNotNull(positionData);
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index ba6f5fc85c..5eb11a3264 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -156,8 +156,8 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get();
// ReSharper disable once AccessToModifiedClosure
- manager.ItemAdded += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
- manager.ItemRemoved += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
+ manager.ItemUpdated.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount));
+ manager.ItemRemoved.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount));
var imported = await LoadOszIntoOsu(osu);
@@ -166,7 +166,7 @@ namespace osu.Game.Tests.Beatmaps.IO
imported.Hash += "-changed";
manager.Update(imported);
- Assert.AreEqual(0, itemAddRemoveFireCount -= 2);
+ Assert.AreEqual(0, itemAddRemoveFireCount -= 1);
checkBeatmapSetCount(osu, 1);
checkBeatmapCount(osu, 12);
diff --git a/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs
index 9fba0f1668..6c8133660f 100644
--- a/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs
+++ b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestSingleSpan()
{
- var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray();
+ var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null, default).ToArray();
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
Assert.That(events[0].Time, Is.EqualTo(start_time));
@@ -31,7 +31,7 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestRepeat()
{
- var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray();
+ var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null, default).ToArray();
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
Assert.That(events[0].Time, Is.EqualTo(start_time));
@@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestNonEvenTicks()
{
- var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray();
+ var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null, default).ToArray();
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
Assert.That(events[0].Time, Is.EqualTo(start_time));
@@ -85,7 +85,7 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestLegacyLastTickOffset()
{
- var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray();
+ var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100, default).ToArray();
Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick));
Assert.That(events[2].Time, Is.EqualTo(900));
@@ -97,7 +97,7 @@ namespace osu.Game.Tests.Beatmaps
const double velocity = 5;
const double min_distance = velocity * 10;
- var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray();
+ var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0, default).ToArray();
Assert.Multiple(() =>
{
diff --git a/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs b/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs
new file mode 100644
index 0000000000..e663e1128e
--- /dev/null
+++ b/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs
@@ -0,0 +1,73 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Tests.NonVisual
+{
+ public class BarLineGeneratorTest
+ {
+ [Test]
+ public void TestRoundingErrorCompensation()
+ {
+ // The aim of this test is to make sure bar line generation compensates for floating-point errors.
+ // The premise of the test is that we have a single timing point that should result in bar lines
+ // that start at a time point that is a whole number every seventh beat.
+
+ // The fact it's every seventh beat is important - it's a number indivisible by 2, which makes
+ // it susceptible to rounding inaccuracies. In fact this was originally spotted in cases of maps
+ // that met exactly this criteria.
+
+ const int beat_length_numerator = 2000;
+ const int beat_length_denominator = 7;
+ const TimeSignatures signature = TimeSignatures.SimpleQuadruple;
+
+ var beatmap = new Beatmap
+ {
+ HitObjects = new List
+ {
+ new HitObject { StartTime = 0 },
+ new HitObject { StartTime = 120_000 }
+ },
+ ControlPointInfo = new ControlPointInfo()
+ };
+
+ beatmap.ControlPointInfo.Add(0, new TimingControlPoint
+ {
+ BeatLength = (double)beat_length_numerator / beat_length_denominator,
+ TimeSignature = signature
+ });
+
+ var barLines = new BarLineGenerator(beatmap).BarLines;
+
+ for (int i = 0; i * beat_length_denominator < barLines.Count; i++)
+ {
+ var barLine = barLines[i * beat_length_denominator];
+ var expectedTime = beat_length_numerator * (int)signature * i;
+
+ // every seventh bar's start time should be at least greater than the whole number we expect.
+ // It cannot be less, as that can affect overlapping scroll algorithms
+ // (the previous timing point might be chosen incorrectly if this is not the case)
+ Assert.GreaterOrEqual(barLine.StartTime, expectedTime);
+
+ // on the other side, make sure we don't stray too far from the expected time either.
+ Assert.IsTrue(Precision.AlmostEquals(barLine.StartTime, expectedTime));
+
+ // check major/minor lines for good measure too
+ Assert.AreEqual(i % (int)signature == 0, barLine.Major);
+ }
+ }
+
+ private class BarLine : IBarLine
+ {
+ public double StartTime { get; set; }
+ public bool Major { get; set; }
+ }
+ }
+}
diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
new file mode 100644
index 0000000000..743c924bbd
--- /dev/null
+++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
@@ -0,0 +1,295 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
+using osu.Framework.Platform;
+using osu.Game.Configuration;
+using osu.Game.IO;
+
+namespace osu.Game.Tests.NonVisual
+{
+ [TestFixture]
+ public class CustomDataDirectoryTest
+ {
+ [SetUp]
+ public void SetUp()
+ {
+ if (Directory.Exists(customPath))
+ Directory.Delete(customPath, true);
+ }
+
+ [Test]
+ public void TestDefaultDirectory()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestDefaultDirectory)))
+ {
+ try
+ {
+ var osu = loadOsu(host);
+ var storage = osu.Dependencies.Get();
+
+ string defaultStorageLocation = Path.Combine(Environment.CurrentDirectory, "headless", nameof(TestDefaultDirectory));
+
+ Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation));
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ private string customPath => Path.Combine(Environment.CurrentDirectory, "custom-path");
+
+ [Test]
+ public void TestCustomDirectory()
+ {
+ using (var host = new HeadlessGameHost(nameof(TestCustomDirectory)))
+ {
+ string headlessPrefix = Path.Combine("headless", nameof(TestCustomDirectory));
+
+ // need access before the game has constructed its own storage yet.
+ Storage storage = new DesktopStorage(headlessPrefix, host);
+ // manual cleaning so we can prepare a config file.
+ storage.DeleteDirectory(string.Empty);
+
+ using (var storageConfig = new StorageConfigManager(storage))
+ storageConfig.Set(StorageConfig.FullPath, customPath);
+
+ try
+ {
+ var osu = loadOsu(host);
+
+ // switch to DI'd storage
+ storage = osu.Dependencies.Get();
+
+ Assert.That(storage.GetFullPath("."), Is.EqualTo(customPath));
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ [Test]
+ public void TestSubDirectoryLookup()
+ {
+ using (var host = new HeadlessGameHost(nameof(TestSubDirectoryLookup)))
+ {
+ string headlessPrefix = Path.Combine("headless", nameof(TestSubDirectoryLookup));
+
+ // need access before the game has constructed its own storage yet.
+ Storage storage = new DesktopStorage(headlessPrefix, host);
+ // manual cleaning so we can prepare a config file.
+ storage.DeleteDirectory(string.Empty);
+
+ using (var storageConfig = new StorageConfigManager(storage))
+ storageConfig.Set(StorageConfig.FullPath, customPath);
+
+ try
+ {
+ var osu = loadOsu(host);
+
+ // switch to DI'd storage
+ storage = osu.Dependencies.Get();
+
+ string actualTestFile = Path.Combine(customPath, "rulesets", "test");
+
+ File.WriteAllText(actualTestFile, "test");
+
+ var rulesetStorage = storage.GetStorageForDirectory("rulesets");
+ var lookupPath = rulesetStorage.GetFiles(".").Single();
+
+ Assert.That(lookupPath, Is.EqualTo("test"));
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ [Test]
+ public void TestMigration()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestMigration)))
+ {
+ try
+ {
+ var osu = loadOsu(host);
+ var storage = osu.Dependencies.Get();
+
+ // ensure we perform a save
+ host.Dependencies.Get().Save();
+
+ // ensure we "use" cache
+ host.Storage.GetStorageForDirectory("cache");
+
+ // for testing nested files are not ignored (only top level)
+ host.Storage.GetStorageForDirectory("test-nested").GetStorageForDirectory("cache");
+
+ string defaultStorageLocation = Path.Combine(Environment.CurrentDirectory, "headless", nameof(TestMigration));
+
+ Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation));
+
+ osu.Migrate(customPath);
+
+ Assert.That(storage.GetFullPath("."), Is.EqualTo(customPath));
+
+ // ensure cache was not moved
+ Assert.That(host.Storage.ExistsDirectory("cache"));
+
+ // ensure nested cache was moved
+ Assert.That(!host.Storage.ExistsDirectory(Path.Combine("test-nested", "cache")));
+ Assert.That(storage.ExistsDirectory(Path.Combine("test-nested", "cache")));
+
+ foreach (var file in OsuStorage.IGNORE_FILES)
+ {
+ Assert.That(host.Storage.Exists(file), Is.True);
+ Assert.That(storage.Exists(file), Is.False);
+ }
+
+ foreach (var dir in OsuStorage.IGNORE_DIRECTORIES)
+ {
+ Assert.That(host.Storage.ExistsDirectory(dir), Is.True);
+ Assert.That(storage.ExistsDirectory(dir), Is.False);
+ }
+
+ Assert.That(new StreamReader(host.Storage.GetStream("storage.ini")).ReadToEnd().Contains($"FullPath = {customPath}"));
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ [Test]
+ public void TestMigrationBetweenTwoTargets()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestMigrationBetweenTwoTargets)))
+ {
+ try
+ {
+ var osu = loadOsu(host);
+
+ string customPath2 = $"{customPath}-2";
+
+ const string database_filename = "client.db";
+
+ Assert.DoesNotThrow(() => osu.Migrate(customPath));
+ Assert.That(File.Exists(Path.Combine(customPath, database_filename)));
+
+ Assert.DoesNotThrow(() => osu.Migrate(customPath2));
+ Assert.That(File.Exists(Path.Combine(customPath2, database_filename)));
+
+ Assert.DoesNotThrow(() => osu.Migrate(customPath));
+ Assert.That(File.Exists(Path.Combine(customPath, database_filename)));
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ [Test]
+ public void TestMigrationToSameTargetFails()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestMigrationToSameTargetFails)))
+ {
+ try
+ {
+ var osu = loadOsu(host);
+
+ Assert.DoesNotThrow(() => osu.Migrate(customPath));
+ Assert.Throws(() => osu.Migrate(customPath));
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ [Test]
+ public void TestMigrationToNestedTargetFails()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestMigrationToNestedTargetFails)))
+ {
+ try
+ {
+ var osu = loadOsu(host);
+
+ Assert.DoesNotThrow(() => osu.Migrate(customPath));
+
+ string subFolder = Path.Combine(customPath, "sub");
+
+ if (Directory.Exists(subFolder))
+ Directory.Delete(subFolder, true);
+
+ Directory.CreateDirectory(subFolder);
+
+ Assert.Throws(() => osu.Migrate(subFolder));
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ [Test]
+ public void TestMigrationToSeeminglyNestedTarget()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestMigrationToSeeminglyNestedTarget)))
+ {
+ try
+ {
+ var osu = loadOsu(host);
+
+ Assert.DoesNotThrow(() => osu.Migrate(customPath));
+
+ string seeminglySubFolder = customPath + "sub";
+
+ if (Directory.Exists(seeminglySubFolder))
+ Directory.Delete(seeminglySubFolder, true);
+
+ Directory.CreateDirectory(seeminglySubFolder);
+
+ osu.Migrate(seeminglySubFolder);
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ private OsuGameBase loadOsu(GameHost host)
+ {
+ var osu = new OsuGameBase();
+ Task.Run(() => host.Run(osu));
+ waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
+ return osu;
+ }
+
+ private static void waitForOrAssert(Func result, string failureMessage, int timeout = 60000)
+ {
+ Task task = Task.Run(() =>
+ {
+ while (!result()) Thread.Sleep(200);
+ });
+
+ Assert.IsTrue(task.Wait(timeout), failureMessage);
+ }
+ }
+}
diff --git a/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs b/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs
index 1f0c069f8d..bd578dcbc4 100644
--- a/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs
+++ b/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs
@@ -29,8 +29,22 @@ namespace osu.Game.Tests.ScrollAlgorithms
[Test]
public void TestDisplayStartTime()
{
- // Sequential scroll algorithm approximates the start time
- // This should be fixed in the future
+ // easy cases - time range adjusted for velocity fits within control point duration
+ Assert.AreEqual(2500, algorithm.GetDisplayStartTime(5000, 0, 2500, 1)); // 5000 - (2500 / 1)
+ Assert.AreEqual(13750, algorithm.GetDisplayStartTime(15000, 0, 2500, 1)); // 15000 - (2500 / 2)
+ Assert.AreEqual(20000, algorithm.GetDisplayStartTime(25000, 0, 2500, 1)); // 25000 - (2500 / 0.5)
+
+ // hard case - time range adjusted for velocity exceeds control point duration
+
+ // 1st multiplier point takes 10000 / 2500 = 4 scroll lengths
+ // 2nd multiplier point takes 10000 / (2500 / 2) = 8 scroll lengths
+ // 3rd multiplier point takes 2500 / (2500 * 2) = 0.5 scroll lengths up to hitobject start
+
+ // absolute position of the hitobject = 1000 * (4 + 8 + 0.5) = 12500
+ // minus one scroll length allowance = 12500 - 1000 = 11500 = 11.5 [scroll lengths]
+ // therefore the start time lies within the second multiplier point (because 11.5 < 4 + 8)
+ // its exact time position is = 10000 + 7.5 * (2500 / 2) = 19375
+ Assert.AreEqual(19375, algorithm.GetDisplayStartTime(22500, 0, 2500, 1000));
}
[Test]
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
index 685decf097..8deed75a56 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
@@ -152,11 +152,12 @@ namespace osu.Game.Tests.Skins
}
[Test]
- public void TestSetBeatmapVersionNoFallback()
+ public void TestSetBeatmapVersionFallsBackToUserSkin()
{
+ // completely ignoring beatmap versions for simplicity.
AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m);
AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = 1.7m);
- AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.7m);
+ AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m);
}
[Test]
@@ -172,7 +173,6 @@ namespace osu.Game.Tests.Skins
public void TestIniWithNoVersionFallsBackTo1()
{
AddStep("Parse skin with no version", () => userSource.Configuration = new LegacySkinDecoder().Decode(new LineBufferedReader(new MemoryStream())));
- AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null);
AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.0m);
}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
index f97aa48f11..d601f40afe 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.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 System.Threading;
using NUnit.Framework;
@@ -39,15 +37,6 @@ namespace osu.Game.Tests.Visual.Background
[TestFixture]
public class TestSceneUserDimBackgrounds : OsuManualInputManagerTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(ScreenWithBeatmapBackground),
- typeof(PlayerLoader),
- typeof(Player),
- typeof(UserDimContainer),
- typeof(OsuScreen)
- };
-
private DummySongSelect songSelect;
private TestPlayerLoader playerLoader;
private LoadBlockingTestPlayer player;
@@ -79,7 +68,7 @@ namespace osu.Game.Tests.Visual.Background
/// Check if properly triggers the visual settings preview when a user hovers over the visual settings panel.
///
[Test]
- public void PlayerLoaderSettingsHoverTest()
+ public void TestPlayerLoaderSettingsHover()
{
setupUserSettings();
AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new LoadBlockingTestPlayer { BlockLoad = true })));
@@ -90,11 +79,9 @@ namespace osu.Game.Tests.Visual.Background
InputManager.MoveMouseTo(playerLoader.ScreenPos);
InputManager.MoveMouseTo(playerLoader.VisualSettingsPos);
});
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
- waitForDim();
- AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
+ AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
}
///
@@ -103,20 +90,19 @@ namespace osu.Game.Tests.Visual.Background
/// We need to check that in this scenario, the dim and blur is still properly applied after entering player.
///
[Test]
- public void PlayerLoaderTransitionTest()
+ public void TestPlayerLoaderTransition()
{
performFullSetup();
AddStep("Trigger hover event", () => playerLoader.TriggerOnHover());
AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
}
///
/// Make sure the background is fully invisible (Alpha == 0) when the background should be disabled by the storyboard.
///
[Test]
- public void StoryboardBackgroundVisibilityTest()
+ public void TestStoryboardBackgroundVisibility()
{
performFullSetup();
createFakeStoryboard();
@@ -125,52 +111,46 @@ namespace osu.Game.Tests.Visual.Background
player.ReplacesBackground.Value = true;
player.StoryboardEnabled.Value = true;
});
- waitForDim();
- AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible);
+ AddUntilStep("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible);
AddStep("Disable Storyboard", () =>
{
player.ReplacesBackground.Value = false;
player.StoryboardEnabled.Value = false;
});
- waitForDim();
- AddAssert("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && !player.IsStoryboardVisible);
+ AddUntilStep("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && !player.IsStoryboardVisible);
}
///
/// When exiting player, the screen that it suspends/exits to needs to have a fully visible (Alpha == 1) background.
///
[Test]
- public void StoryboardTransitionTest()
+ public void TestStoryboardTransition()
{
performFullSetup();
createFakeStoryboard();
AddStep("Exit to song select", () => player.Exit());
- waitForDim();
- AddAssert("Background is visible", () => songSelect.IsBackgroundVisible());
+ AddUntilStep("Background is visible", () => songSelect.IsBackgroundVisible());
}
///
/// Ensure is properly accepting user-defined visual changes for a background.
///
[Test]
- public void DisableUserDimBackgroundTest()
+ public void TestDisableUserDimBackground()
{
performFullSetup();
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false);
- waitForDim();
- AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
+ AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true);
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
}
///
/// Ensure is properly accepting user-defined visual changes for a storyboard.
///
[Test]
- public void DisableUserDimStoryboardTest()
+ public void TestDisableUserDimStoryboard()
{
performFullSetup();
createFakeStoryboard();
@@ -181,41 +161,36 @@ namespace osu.Game.Tests.Visual.Background
});
AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true);
AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f);
- waitForDim();
- AddAssert("Storyboard is invisible", () => !player.IsStoryboardVisible);
+ AddUntilStep("Storyboard is invisible", () => !player.IsStoryboardVisible);
AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false);
- waitForDim();
- AddAssert("Storyboard is visible", () => player.IsStoryboardVisible);
+ AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible);
}
///
/// Check if the visual settings container retains dim and blur when pausing
///
[Test]
- public void PauseTest()
+ public void TestPause()
{
performFullSetup(true);
AddStep("Pause", () => player.Pause());
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Unpause", () => player.Resume());
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
}
///
/// Check if the visual settings container removes user dim when suspending for
///
[Test]
- public void TransitionTest()
+ public void TestTransition()
{
performFullSetup();
FadeAccessibleResults results = null;
AddStep("Transition to Results", () => player.Push(results =
new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } })));
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
- waitForDim();
- AddAssert("Screen is undimmed, original background retained", () =>
+ AddUntilStep("Screen is undimmed, original background retained", () =>
songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect());
}
@@ -223,32 +198,27 @@ namespace osu.Game.Tests.Visual.Background
/// Check if background gets undimmed and unblurred when leaving for
///
[Test]
- public void TransitionOutTest()
+ public void TestTransitionOut()
{
performFullSetup();
AddStep("Exit to song select", () => player.Exit());
- waitForDim();
- AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect());
+ AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect());
}
///
/// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim.
///
[Test]
- public void ResumeFromPlayerTest()
+ public void TestResumeFromPlayer()
{
performFullSetup();
AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos));
AddStep("Resume PlayerLoader", () => player.Restart());
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
- waitForDim();
- AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
+ AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
}
- private void waitForDim() => AddWaitStep("Wait for dim", 5);
-
private void createFakeStoryboard() => AddStep("Create storyboard", () =>
{
player.StoryboardEnabled.Value = false;
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs
index f6e69fd8bf..6cf5e6a987 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.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.Graphics;
@@ -18,7 +17,6 @@ namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneBeatDivisorControl : OsuManualInputManagerTestScene
{
- public override IReadOnlyList RequiredTypes => new[] { typeof(BindableBeatDivisor) };
private BeatDivisorControl beatDivisorControl;
private BindableBeatDivisor bindableBeatDivisor;
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs
index 417d16fdb0..8190cf5f89 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing
[Cached(typeof(EditorBeatmap))]
private readonly EditorBeatmap editorBeatmap;
- [Cached(typeof(IDistanceSnapProvider))]
+ [Cached(typeof(IPositionSnapProvider))]
private readonly SnapProvider snapProvider = new SnapProvider();
public TestSceneDistanceSnapGrid()
@@ -151,9 +151,9 @@ namespace osu.Game.Tests.Visual.Editing
=> (Vector2.Zero, 0);
}
- 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) => 10;
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs
index 2deeaef1f6..e4d7e025a8 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.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.Graphics;
using osu.Game.Screens.Edit.Components.RadioButtons;
@@ -12,8 +10,6 @@ namespace osu.Game.Tests.Visual.Editing
[TestFixture]
public class TestSceneEditorComposeRadioButtons : OsuTestScene
{
- public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableRadioButton) };
-
public TestSceneEditorComposeRadioButtons()
{
RadioButtonCollection collection;
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs
index 2cbdacb61c..3cb44d9ae8 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.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.Graphics;
using osu.Framework.Graphics.Containers;
@@ -15,8 +13,6 @@ namespace osu.Game.Tests.Visual.Editing
[TestFixture]
public class TestSceneEditorMenuBar : OsuTestScene
{
- public override IReadOnlyList RequiredTypes => new[] { typeof(EditorMenuBar), typeof(ScreenSelectionTabControl) };
-
public TestSceneEditorMenuBar()
{
Add(new Container
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs
index c92423545d..3adc1bd425 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.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.Allocation;
using osu.Framework.Graphics;
@@ -15,8 +13,6 @@ namespace osu.Game.Tests.Visual.Editing
[TestFixture]
public class TestSceneEditorSummaryTimeline : EditorClockTestScene
{
- public override IReadOnlyList RequiredTypes => new[] { typeof(SummaryTimeline) };
-
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs
index ddaca26220..7ca24346aa 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs
@@ -1,9 +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 JetBrains.Annotations;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Timing;
@@ -13,11 +11,8 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Edit;
-using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
-using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
-using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
namespace osu.Game.Tests.Visual.Editing
@@ -25,19 +20,6 @@ namespace osu.Game.Tests.Visual.Editing
[TestFixture]
public class TestSceneHitObjectComposer : EditorClockTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(SelectionHandler),
- typeof(DragBox),
- typeof(HitObjectComposer),
- typeof(OsuHitObjectComposer),
- typeof(BlueprintContainer),
- typeof(NotNullAttribute),
- typeof(HitCirclePiece),
- typeof(HitCircleSelectionBlueprint),
- typeof(HitCirclePlacementBlueprint),
- };
-
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs b/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs
index 3af976cae0..6aa884a197 100644
--- a/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs
+++ b/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs
@@ -4,8 +4,8 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Timing;
using osu.Game.Beatmaps;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components;
using osuTK;
@@ -17,9 +17,8 @@ namespace osu.Game.Tests.Visual.Editing
[BackgroundDependencyLoader]
private void load()
{
- var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
- Dependencies.CacheAs(clock);
- Dependencies.CacheAs(clock);
+ var clock = new EditorClock { IsCoupled = false };
+ Dependencies.CacheAs(clock);
var playback = new PlaybackControl
{
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineBlueprintContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineBlueprintContainer.cs
index 5ab2f49b4a..e931be044c 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineBlueprintContainer.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineBlueprintContainer.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.Graphics;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
@@ -12,11 +10,6 @@ namespace osu.Game.Tests.Visual.Editing
[TestFixture]
public class TestSceneTimelineBlueprintContainer : TimelineTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(TimelineHitObjectBlueprint),
- };
-
public override Drawable CreateTestComponent() => new TimelineBlueprintContainer();
protected override void LoadComplete()
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs
index a6dbe9571e..2a7f9389d1 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.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.Allocation;
using osu.Game.Rulesets.Osu.Beatmaps;
@@ -14,18 +12,6 @@ namespace osu.Game.Tests.Visual.Editing
[TestFixture]
public class TestSceneTimingScreen : EditorClockTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(ControlPointTable),
- typeof(ControlPointSettings),
- typeof(Section<>),
- typeof(TimingSection),
- typeof(EffectSection),
- typeof(SampleSection),
- typeof(DifficultySection),
- typeof(RowAttribute)
- };
-
[Cached(typeof(EditorBeatmap))]
private readonly EditorBeatmap editorBeatmap;
diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs
index 56b2860e96..fdb8781563 100644
--- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs
+++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.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 osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
@@ -22,14 +19,6 @@ namespace osu.Game.Tests.Visual.Editing
{
public abstract class TimelineTestScene : EditorClockTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(TimelineArea),
- typeof(Timeline),
- typeof(TimelineButton),
- typeof(CentreMarker)
- };
-
protected TimelineArea TimelineArea { get; private set; }
[BackgroundDependencyLoader]
@@ -79,7 +68,7 @@ namespace osu.Game.Tests.Visual.Editing
private IBindable beatmap { get; set; }
[Resolved]
- private IAdjustableClock adjustableClock { get; set; }
+ private EditorClock editorClock { get; set; }
public AudioVisualiser()
{
@@ -106,13 +95,15 @@ namespace osu.Game.Tests.Visual.Editing
base.Update();
if (beatmap.Value.Track.IsLoaded)
- marker.X = (float)(adjustableClock.CurrentTime / beatmap.Value.Track.Length);
+ marker.X = (float)(editorClock.CurrentTime / beatmap.Value.Track.Length);
}
}
private class StartStopButton : OsuButton
{
- private IAdjustableClock adjustableClock;
+ [Resolved]
+ private EditorClock editorClock { get; set; }
+
private bool started;
public StartStopButton()
@@ -124,22 +115,16 @@ namespace osu.Game.Tests.Visual.Editing
Action = onClick;
}
- [BackgroundDependencyLoader]
- private void load(IAdjustableClock adjustableClock)
- {
- this.adjustableClock = adjustableClock;
- }
-
private void onClick()
{
if (started)
{
- adjustableClock.Stop();
+ editorClock.Stop();
Text = "Start";
}
else
{
- adjustableClock.Start();
+ editorClock.Start();
Text = "Stop";
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs
index a6f996c30d..be17721b88 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.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;
@@ -15,11 +14,6 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneBreakTracker : OsuTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(BreakOverlay),
- };
-
private readonly BreakOverlay breakOverlay;
private readonly TestBreakTracker breakTracker;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
index b25b81c9af..bd7e894cf8 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
@@ -281,7 +281,7 @@ namespace osu.Game.Tests.Visual.Gameplay
yield return new TestHitObject
{
StartTime = original.StartTime,
- EndTime = (original as IHasEndTime)?.EndTime ?? (original.StartTime + 100)
+ Duration = (original as IHasDuration)?.Duration ?? 100
};
}
}
@@ -290,11 +290,11 @@ namespace osu.Game.Tests.Visual.Gameplay
#region HitObject
- private class TestHitObject : ConvertHitObject, IHasEndTime
+ private class TestHitObject : ConvertHitObject, IHasDuration
{
- public double EndTime { get; set; }
+ public double EndTime => StartTime + Duration;
- public double Duration => EndTime - StartTime;
+ public double Duration { get; set; }
}
private class DrawableTestHitObject : DrawableHitObject
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
index de257c9e53..85aaf20a19 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@@ -18,13 +17,6 @@ namespace osu.Game.Tests.Visual.Gameplay
return new FailPlayer();
}
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(TestSceneAllRulesetPlayers),
- typeof(TestPlayer),
- typeof(Player),
- };
-
protected override void AddCheckSteps()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
index ea3e0c2293..e8b8c7c8e9 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.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.Allocation;
@@ -20,8 +19,6 @@ namespace osu.Game.Tests.Visual.Gameplay
[Description("player pause/fail screens")]
public class TestSceneGameplayMenuOverlay : OsuManualInputManagerTestScene
{
- public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseOverlay) };
-
private FailOverlay failOverlay;
private PauseOverlay pauseOverlay;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
index 1527cba6fc..253b8d9c55 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
@@ -2,8 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
-using System;
-using System.Collections.Generic;
using osu.Game.Rulesets.Judgements;
using osu.Framework.Utils;
using osu.Framework.Graphics;
@@ -22,13 +20,6 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneHitErrorMeter : OsuTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(HitErrorMeter),
- typeof(BarHitErrorMeter),
- typeof(ColourHitErrorMeter)
- };
-
private BarHitErrorMeter barMeter;
private BarHitErrorMeter barMeter2;
private ColourHitErrorMeter colourMeter;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs
index 593dcd245c..d7a3f80256 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.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;
@@ -15,13 +13,6 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneKeyCounter : OsuManualInputManagerTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(KeyCounterKeyboard),
- typeof(KeyCounterMouse),
- typeof(KeyCounterDisplay)
- };
-
public TestSceneKeyCounter()
{
KeyCounterKeyboard testCounter;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs
index 41722b430e..0ada3cf05f 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs
@@ -1,11 +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 NUnit.Framework;
using osu.Game.Overlays;
-using osu.Game.Overlays.MedalSplash;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Gameplay
@@ -13,12 +10,6 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneMedalOverlay : OsuTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(MedalOverlay),
- typeof(DrawableMedal),
- };
-
public TestSceneMedalOverlay()
{
AddStep(@"display", () =>
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs
index 3473b03eaf..951ee1489d 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.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 osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Beatmaps.Timing;
@@ -15,11 +13,6 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneNightcoreBeatContainer : TestSceneBeatSyncedContainer
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(ModNightcore<>)
- };
-
protected override void LoadComplete()
{
base.LoadComplete();
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs
index e82722e7a2..1908988739 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs
@@ -33,7 +33,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public new HUDOverlay HUDOverlay => base.HUDOverlay;
- public new bool AllowFail => base.AllowFail;
+
+ public bool AllowFail => base.CheckModsAllowFailure();
protected override bool PauseOnFocusLost => false;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
index c9561a70fa..1809332bce 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
@@ -7,8 +7,6 @@ using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Scoring;
using osu.Game.Users;
-using System;
-using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Rulesets;
using osu.Game.Screens.Ranking;
@@ -21,11 +19,6 @@ namespace osu.Game.Tests.Visual.Gameplay
[Resolved]
private RulesetStore rulesets { get; set; }
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(ReplayDownloadButton)
- };
-
private TestReplayDownloadButton downloadButton;
public TestSceneReplayDownloadButton()
@@ -35,6 +28,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep(@"locally available state", () => downloadButton.SetDownloadState(DownloadState.LocallyAvailable));
AddStep(@"not downloaded state", () => downloadButton.SetDownloadState(DownloadState.NotDownloaded));
createButton(false);
+ createButtonNoScore();
}
private void createButton(bool withReplay)
@@ -47,6 +41,22 @@ namespace osu.Game.Tests.Visual.Gameplay
Origin = Anchor.Centre,
};
});
+
+ AddUntilStep("wait for load", () => downloadButton.IsLoaded);
+ }
+
+ private void createButtonNoScore()
+ {
+ AddStep("create button with null score", () =>
+ {
+ Child = downloadButton = new TestReplayDownloadButton(null)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ };
+ });
+
+ AddUntilStep("wait for load", () => downloadButton.IsLoaded);
}
private ScoreInfo getScoreInfo(bool replayAvailable)
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
index d03716db2e..2f15e549f7 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
@@ -16,8 +16,8 @@ using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Timing;
-using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
using osuTK.Graphics;
@@ -27,8 +27,6 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneScrollingHitObjects : OsuTestScene
{
- public override IReadOnlyList RequiredTypes => new[] { typeof(Playfield) };
-
[Cached(typeof(IReadOnlyList))]
private IReadOnlyList mods { get; set; } = Array.Empty();
@@ -80,19 +78,18 @@ namespace osu.Game.Tests.Visual.Gameplay
}
};
- setUpHitObjects();
+ hitObjectSpawnDelegate?.Cancel();
});
- private void setUpHitObjects()
+ private void setUpHitObjects() => AddStep("set up hit objects", () =>
{
scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0)));
for (int i = spawn_rate / 2; i <= time_range; i += spawn_rate)
addHitObject(Time.Current + i);
- hitObjectSpawnDelegate?.Cancel();
hitObjectSpawnDelegate = Scheduler.AddDelayed(() => addHitObject(Time.Current + time_range), spawn_rate, true);
- }
+ });
private IList testControlPoints => new List
{
@@ -104,6 +101,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestScrollAlgorithms()
{
+ setUpHitObjects();
+
AddStep("constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
AddStep("overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
AddStep("sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
@@ -116,6 +115,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestConstantScrollLifetime()
{
+ setUpHitObjects();
+
AddStep("set constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
// scroll container time range must be less than the rate of spawning hitobjects
// otherwise the hitobjects will spawn already partly visible on screen and look wrong
@@ -125,14 +126,40 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestSequentialScrollLifetime()
{
+ setUpHitObjects();
+
AddStep("set sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
}
+ [Test]
+ public void TestSlowSequentialScroll()
+ {
+ AddStep("set sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
+ AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range));
+ AddStep("add control points", () => addControlPoints(
+ new List
+ {
+ new MultiplierControlPoint { Velocity = 0.1 }
+ },
+ Time.Current + time_range));
+
+ // All of the hit objects added below should be immediately visible on screen
+ AddStep("add hit objects", () =>
+ {
+ for (int i = 0; i < 20; ++i)
+ {
+ addHitObject(Time.Current + time_range * (2 + 0.1 * i));
+ }
+ });
+ }
+
[Test]
public void TestOverlappingScrollLifetime()
{
+ setUpHitObjects();
+
AddStep("set overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
@@ -224,7 +251,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private class TestDrawableControlPoint : DrawableHitObject
{
public TestDrawableControlPoint(ScrollingDirection direction, double time)
- : base(new HitObject { StartTime = time })
+ : base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
{
Origin = Anchor.Centre;
@@ -255,7 +282,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private class TestDrawableHitObject : DrawableHitObject
{
public TestDrawableHitObject(double time)
- : base(new HitObject { StartTime = time })
+ : base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
{
Origin = Anchor.Custom;
OriginPosition = new Vector2(75 / 4.0f);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
index 6a0f86fe53..7ed7a116b4 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
@@ -2,9 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
@@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneSkipOverlay : OsuManualInputManagerTestScene
{
- private SkipOverlay skip;
+ private TestSkipOverlay skip;
private int requestCount;
private double increment;
@@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Gameplay
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
- skip = new SkipOverlay(skip_time)
+ skip = new TestSkipOverlay(skip_time)
{
RequestSkip = () =>
{
@@ -56,19 +56,19 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestFadeOnIdle()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(Vector2.Zero));
- AddUntilStep("fully visible", () => skip.Children.First().Alpha == 1);
- AddUntilStep("wait for fade", () => skip.Children.First().Alpha < 1);
+ AddUntilStep("fully visible", () => skip.FadingContent.Alpha == 1);
+ AddUntilStep("wait for fade", () => skip.FadingContent.Alpha < 1);
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
- AddUntilStep("fully visible", () => skip.Children.First().Alpha == 1);
- AddUntilStep("wait for fade", () => skip.Children.First().Alpha < 1);
+ AddUntilStep("fully visible", () => skip.FadingContent.Alpha == 1);
+ AddUntilStep("wait for fade", () => skip.FadingContent.Alpha < 1);
}
[Test]
public void TestClickableAfterFade()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
- AddUntilStep("wait for fade", () => skip.Children.First().Alpha == 0);
+ AddUntilStep("wait for fade", () => skip.FadingContent.Alpha == 0);
AddStep("click", () => InputManager.Click(MouseButton.Left));
checkRequestCount(1);
}
@@ -105,13 +105,25 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("button down", () => InputManager.PressButton(MouseButton.Left));
- AddUntilStep("wait for overlay disappear", () => !skip.IsPresent);
- AddAssert("ensure button didn't disappear", () => skip.Children.First().Alpha > 0);
+ AddUntilStep("wait for overlay disappear", () => !skip.OverlayContent.IsPresent);
+ AddAssert("ensure button didn't disappear", () => skip.FadingContent.Alpha > 0);
AddStep("button up", () => InputManager.ReleaseButton(MouseButton.Left));
checkRequestCount(0);
}
private void checkRequestCount(int expected) =>
AddAssert($"request count is {expected}", () => requestCount == expected);
+
+ private class TestSkipOverlay : SkipOverlay
+ {
+ public TestSkipOverlay(double startTime)
+ : base(startTime)
+ {
+ }
+
+ public Drawable OverlayContent => InternalChild;
+
+ public Drawable FadingContent => (OverlayContent as Container)?.Child;
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
index b9b13d7bd8..733e8f4290 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.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 NUnit.Framework;
using osu.Framework.Allocation;
@@ -20,11 +19,6 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneSongProgress : OsuTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(SongProgressBar),
- };
-
private SongProgress progress;
private TestSongProgressGraph graph;
private readonly Container progressContainer;
diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs
index 33811f9529..2d2f1a1618 100644
--- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs
+++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.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.Allocation;
using osu.Framework.Graphics;
@@ -18,13 +16,6 @@ namespace osu.Game.Tests.Visual.Menus
[TestFixture]
public abstract class IntroTestScene : OsuTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(StartupScreen),
- typeof(IntroScreen),
- typeof(IntroTestScene),
- };
-
[Cached]
private OsuLogo logo;
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
index 8fbbc8ebd8..b4985cad9f 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.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;
@@ -17,14 +15,6 @@ namespace osu.Game.Tests.Visual.Menus
[TestFixture]
public class TestSceneToolbar : OsuManualInputManagerTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(ToolbarButton),
- typeof(ToolbarRulesetSelector),
- typeof(ToolbarRulesetTabButton),
- typeof(ToolbarNotificationButton),
- };
-
private Toolbar toolbar;
[Resolved]
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
index 713ba13439..5ef4dd6773 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.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,12 +21,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneDrawableRoomPlaylist : OsuManualInputManagerTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(DrawableRoomPlaylist),
- typeof(DrawableRoomPlaylistItem)
- };
-
private TestPlaylist playlist;
[Test]
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs
index 1e1bc9725c..8b74eb5f27 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.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.Graphics;
using osu.Game.Online.Multiplayer;
@@ -14,11 +13,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneLoungeRoomInfo : MultiplayerTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(RoomInfo)
- };
-
[SetUp]
public void Setup() => Schedule(() =>
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
index b5d946d049..77b41c89b0 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.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.Allocation;
@@ -23,12 +22,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneLoungeRoomsContainer : MultiplayerTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(RoomsContainer),
- typeof(DrawableRoom)
- };
-
[Cached(Type = typeof(IRoomManager))]
private TestRoomManager roomManager = new TestRoomManager();
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs
index cf40995fc0..38eb3181bf 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.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.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets.Osu;
@@ -14,11 +12,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchHeader : MultiplayerTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(Header)
- };
-
public TestSceneMatchHeader()
{
Room.Playlist.Add(new PlaylistItem
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboardChatDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboardChatDisplay.cs
index e46386b263..72bbc11cd0 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboardChatDisplay.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboardChatDisplay.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.Game.Screens.Multi.Match.Components;
@@ -12,11 +10,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchLeaderboardChatDisplay : MultiplayerTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(LeaderboardChatDisplay)
- };
-
protected override bool UseOnlineAPI => true;
public TestSceneMatchLeaderboardChatDisplay()
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs
index 047e9d860d..34c6940552 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.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.Bindables;
@@ -18,11 +17,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchSettingsOverlay : MultiplayerTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(MatchSettingsOverlay)
- };
-
[Cached(Type = typeof(IRoomManager))]
private TestRoomManager roomManager = new TestRoomManager();
@@ -75,6 +69,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
settings.NameField.Current.Value = expected_name;
settings.DurationField.Current.Value = expectedDuration;
+ Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
roomManager.CreateRequested = r =>
{
@@ -95,6 +90,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("setup", () =>
{
+ Room.Name.Value = "Test Room";
+ Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
+
fail = true;
roomManager.CreateRequested = _ => !fail;
});
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs
index 2c6f34d8a6..5cff2d7d05 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs
@@ -23,12 +23,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchSongSelect : MultiplayerTestScene
{
- public override IReadOnlyList