diff --git a/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml
index 7515e76054..4bb9f4d2a0 100644
--- a/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml
+++ b/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/global.json b/global.json
index 6858d4044d..0223dc7330 100644
--- a/global.json
+++ b/global.json
@@ -5,6 +5,6 @@
"version": "3.1.100"
},
"msbuild-sdks": {
- "Microsoft.Build.Traversal": "2.0.24"
+ "Microsoft.Build.Traversal": "2.0.32"
}
}
\ No newline at end of file
diff --git a/osu.Android.props b/osu.Android.props
index 7e17f9da16..77365b51a9 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,7 +51,7 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail@2x.png
similarity index 100%
rename from osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail.png
rename to osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail@2x.png
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai@2x.png
similarity index 100%
rename from osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai.png
rename to osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai@2x.png
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
index 6bad45f7ba..49ab70f5d7 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
@@ -1,9 +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.Linq;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -28,6 +28,9 @@ namespace osu.Game.Rulesets.Catch.Tests
{
public class TestSceneHyperDashColouring : OsuTestScene
{
+ [Resolved]
+ private SkinManager skins { get; set; }
+
[Test]
public void TestHyperDashCatcherColour()
{
@@ -35,32 +38,31 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("setup catcher", () =>
{
- Child = setupSkinHierarchy(() =>
- catcherArea = new CatcherArea
- {
- RelativePositionAxes = Axes.None,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Scale = new Vector2(4f),
- }, false);
+ Child = setupSkinHierarchy(catcherArea = new CatcherArea
+ {
+ RelativePositionAxes = Axes.None,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(4f),
+ }, false, false, false);
});
- AddStep("set hyper-dash", () =>
+ AddStep("start hyper-dashing", () =>
{
catcherArea.MovableCatcher.SetHyperDashState(2);
catcherArea.MovableCatcher.FinishTransforms();
});
- AddAssert("catcher colour default-hyperdash", () => catcherArea.MovableCatcher.Colour == Color4.OrangeRed);
- AddAssert("catcher trails colour default-hyperdash", () => catcherArea.OfType>().Any(c => c.Colour == Catcher.DefaultHyperDashColour));
+ AddAssert("catcher has default hyper-dash colour", () => catcherArea.MovableCatcher.Colour == Color4.OrangeRed);
+ AddAssert("catcher trails have default hyper-dash colour", () => catcherArea.OfType>().Any(c => c.Colour == Catcher.DefaultHyperDashColour));
- AddStep("clear hyper-dash", () =>
+ AddStep("finish hyper-dashing", () =>
{
catcherArea.MovableCatcher.SetHyperDashState(1);
catcherArea.MovableCatcher.FinishTransforms();
});
- AddAssert("catcher colour white", () => catcherArea.MovableCatcher.Colour == Color4.White);
+ AddAssert("hyper-dash colour cleared from catcher", () => catcherArea.MovableCatcher.Colour == Color4.White);
}
[Test]
@@ -70,24 +72,23 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("setup catcher", () =>
{
- Child = setupSkinHierarchy(() =>
- catcherArea = new CatcherArea
- {
- RelativePositionAxes = Axes.None,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Scale = new Vector2(4f),
- }, true);
+ Child = setupSkinHierarchy(catcherArea = new CatcherArea
+ {
+ RelativePositionAxes = Axes.None,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(4f),
+ }, true, false, false);
});
- AddStep("set hyper-dash", () =>
+ AddStep("start hyper-dashing", () =>
{
catcherArea.MovableCatcher.SetHyperDashState(2);
catcherArea.MovableCatcher.FinishTransforms();
});
- AddAssert("catcher colour custom-hyperdash", () => catcherArea.MovableCatcher.Colour == TestLegacySkin.CustomHyperDashColour);
- AddAssert("catcher trails colour custom-hyperdash", () => catcherArea.OfType>().Any(c => c.Colour == TestLegacySkin.CustomHyperDashColour));
+ AddAssert("catcher use hyper-dash colour from skin", () => catcherArea.MovableCatcher.Colour == TestSkin.CustomHyperDashColour);
+ AddAssert("catcher trails use hyper-dash colour from skin", () => catcherArea.OfType>().Any(c => c.Colour == TestSkin.CustomHyperDashColour));
AddStep("clear hyper-dash", () =>
{
@@ -95,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.Tests
catcherArea.MovableCatcher.FinishTransforms();
});
- AddAssert("catcher colour white", () => catcherArea.MovableCatcher.Colour == Color4.White);
+ AddAssert("hyper-dash colour cleared from catcher", () => catcherArea.MovableCatcher.Colour == Color4.White);
}
[Test]
@@ -105,18 +106,17 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("setup catcher", () =>
{
- Child = setupSkinHierarchy(() =>
- catcherArea = new CatcherArea
- {
- RelativePositionAxes = Axes.None,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Scale = new Vector2(4f),
- }, false, false, false);
+ Child = setupSkinHierarchy(catcherArea = new CatcherArea
+ {
+ RelativePositionAxes = Axes.None,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(4f),
+ }, false, false, false);
});
- AddStep("set hyper-dash", () => catcherArea.MovableCatcher.SetHyperDashState(2));
- AddAssert("end-glow sprite colour default-hyperdash", () => catcherArea.OfType>().Any(c => c.Colour == Catcher.DefaultHyperDashColour));
+ AddStep("start hyper-dashing", () => catcherArea.MovableCatcher.SetHyperDashState(2));
+ AddAssert("end-glow catcher sprite has default hyper-dash colour", () => catcherArea.OfType>().Any(c => c.Colour == Catcher.DefaultHyperDashColour));
}
[TestCase(true)]
@@ -127,18 +127,17 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("setup catcher", () =>
{
- Child = setupSkinHierarchy(() =>
- catcherArea = new CatcherArea
- {
- RelativePositionAxes = Axes.None,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Scale = new Vector2(4f),
- }, customHyperDashCatcherColour, false, true);
+ Child = setupSkinHierarchy(catcherArea = new CatcherArea
+ {
+ RelativePositionAxes = Axes.None,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(4f),
+ }, customHyperDashCatcherColour, true, false);
});
- AddStep("set hyper-dash", () => catcherArea.MovableCatcher.SetHyperDashState(2));
- AddAssert("end-glow sprite colour custom-hyperdash", () => catcherArea.OfType>().Any(c => c.Colour == TestLegacySkin.CustomHyperDashAfterColour));
+ AddStep("start hyper-dashing", () => catcherArea.MovableCatcher.SetHyperDashState(2));
+ AddAssert("end-glow catcher sprite use its hyper-dash colour from skin", () => catcherArea.OfType>().Any(c => c.Colour == TestSkin.CustomHyperDashAfterColour));
}
[Test]
@@ -148,18 +147,17 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("setup catcher", () =>
{
- Child = setupSkinHierarchy(() =>
- catcherArea = new CatcherArea
- {
- RelativePositionAxes = Axes.None,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Scale = new Vector2(4f),
- }, true, false, false);
+ Child = setupSkinHierarchy(catcherArea = new CatcherArea
+ {
+ RelativePositionAxes = Axes.None,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(4f),
+ }, true, false, false);
});
- AddStep("set hyper-dash", () => catcherArea.MovableCatcher.SetHyperDashState(2));
- AddAssert("end-glow sprite colour catcher-custom-hyperdash", () => catcherArea.OfType>().Any(c => c.Colour == TestLegacySkin.CustomHyperDashColour));
+ AddStep("start hyper-dashing", () => catcherArea.MovableCatcher.SetHyperDashState(2));
+ AddAssert("end-glow catcher sprite colour falls back to catcher colour from skin", () => catcherArea.OfType>().Any(c => c.Colour == TestSkin.CustomHyperDashColour));
}
[TestCase(false)]
@@ -168,21 +166,23 @@ namespace osu.Game.Rulesets.Catch.Tests
{
DrawableFruit drawableFruit = null;
- AddStep("setup fruit", () =>
+ AddStep("setup hyper-dash fruit", () =>
{
- var fruit = new Fruit { IndexInBeatmap = legacyFruit ? 0 : 1, HyperDashTarget = new Banana() };
+ var fruit = new Fruit { HyperDashTarget = new Banana() };
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- Child = setupSkinHierarchy(() =>
- drawableFruit = new DrawableFruit(fruit)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Scale = new Vector2(4f),
- }, false, false);
+ Child = setupSkinHierarchy(drawableFruit = new DrawableFruit(fruit)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(4f),
+ }, false, false, false, legacyFruit);
});
- AddAssert("fruit colour default-hyperdash", () => checkFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour, legacyFruit));
+ AddAssert("hyper-dash fruit has default colour", () =>
+ legacyFruit
+ ? checkLegacyFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour)
+ : checkFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour));
}
[TestCase(false, true)]
@@ -193,21 +193,23 @@ namespace osu.Game.Rulesets.Catch.Tests
{
DrawableFruit drawableFruit = null;
- AddStep("setup fruit", () =>
+ AddStep("setup hyper-dash fruit", () =>
{
- var fruit = new Fruit { IndexInBeatmap = legacyFruit ? 0 : 1, HyperDashTarget = new Banana() };
+ var fruit = new Fruit { HyperDashTarget = new Banana() };
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- Child = setupSkinHierarchy(() =>
- drawableFruit = new DrawableFruit(fruit)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Scale = new Vector2(4f),
- }, customCatcherHyperDashColour, true);
+ Child = setupSkinHierarchy(drawableFruit = new DrawableFruit(fruit)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(4f),
+ }, customCatcherHyperDashColour, false, true, legacyFruit);
});
- AddAssert("fruit colour custom-hyperdash", () => checkFruitHyperDashColour(drawableFruit, TestLegacySkin.CustomHyperDashFruitColour, legacyFruit));
+ AddAssert("hyper-dash fruit use fruit colour from skin", () =>
+ legacyFruit
+ ? checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashFruitColour)
+ : checkFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashFruitColour));
}
[TestCase(false)]
@@ -216,98 +218,85 @@ namespace osu.Game.Rulesets.Catch.Tests
{
DrawableFruit drawableFruit = null;
- AddStep("setup fruit", () =>
+ AddStep("setup hyper-dash fruit", () =>
{
- var fruit = new Fruit { IndexInBeatmap = legacyFruit ? 0 : 1, HyperDashTarget = new Banana() };
+ var fruit = new Fruit { HyperDashTarget = new Banana() };
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- Child = setupSkinHierarchy(() =>
+ Child = setupSkinHierarchy(
drawableFruit = new DrawableFruit(fruit)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(4f),
- }, true, false);
+ }, true, false, false, legacyFruit);
});
- AddAssert("fruit colour catcher-custom-hyperdash", () => checkFruitHyperDashColour(drawableFruit, TestLegacySkin.CustomHyperDashColour, legacyFruit));
+ AddAssert("hyper-dash fruit colour falls back to catcher colour from skin", () =>
+ legacyFruit
+ ? checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour)
+ : checkFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour));
}
- private Drawable setupSkinHierarchy(Func getChild, bool customHyperDashCatcherColour = false, bool customHyperDashFruitColour = false, bool customHyperDashAfterColour = false)
+ private Drawable setupSkinHierarchy(Drawable child, bool customCatcherColour, bool customAfterColour, bool customFruitColour, bool legacySkin = true)
{
- var testSkinProvider = new SkinProvidingContainer(new TestLegacySkin(customHyperDashCatcherColour, customHyperDashFruitColour, customHyperDashAfterColour));
+ var testSkinProvider = new SkinProvidingContainer(new TestSkin(customCatcherColour, customAfterColour, customFruitColour));
- var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider));
+ if (legacySkin)
+ {
+ var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.Info));
+ var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider));
- return testSkinProvider
- .WithChild(legacySkinTransformer
- .WithChild(getChild.Invoke()));
+ return legacySkinProvider
+ .WithChild(testSkinProvider
+ .WithChild(legacySkinTransformer
+ .WithChild(child)));
+ }
+
+ return testSkinProvider.WithChild(child);
}
- private bool checkFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour, bool isLegacyFruit) =>
- isLegacyFruit
- ? fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Any(c => c.Colour == expectedColour)
- : fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Single(c => c.BorderColour == expectedColour).Any(d => d.Colour == expectedColour);
+ private bool checkFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour) =>
+ fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Single(c => c.BorderColour == expectedColour).Any(d => d.Colour == expectedColour);
- private class TestLegacySkin : ISkin
+ private bool checkLegacyFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour) =>
+ fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Any(c => c.Colour == expectedColour);
+
+ private class TestSkin : ISkin
{
public static Color4 CustomHyperDashColour { get; } = Color4.Goldenrod;
public static Color4 CustomHyperDashFruitColour { get; } = Color4.Cyan;
public static Color4 CustomHyperDashAfterColour { get; } = Color4.Lime;
- private readonly bool customHyperDashCatcherColour;
- private readonly bool customHyperDashFruitColour;
- private readonly bool customHyperDashAfterColour;
+ private readonly bool customCatcherColour;
+ private readonly bool customAfterColour;
+ private readonly bool customFruitColour;
- public TestLegacySkin(bool customHyperDashCatcherColour = false, bool customHyperDashFruitColour = false, bool customHyperDashAfterColour = false)
+ public TestSkin(bool customCatcherColour, bool customAfterColour, bool customFruitColour)
{
- this.customHyperDashCatcherColour = customHyperDashCatcherColour;
- this.customHyperDashFruitColour = customHyperDashFruitColour;
- this.customHyperDashAfterColour = customHyperDashAfterColour;
+ this.customCatcherColour = customCatcherColour;
+ this.customAfterColour = customAfterColour;
+ this.customFruitColour = customFruitColour;
}
public Drawable GetDrawableComponent(ISkinComponent component) => null;
- public Texture GetTexture(string componentName)
- {
- if (componentName == "fruit-pear")
- {
- // convince CatchLegacySkinTransformer to use the LegacyFruitPiece for pear fruit.
- var texture = new Texture(Texture.WhitePixel.TextureGL)
- {
- Width = 1,
- Height = 1,
- ScaleAdjust = 1 / 96f
- };
- return texture;
- }
-
- return null;
- }
+ public Texture GetTexture(string componentName) => null;
public SampleChannel GetSample(ISampleInfo sampleInfo) => null;
public IBindable GetConfig(TLookup lookup)
{
- switch (lookup)
+ if (lookup is CatchSkinColour config)
{
- case CatchSkinConfiguration config when config == CatchSkinConfiguration.HyperDash:
- if (customHyperDashCatcherColour)
- return SkinUtils.As(new Bindable(CustomHyperDashColour));
+ if (config == CatchSkinColour.HyperDash && customCatcherColour)
+ return SkinUtils.As(new Bindable(CustomHyperDashColour));
- return null;
+ if (config == CatchSkinColour.HyperDashFruit && customFruitColour)
+ return SkinUtils.As(new Bindable(CustomHyperDashFruitColour));
- case CatchSkinConfiguration config when config == CatchSkinConfiguration.HyperDashFruit:
- if (customHyperDashFruitColour)
- return SkinUtils.As(new Bindable(CustomHyperDashFruitColour));
-
- return null;
-
- case CatchSkinConfiguration config when config == CatchSkinConfiguration.HyperDashAfterImage:
- if (customHyperDashAfterColour)
- return SkinUtils.As(new Bindable(CustomHyperDashAfterColour));
-
- return null;
+ if (config == CatchSkinColour.HyperDashAfterImage && customAfterColour)
+ return SkinUtils.As(new Bindable(CustomHyperDashAfterColour));
}
return null;
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 212365caad..ca75a816f1 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Catch
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
- public override ISkin CreateLegacySkinProvider(ISkinSource source) => new CatchLegacySkinTransformer(source);
+ public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new CatchLegacySkinTransformer(source);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new CatchPerformanceCalculator(this, beatmap, score);
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index 5880a227c2..4d9dbbbc5f 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -72,10 +72,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
protected override Skill[] CreateSkills(IBeatmap beatmap)
{
using (var catcher = new Catcher(beatmap.BeatmapInfo.BaseDifficulty))
- {
halfCatcherWidth = catcher.CatchWidth * 0.5f;
- halfCatcherWidth *= 0.8f; // We're only using 80% of the catcher's width to simulate imperfect gameplay.
- }
return new Skill[]
{
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
index e2465d727e..acdd0a420c 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.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.Linq;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
@@ -30,6 +31,22 @@ namespace osu.Game.Rulesets.Catch.Mods
Value = 5,
};
+ public override string SettingDescription
+ {
+ get
+ {
+ string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}";
+ string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}";
+
+ return string.Join(", ", new[]
+ {
+ circleSize,
+ base.SettingDescription,
+ approachRate
+ }.Where(s => !string.IsNullOrEmpty(s)));
+ }
+ }
+
protected override void TransferSettings(BeatmapDifficulty difficulty)
{
base.TransferSettings(difficulty);
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs
index c8f7c4912e..2437958916 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs
@@ -64,8 +64,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
});
var hyperDashColour =
- skin.GetConfig(CatchSkinConfiguration.HyperDashFruit)?.Value ??
- skin.GetConfig(CatchSkinConfiguration.HyperDash)?.Value ??
+ skin.GetHyperDashFruitColour()?.Value ??
Catcher.DefaultHyperDashColour;
if (hitObject.HyperDash)
diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs b/osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs
similarity index 94%
rename from osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs
rename to osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs
index aea5beaa6b..2ad8f89739 100644
--- a/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs
@@ -3,7 +3,7 @@
namespace osu.Game.Rulesets.Catch.Skinning
{
- public enum CatchSkinConfiguration
+ public enum CatchSkinColour
{
///
/// The colour to be used for the catcher while on hyper-dashing state.
diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs b/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs
new file mode 100644
index 0000000000..8fc0831918
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs
@@ -0,0 +1,16 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+using osu.Game.Skinning;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Skinning
+{
+ internal static class CatchSkinExtensions
+ {
+ public static IBindable GetHyperDashFruitColour(this ISkin skin)
+ => skin.GetConfig(CatchSkinColour.HyperDashFruit) ??
+ skin.GetConfig(CatchSkinColour.HyperDash);
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs
index 99ecf12fd3..d8489399d2 100644
--- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs
@@ -54,15 +54,10 @@ namespace osu.Game.Rulesets.Catch.Skinning
if (drawableCatchObject.HitObject.HyperDash)
{
- var hyperDashColour =
- skin.GetConfig(CatchSkinConfiguration.HyperDashFruit)?.Value ??
- skin.GetConfig(CatchSkinConfiguration.HyperDash)?.Value ??
- Catcher.DefaultHyperDashColour;
-
var hyperDash = new Sprite
{
Texture = skin.GetTexture(lookupName),
- Colour = hyperDashColour,
+ Colour = skin.GetHyperDashFruitColour()?.Value ?? Catcher.DefaultHyperDashColour,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Blending = BlendingParameters.Additive,
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index b3742aa1ad..98cc10aa31 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -64,10 +64,15 @@ namespace osu.Game.Rulesets.Catch.UI
public CatcherAnimationState CurrentState { get; private set; }
+ ///
+ /// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable.
+ ///
+ private const float allowed_catch_range = 0.8f;
+
///
/// Width of the area that can be used to attempt catches during gameplay.
///
- internal float CatchWidth => CatcherArea.CATCHER_SIZE * Math.Abs(Scale.X);
+ internal float CatchWidth => CatcherArea.CATCHER_SIZE * Math.Abs(Scale.X) * allowed_catch_range;
protected bool Dashing
{
@@ -171,14 +176,14 @@ namespace osu.Game.Rulesets.Catch.UI
var ourRadius = fruit.DisplayRadius;
float theirRadius = 0;
- const float allowance = 6;
+ const float allowance = 10;
while (caughtFruit.Any(f =>
f.LifetimeEnd == double.MaxValue &&
Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2)))
{
var diff = (ourRadius + theirRadius) / allowance;
- fruit.X += (RNG.NextSingle() - 0.5f) * 2 * diff;
+ fruit.X += (RNG.NextSingle() - 0.5f) * diff * 2;
fruit.Y -= RNG.NextSingle() * diff;
}
@@ -384,8 +389,8 @@ namespace osu.Game.Rulesets.Catch.UI
{
base.SkinChanged(skin, allowFallback);
- hyperDashColour = skin.GetConfig(CatchSkinConfiguration.HyperDash)?.Value ?? DefaultHyperDashColour;
- hyperDashEndGlowColour = skin.GetConfig(CatchSkinConfiguration.HyperDashAfterImage)?.Value ?? hyperDashColour;
+ hyperDashColour = skin.GetConfig(CatchSkinColour.HyperDash)?.Value ?? DefaultHyperDashColour;
+ hyperDashEndGlowColour = skin.GetConfig(CatchSkinColour.HyperDashAfterImage)?.Value ?? hyperDashColour;
updateCatcherColour();
}
@@ -431,7 +436,7 @@ namespace osu.Game.Rulesets.Catch.UI
}
currentCatcher.Show();
- (currentCatcher.Drawable as IAnimation)?.GotoFrame(0);
+ (currentCatcher.Drawable as IFramedAnimation)?.GotoFrame(0);
}
private void beginTrail()
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
index 52eb8d597e..ef69e3d2d1 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.UI
public CatcherSprite(CatcherAnimationState state)
: base(new CatchSkinComponent(componentFromState(state)), _ =>
- new DefaultCatcherSprite(state), confineMode: ConfineMode.ScaleDownToFit)
+ new DefaultCatcherSprite(state), confineMode: ConfineMode.ScaleToFit)
{
RelativeSizeAxes = Axes.None;
Size = new Vector2(CatcherArea.CATCHER_SIZE);
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaColumnTypeTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaColumnTypeTest.cs
new file mode 100644
index 0000000000..40a6e1fdae
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaColumnTypeTest.cs
@@ -0,0 +1,50 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using NUnit.Framework;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [TestFixture]
+ public class ManiaColumnTypeTest
+ {
+ [TestCase(new[]
+ {
+ ColumnType.Special
+ }, 1)]
+ [TestCase(new[]
+ {
+ ColumnType.Odd,
+ ColumnType.Even,
+ ColumnType.Even,
+ ColumnType.Odd
+ }, 4)]
+ [TestCase(new[]
+ {
+ ColumnType.Odd,
+ ColumnType.Even,
+ ColumnType.Odd,
+ ColumnType.Special,
+ ColumnType.Odd,
+ ColumnType.Even,
+ ColumnType.Odd
+ }, 7)]
+ public void Test(IEnumerable expected, int columns)
+ {
+ var definition = new StageDefinition
+ {
+ Columns = columns
+ };
+ var results = getResults(definition);
+ Assert.AreEqual(expected, results);
+ }
+
+ private IEnumerable getResults(StageDefinition definition)
+ {
+ for (var i = 0; i < definition.Columns; i++)
+ yield return definition.GetTypeOfColumn(i);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs
new file mode 100644
index 0000000000..ff4865c71d
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs
@@ -0,0 +1,40 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.UI;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Tests.Skinning
+{
+ ///
+ /// A container to be used in a to provide a resolvable dependency.
+ ///
+ public class ColumnTestContainer : Container
+ {
+ protected override Container Content => content;
+
+ private readonly Container content;
+
+ [Cached]
+ private readonly Column column;
+
+ public ColumnTestContainer(int column, ManiaAction action)
+ {
+ this.column = new Column(column)
+ {
+ Action = { Value = action },
+ AccentColour = Color4.Orange,
+ ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd
+ };
+
+ InternalChild = content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
+ {
+ RelativeSizeAxes = Axes.Both
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs
new file mode 100644
index 0000000000..18eebada00
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs
@@ -0,0 +1,72 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.UI.Scrolling;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Tests.Skinning
+{
+ ///
+ /// A test scene for a mania hitobject.
+ ///
+ public abstract class ManiaHitObjectTestScene : ManiaSkinnableTestScene
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ SetContents(() => new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Height = 0.7f,
+ Direction = FillDirection.Horizontal,
+ Children = new Drawable[]
+ {
+ new ColumnTestContainer(0, ManiaAction.Key1)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Y,
+ Width = 80,
+ Child = new ScrollingHitObjectContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ }.With(c =>
+ {
+ c.Add(CreateHitObject().With(h =>
+ {
+ h.HitObject.StartTime = START_TIME;
+ h.AccentColour.Value = Color4.Orange;
+ }));
+ })
+ },
+ new ColumnTestContainer(1, ManiaAction.Key2)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Y,
+ Width = 80,
+ Child = new ScrollingHitObjectContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ }.With(c =>
+ {
+ c.Add(CreateHitObject().With(h =>
+ {
+ h.HitObject.StartTime = START_TIME;
+ h.AccentColour.Value = Color4.Orange;
+ }));
+ })
+ },
+ }
+ });
+ }
+
+ protected abstract DrawableManiaHitObject CreateHitObject();
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs
new file mode 100644
index 0000000000..eaa2a56e36
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs
@@ -0,0 +1,79 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Rulesets.UI.Scrolling.Algorithms;
+using osu.Game.Tests.Visual;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Tests.Skinning
+{
+ ///
+ /// A test scene for skinnable mania components.
+ ///
+ public abstract class ManiaSkinnableTestScene : SkinnableTestScene
+ {
+ protected const double START_TIME = 1000000000;
+
+ [Cached(Type = typeof(IScrollingInfo))]
+ private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
+
+ protected ManiaSkinnableTestScene()
+ {
+ scrollingInfo.Direction.Value = ScrollingDirection.Down;
+
+ Add(new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.SlateGray.Opacity(0.2f),
+ Depth = 1
+ });
+ }
+
+ [Test]
+ public void TestScrollingDown()
+ {
+ AddStep("change direction to down", () => scrollingInfo.Direction.Value = ScrollingDirection.Down);
+ }
+
+ [Test]
+ public void TestScrollingUp()
+ {
+ AddStep("change direction to up", () => scrollingInfo.Direction.Value = ScrollingDirection.Up);
+ }
+
+ private class TestScrollingInfo : IScrollingInfo
+ {
+ public readonly Bindable Direction = new Bindable();
+
+ IBindable IScrollingInfo.Direction => Direction;
+ IBindable IScrollingInfo.TimeRange { get; } = new Bindable(1000);
+ IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ZeroScrollAlgorithm();
+ }
+
+ private class ZeroScrollAlgorithm : IScrollAlgorithm
+ {
+ public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
+ => double.MinValue;
+
+ public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
+ => scrollLength;
+
+ public float PositionAt(double time, double currentTime, double timeRange, float scrollLength)
+ => (float)((time - START_TIME) / timeRange) * scrollLength;
+
+ public double TimeAt(float position, double currentTime, double timeRange, float scrollLength)
+ => 0;
+
+ public void Reset()
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs
new file mode 100644
index 0000000000..d6bacbe59e
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.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 osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Mania.UI.Components;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Mania.Tests.Skinning
+{
+ public class TestSceneColumnBackground : ManiaSkinnableTestScene
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ SetContents(() => new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.8f),
+ Direction = FillDirection.Horizontal,
+ Children = new Drawable[]
+ {
+ new ColumnTestContainer(0, ManiaAction.Key1)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Width = 0.5f,
+ Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 0), _ => new DefaultColumnBackground())
+ {
+ RelativeSizeAxes = Axes.Both
+ }
+ },
+ new ColumnTestContainer(1, ManiaAction.Key2)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Width = 0.5f,
+ Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 0), _ => new DefaultColumnBackground())
+ {
+ RelativeSizeAxes = Axes.Both
+ }
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs
new file mode 100644
index 0000000000..4392666cb7
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.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 osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Mania.UI.Components;
+using osu.Game.Rulesets.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Mania.Tests.Skinning
+{
+ public class TestSceneColumnHitObjectArea : ManiaSkinnableTestScene
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ SetContents(() => new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.8f),
+ Direction = FillDirection.Horizontal,
+ Children = new Drawable[]
+ {
+ new ColumnTestContainer(0, ManiaAction.Key1)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Width = 0.5f,
+ Child = new ColumnHitObjectArea(0, new HitObjectContainer())
+ {
+ RelativeSizeAxes = Axes.Both
+ }
+ },
+ new ColumnTestContainer(1, ManiaAction.Key2)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Width = 0.5f,
+ Child = new ColumnHitObjectArea(1, new HitObjectContainer())
+ {
+ RelativeSizeAxes = Axes.Both
+ }
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
similarity index 96%
rename from osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs
rename to osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
index 692d079c16..a6bc64550f 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
@@ -6,13 +6,13 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
-using osu.Game.Tests.Visual;
-using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Tests.Visual;
-namespace osu.Game.Rulesets.Mania.Tests
+namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public class TestSceneDrawableJudgement : SkinnableTestScene
{
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs
new file mode 100644
index 0000000000..5f046574ba
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs
@@ -0,0 +1,66 @@
+// 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;
+using osuTK;
+using osuTK.Graphics;
+
+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;
+
+ AddRepeatStep("explode", () =>
+ {
+ runcount++;
+
+ if (runcount % 15 > 12)
+ return;
+
+ CreatedDrawables.OfType().ForEach(c =>
+ {
+ c.Add(new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, 0),
+ _ => new DefaultHitExplosion((runcount / 15) % 2 == 0 ? new Color4(94, 0, 57, 255) : new Color4(6, 84, 0, 255), runcount % 6 != 0)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }));
+ });
+ }, 100);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ SetContents(() => new ColumnTestContainer(0, ManiaAction.Key1)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativePositionAxes = Axes.Y,
+ Y = -0.25f,
+ Size = new Vector2(Column.COLUMN_WIDTH, DefaultNotePiece.NOTE_HEIGHT),
+ });
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs
new file mode 100644
index 0000000000..95e86de884
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs
@@ -0,0 +1,35 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using osu.Framework.Bindables;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Mania.Tests.Skinning
+{
+ public class TestSceneHoldNote : ManiaHitObjectTestScene
+ {
+ public TestSceneHoldNote()
+ {
+ AddToggleStep("toggle hitting", v =>
+ {
+ foreach (var holdNote in CreatedDrawables.SelectMany(d => d.ChildrenOfType()))
+ {
+ ((Bindable)holdNote.IsHitting).Value = v;
+ }
+ });
+ }
+
+ protected override DrawableManiaHitObject CreateHitObject()
+ {
+ var note = new HoldNote { Duration = 1000 };
+ note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ return new DrawableHoldNote(note);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs
new file mode 100644
index 0000000000..c8f901285a
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs
@@ -0,0 +1,58 @@
+// 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;
+
+namespace osu.Game.Rulesets.Mania.Tests.Skinning
+{
+ public class TestSceneKeyArea : ManiaSkinnableTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DefaultKeyArea),
+ typeof(LegacyKeyArea)
+ };
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ SetContents(() => new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.8f),
+ Direction = FillDirection.Horizontal,
+ Children = new Drawable[]
+ {
+ new ColumnTestContainer(0, ManiaAction.Key1)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Width = 0.5f,
+ Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, 0), _ => new DefaultKeyArea())
+ {
+ RelativeSizeAxes = Axes.Both
+ },
+ },
+ new ColumnTestContainer(1, ManiaAction.Key2)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Width = 0.5f,
+ Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, 1), _ => new DefaultKeyArea())
+ {
+ RelativeSizeAxes = Axes.Both
+ },
+ },
+ }
+ });
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs
new file mode 100644
index 0000000000..bc3bdf0bcb
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs
@@ -0,0 +1,21 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Mania.Tests.Skinning
+{
+ public class TestSceneNote : ManiaHitObjectTestScene
+ {
+ protected override DrawableManiaHitObject CreateHitObject()
+ {
+ var note = new Note();
+ note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ return new DrawableNote(note);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
new file mode 100644
index 0000000000..161eda650e
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
@@ -0,0 +1,52 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.UI;
+
+namespace osu.Game.Rulesets.Mania.Tests.Skinning
+{
+ public class TestScenePlayfield : ManiaSkinnableTestScene
+ {
+ private List stageDefinitions = new List();
+
+ [Test]
+ public void TestSingleStage()
+ {
+ AddStep("create stage", () =>
+ {
+ stageDefinitions = new List
+ {
+ new StageDefinition { Columns = 2 }
+ };
+
+ SetContents(() => new ManiaPlayfield(stageDefinitions));
+ });
+ }
+
+ [Test]
+ public void TestDualStages()
+ {
+ AddStep("create stage", () =>
+ {
+ stageDefinitions = new List
+ {
+ new StageDefinition { Columns = 2 },
+ new StageDefinition { Columns = 2 }
+ };
+
+ SetContents(() => new ManiaPlayfield(stageDefinitions));
+ });
+ }
+
+ protected override IBeatmap CreateBeatmapForSkinProvider()
+ {
+ var maniaBeatmap = (ManiaBeatmap)base.CreateBeatmapForSkinProvider();
+ maniaBeatmap.Stages = stageDefinitions;
+ return maniaBeatmap;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs
new file mode 100644
index 0000000000..0d5ebd33e9
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs
@@ -0,0 +1,27 @@
+// 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.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.UI;
+
+namespace osu.Game.Rulesets.Mania.Tests.Skinning
+{
+ public class TestSceneStage : ManiaSkinnableTestScene
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ SetContents(() =>
+ {
+ ManiaAction normalAction = ManiaAction.Key1;
+ ManiaAction specialAction = ManiaAction.Special1;
+
+ return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
+ {
+ Child = new ManiaStage(0, new StageDefinition { Columns = 4 }, ref normalAction, ref specialAction)
+ };
+ });
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs
index d94a986dae..9aad08c433 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs
@@ -28,7 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests
{
typeof(Column),
typeof(ColumnBackground),
- typeof(ColumnKeyArea),
typeof(ColumnHitObjectArea)
};
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs
deleted file mode 100644
index 26a1b1b1ec..0000000000
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using System.Collections.Generic;
-using NUnit.Framework;
-using osu.Framework.Graphics;
-using osu.Game.Rulesets.Mania.Objects.Drawables;
-using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
-using osu.Game.Rulesets.Mania.UI;
-using osu.Game.Rulesets.UI.Scrolling;
-using osu.Game.Tests.Visual;
-using osuTK;
-using osuTK.Graphics;
-
-namespace osu.Game.Rulesets.Mania.Tests
-{
- [TestFixture]
- public class TestSceneHitExplosion : OsuTestScene
- {
- private ScrollingTestContainer scrolling;
-
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(DrawableNote),
- typeof(DrawableManiaHitObject),
- };
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- Child = scrolling = new ScrollingTestContainer(ScrollingDirection.Down)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativePositionAxes = Axes.Y,
- Y = -0.25f,
- Size = new Vector2(Column.COLUMN_WIDTH, NotePiece.NOTE_HEIGHT),
- };
-
- int runcount = 0;
-
- AddRepeatStep("explode", () =>
- {
- runcount++;
-
- if (runcount % 15 > 12)
- return;
-
- scrolling.AddRange(new Drawable[]
- {
- new HitExplosion((runcount / 15) % 2 == 0 ? new Color4(94, 0, 57, 255) : new Color4(6, 84, 0, 255), runcount % 6 != 0)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- }
- });
- }, 100);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponents.cs b/osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs
similarity index 59%
rename from osu.Game.Rulesets.Mania/ManiaSkinComponents.cs
rename to osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs
index 6d85816e5a..8f904530bc 100644
--- a/osu.Game.Rulesets.Mania/ManiaSkinComponents.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs
@@ -1,9 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-namespace osu.Game.Rulesets.Mania
+namespace osu.Game.Rulesets.Mania.Beatmaps
{
- public enum ManiaSkinComponents
+ public enum ColumnType
{
+ Even,
+ Odd,
+ Special
}
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs
index dff7cb72ce..2557f2acdf 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Beatmaps
@@ -21,5 +22,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// The 0-based column index.
/// Whether the column is a special column.
public bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2;
+
+ ///
+ /// Get the type of column given a column index.
+ ///
+ /// The 0-based column index.
+ /// The type of the column.
+ public ColumnType GetTypeOfColumn(int column)
+ {
+ if (IsSpecialColumn(column))
+ return ColumnType.Special;
+
+ int distanceToEdge = Math.Min(column, (Columns - 1) - column);
+ return distanceToEdge % 2 == 0 ? ColumnType.Odd : ColumnType.Even;
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
index f5412dcfc5..7e84f17809 100644
--- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
+++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using osu.Framework.Configuration.Tracking;
using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration;
@@ -19,13 +20,14 @@ namespace osu.Game.Rulesets.Mania.Configuration
{
base.InitialiseDefaults();
- Set(ManiaRulesetSetting.ScrollTime, 1500.0, 50.0, 5000.0, 50.0);
+ Set(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 1);
Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
}
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
{
- new TrackedSetting(ManiaRulesetSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms"))
+ new TrackedSetting(ManiaRulesetSetting.ScrollTime,
+ v => new SettingDescription(v, "Scroll Speed", $"{(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / v)} ({v}ms)"))
};
}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs
index b99a1157f3..efcfe11dad 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs
@@ -7,12 +7,12 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components
{
- public class EditBodyPiece : BodyPiece
+ public class EditBodyPiece : DefaultBodyPiece
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- AccentColour = colours.Yellow;
+ AccentColour.Value = colours.Yellow;
Background.Alpha = 0.5f;
Foreground.Alpha = 0;
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs
index 6f85fd9167..8773a39939 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs
@@ -12,12 +12,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components
{
public EditNotePiece()
{
- Height = NotePiece.NOTE_HEIGHT;
+ Height = DefaultNotePiece.NOTE_HEIGHT;
CornerRadius = 5;
Masking = true;
- InternalChild = new NotePiece();
+ InternalChild = new DefaultNotePiece();
}
[BackgroundDependencyLoader]
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
index 56c0b671a0..f1750f4a01 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
@@ -4,13 +4,13 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
+using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables;
-using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
@@ -42,11 +42,18 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.Start),
new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.End),
- new BodyPiece
+ new Container
{
- AccentColour = Color4.Transparent,
- BorderColour = colours.Yellow
- },
+ RelativeSizeAxes = Axes.Both,
+ BorderThickness = 1,
+ BorderColour = colours.Yellow,
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true,
+ }
+ }
};
}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
index a3657d3bb9..6ddf212266 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
@@ -122,11 +122,11 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
switch (scrollingInfo.Direction.Value)
{
case ScrollingDirection.Up:
- mousePosition.Y -= NotePiece.NOTE_HEIGHT / 2;
+ mousePosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2;
break;
case ScrollingDirection.Down:
- mousePosition.Y += NotePiece.NOTE_HEIGHT / 2;
+ mousePosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2;
break;
}
@@ -143,11 +143,11 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
switch (scrollingInfo.Direction.Value)
{
case ScrollingDirection.Up:
- hitObjectPosition.Y += NotePiece.NOTE_HEIGHT / 2;
+ hitObjectPosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2;
break;
case ScrollingDirection.Down:
- hitObjectPosition.Y -= NotePiece.NOTE_HEIGHT / 2;
+ hitObjectPosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2;
break;
}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 9d06bd7c25..2bd88fee90 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania
public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this);
- public override ISkin CreateLegacySkinProvider(ISkinSource source) => new ManiaLegacySkinTransformer(source);
+ public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new ManiaLegacySkinTransformer(source, beatmap);
public override IEnumerable ConvertFromLegacyMods(LegacyMods mods)
{
diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs
index 69bd4b0ecf..2371d74a2b 100644
--- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs
+++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs
@@ -1,19 +1,44 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Rulesets.Mania.UI;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania
{
public class ManiaSkinComponent : GameplaySkinComponent
{
- public ManiaSkinComponent(ManiaSkinComponents component)
+ ///
+ /// The intended index for this component.
+ /// May be null if the component does not exist in a .
+ ///
+ public readonly int? TargetColumn;
+
+ ///
+ /// Creates a new .
+ ///
+ /// The component.
+ /// The intended index for this component. May be null if the component does not exist in a .
+ public ManiaSkinComponent(ManiaSkinComponents component, int? targetColumn = null)
: base(component)
{
+ TargetColumn = targetColumn;
}
protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME;
protected override string ComponentName => Component.ToString().ToLower();
}
+
+ public enum ManiaSkinComponents
+ {
+ ColumnBackground,
+ HitTarget,
+ KeyArea,
+ Note,
+ HoldNoteHead,
+ HoldNoteTail,
+ HoldNoteBody,
+ HitExplosion
+ }
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 14a7c5fda3..a9ef661aaa 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -10,6 +10,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
@@ -20,6 +21,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
public override bool DisplayResult => false;
+ public IBindable IsHitting => isHitting;
+
+ private readonly Bindable isHitting = new Bindable();
+
public DrawableHoldNoteHead Head => headContainer.Child;
public DrawableHoldNoteTail Tail => tailContainer.Child;
@@ -27,7 +32,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private readonly Container tailContainer;
private readonly Container tickContainer;
- private readonly BodyPiece bodyPiece;
+ private readonly Drawable bodyPiece;
///
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
@@ -44,18 +49,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
RelativeSizeAxes = Axes.X;
- AddRangeInternal(new Drawable[]
+ AddRangeInternal(new[]
{
- bodyPiece = new BodyPiece { RelativeSizeAxes = Axes.X },
+ bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece())
+ {
+ RelativeSizeAxes = Axes.X
+ },
tickContainer = new Container { RelativeSizeAxes = Axes.Both },
headContainer = new Container { RelativeSizeAxes = Axes.Both },
tailContainer = new Container { RelativeSizeAxes = Axes.Both },
});
-
- AccentColour.BindValueChanged(colour =>
- {
- bodyPiece.AccentColour = colour.NewValue;
- }, true);
}
protected override void AddNestedHitObject(DrawableHitObject hitObject)
@@ -168,7 +171,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
return;
HoldStartTime = Time.Current;
- bodyPiece.Hitting = true;
+ isHitting.Value = true;
}
public void OnReleased(ManiaAction action)
@@ -194,7 +197,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private void endHold()
{
HoldStartTime = null;
- bodyPiece.Hitting = false;
+ isHitting.Value = false;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
index 390c64c5e2..a73fe259e4 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
@@ -8,6 +8,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
public class DrawableHoldNoteHead : DrawableNote
{
+ protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteHead;
+
public DrawableHoldNoteHead(DrawableHoldNote holdNote)
: base(holdNote.HitObject.Head)
{
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
index 568b07c958..31e43d3ee2 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
@@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
private const double release_window_lenience = 1.5;
+ protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
+
private readonly DrawableHoldNote holdNote;
public DrawableHoldNoteTail(DrawableHoldNote holdNote)
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
index 85613d3afb..9451bc4430 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -3,13 +3,12 @@
using System.Diagnostics;
using osu.Framework.Bindables;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Effects;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
@@ -18,7 +17,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
public class DrawableNote : DrawableManiaHitObject, IKeyBindingHandler
{
- private readonly NotePiece headPiece;
+ protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note;
+
+ private readonly Drawable headPiece;
public DrawableNote(Note hitObject)
: base(hitObject)
@@ -26,22 +27,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
- CornerRadius = 5;
- Masking = true;
-
- AddInternal(headPiece = new NotePiece());
-
- AccentColour.BindValueChanged(colour =>
+ AddInternal(headPiece = new SkinnableDrawable(new ManiaSkinComponent(Component, hitObject.Column), _ => new DefaultNotePiece())
{
- headPiece.AccentColour = colour.NewValue;
-
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = colour.NewValue.Lighten(1f).Opacity(0.2f),
- Radius = 10,
- };
- }, true);
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y
+ });
}
protected override void OnDirectionChanged(ValueChangedEvent e)
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs
similarity index 70%
rename from osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs
rename to osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs
index 43f9ae2783..0ee0a14df3 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs
@@ -2,6 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using JetBrains.Annotations;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -9,26 +12,38 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Layout;
-using osu.Game.Graphics;
+using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
{
///
/// Represents length-wise portion of a hold note.
///
- public class BodyPiece : Container, IHasAccentColour
+ public class DefaultBodyPiece : CompositeDrawable
{
- private readonly Container subtractionLayer;
+ protected readonly Bindable AccentColour = new Bindable();
- protected readonly Drawable Background;
- protected readonly BufferedContainer Foreground;
- private readonly BufferedContainer subtractionContainer;
+ private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize);
+ private readonly IBindable isHitting = new Bindable();
- public BodyPiece()
+ protected Drawable Background { get; private set; }
+ protected BufferedContainer Foreground { get; private set; }
+
+ private BufferedContainer subtractionContainer;
+ private Container subtractionLayer;
+
+ public DefaultBodyPiece()
{
+ RelativeSizeAxes = Axes.Both;
Blending = BlendingParameters.Additive;
- Children = new[]
+ AddLayout(subtractionCache);
+ }
+
+ [BackgroundDependencyLoader(true)]
+ private void load([CanBeNull] DrawableHitObject drawableObject)
+ {
+ InternalChildren = new[]
{
Background = new Box { RelativeSizeAxes = Axes.Both },
Foreground = new BufferedContainer
@@ -66,43 +81,37 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
}
};
- AddLayout(subtractionCache);
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- updateAccentColour();
- }
-
- private Color4 accentColour;
-
- public Color4 AccentColour
- {
- get => accentColour;
- set
+ if (drawableObject != null)
{
- if (accentColour == value)
- return;
+ var holdNote = (DrawableHoldNote)drawableObject;
- accentColour = value;
-
- updateAccentColour();
+ AccentColour.BindTo(drawableObject.AccentColour);
+ isHitting.BindTo(holdNote.IsHitting);
}
+
+ AccentColour.BindValueChanged(onAccentChanged, true);
+ isHitting.BindValueChanged(_ => onAccentChanged(new ValueChangedEvent(AccentColour.Value, AccentColour.Value)), true);
}
- public bool Hitting
+ private void onAccentChanged(ValueChangedEvent accent)
{
- get => hitting;
- set
- {
- hitting = value;
- updateAccentColour();
- }
- }
+ Foreground.Colour = accent.NewValue.Opacity(0.5f);
+ Background.Colour = accent.NewValue.Opacity(0.7f);
- private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize);
+ const float animation_length = 50;
+
+ Foreground.ClearTransforms(false, nameof(Foreground.Colour));
+
+ if (isHitting.Value)
+ {
+ // wait for the next sync point
+ double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
+ using (Foreground.BeginDelayedSequence(synchronisedOffset))
+ Foreground.FadeColour(accent.NewValue.Lighten(0.2f), animation_length).Then().FadeColour(Foreground.Colour, animation_length).Loop();
+ }
+
+ subtractionCache.Invalidate();
+ }
protected override void Update()
{
@@ -125,30 +134,5 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
subtractionCache.Validate();
}
}
-
- private bool hitting;
-
- private void updateAccentColour()
- {
- if (!IsLoaded)
- return;
-
- Foreground.Colour = AccentColour.Opacity(0.5f);
- Background.Colour = AccentColour.Opacity(0.7f);
-
- const float animation_length = 50;
-
- Foreground.ClearTransforms(false, nameof(Foreground.Colour));
-
- if (hitting)
- {
- // wait for the next sync point
- double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
- using (Foreground.BeginDelayedSequence(synchronisedOffset))
- Foreground.FadeColour(AccentColour.Lighten(0.2f), animation_length).Then().FadeColour(Foreground.Colour, animation_length).Loop();
- }
-
- subtractionCache.Invalidate();
- }
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultNotePiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultNotePiece.cs
new file mode 100644
index 0000000000..29f5217fd8
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultNotePiece.cs
@@ -0,0 +1,85 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using JetBrains.Annotations;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osuTK.Graphics;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI.Scrolling;
+
+namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
+{
+ ///
+ /// Represents the static hit markers of notes.
+ ///
+ internal class DefaultNotePiece : CompositeDrawable
+ {
+ public const float NOTE_HEIGHT = 12;
+
+ private readonly IBindable direction = new Bindable();
+ private readonly IBindable accentColour = new Bindable();
+
+ private readonly Box colouredBox;
+
+ public DefaultNotePiece()
+ {
+ RelativeSizeAxes = Axes.X;
+ Height = NOTE_HEIGHT;
+
+ CornerRadius = 5;
+ Masking = true;
+
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both
+ },
+ colouredBox = new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = NOTE_HEIGHT / 2,
+ Alpha = 0.1f
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader(true)]
+ private void load([NotNull] IScrollingInfo scrollingInfo, [CanBeNull] DrawableHitObject drawableObject)
+ {
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
+
+ if (drawableObject != null)
+ {
+ accentColour.BindTo(drawableObject.AccentColour);
+ accentColour.BindValueChanged(onAccentChanged, true);
+ }
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up
+ ? Anchor.TopCentre
+ : Anchor.BottomCentre;
+ }
+
+ private void onAccentChanged(ValueChangedEvent accent)
+ {
+ colouredBox.Colour = accent.NewValue.Lighten(0.9f);
+
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = accent.NewValue.Lighten(1f).Opacity(0.2f),
+ Radius = 10,
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
deleted file mode 100644
index 4521af7dfb..0000000000
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osuTK.Graphics;
-using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
-using osu.Game.Rulesets.UI.Scrolling;
-
-namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
-{
- ///
- /// Represents the static hit markers of notes.
- ///
- internal class NotePiece : Container, IHasAccentColour
- {
- public const float NOTE_HEIGHT = 12;
-
- private readonly IBindable direction = new Bindable();
-
- private readonly Box colouredBox;
-
- public NotePiece()
- {
- RelativeSizeAxes = Axes.X;
- Height = NOTE_HEIGHT;
-
- Children = new[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both
- },
- colouredBox = new Box
- {
- RelativeSizeAxes = Axes.X,
- Height = NOTE_HEIGHT / 2,
- Alpha = 0.1f
- }
- };
- }
-
- [BackgroundDependencyLoader]
- private void load(IScrollingInfo scrollingInfo)
- {
- direction.BindTo(scrollingInfo.Direction);
- direction.BindValueChanged(dir =>
- {
- colouredBox.Anchor = colouredBox.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
- }, true);
- }
-
- private Color4 accentColour;
-
- public Color4 AccentColour
- {
- get => accentColour;
- set
- {
- if (accentColour == value)
- return;
-
- accentColour = value;
-
- colouredBox.Colour = AccentColour.Lighten(0.9f);
- }
- }
- }
-}
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
index b93e372027..8c73c36e99 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Replays.Legacy;
using osu.Game.Rulesets.Mania.Beatmaps;
@@ -26,13 +27,7 @@ namespace osu.Game.Rulesets.Mania.Replays
public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{
- // We don't need to fully convert, just create the converter
- var converter = new ManiaBeatmapConverter(beatmap, new ManiaRuleset());
-
- // NB: Via co-op mod, osu-stable can have two stages with floor(col/2) and ceil(col/2) columns. This will need special handling
- // elsewhere in the game if we do choose to support the old co-op mod anyway. For now, assume that there is only one stage.
-
- var stage = new StageDefinition { Columns = converter.TargetColumns };
+ var maniaBeatmap = (ManiaBeatmap)beatmap;
var normalAction = ManiaAction.Key1;
var specialAction = ManiaAction.Special1;
@@ -42,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Replays
while (activeColumns > 0)
{
- var isSpecial = stage.IsSpecialColumn(counter);
+ var isSpecial = maniaBeatmap.Stages.First().IsSpecialColumn(counter);
if ((activeColumns & 1) > 0)
Actions.Add(isSpecial ? specialAction : normalAction);
@@ -59,17 +54,15 @@ namespace osu.Game.Rulesets.Mania.Replays
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
{
+ var maniaBeatmap = (ManiaBeatmap)beatmap;
+
int keys = 0;
- var converter = new ManiaBeatmapConverter(beatmap, new ManiaRuleset());
-
- var stage = new StageDefinition { Columns = converter.TargetColumns };
-
var specialColumns = new List();
- for (int i = 0; i < converter.TargetColumns; i++)
+ for (int i = 0; i < maniaBeatmap.TotalColumns; i++)
{
- if (stage.IsSpecialColumn(i))
+ if (maniaBeatmap.Stages.First().IsSpecialColumn(i))
specialColumns.Add(i);
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs
new file mode 100644
index 0000000000..0c9bc97ba9
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs
@@ -0,0 +1,86 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Animations;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Mania.Skinning
+{
+ public class LegacyBodyPiece : LegacyManiaColumnElement
+ {
+ private readonly IBindable direction = new Bindable();
+ private readonly IBindable isHitting = new Bindable();
+
+ private Drawable sprite;
+
+ public LegacyBodyPiece()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin, IScrollingInfo scrollingInfo, DrawableHitObject drawableObject)
+ {
+ string imageName = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value
+ ?? $"mania-note{FallbackColumnIndex}L";
+
+ sprite = skin.GetAnimation(imageName, true, true).With(d =>
+ {
+ if (d == null)
+ return;
+
+ if (d is TextureAnimation animation)
+ animation.IsPlaying = false;
+
+ d.Anchor = Anchor.TopCentre;
+ d.RelativeSizeAxes = Axes.Both;
+ d.Size = Vector2.One;
+ d.FillMode = FillMode.Stretch;
+ // Todo: Wrap
+ });
+
+ if (sprite != null)
+ InternalChild = sprite;
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
+
+ var holdNote = (DrawableHoldNote)drawableObject;
+ isHitting.BindTo(holdNote.IsHitting);
+ isHitting.BindValueChanged(onIsHittingChanged, true);
+ }
+
+ private void onIsHittingChanged(ValueChangedEvent isHitting)
+ {
+ if (!(sprite is TextureAnimation animation))
+ return;
+
+ animation.GotoFrame(0);
+ animation.IsPlaying = isHitting.NewValue;
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ if (sprite == null)
+ return;
+
+ if (direction.NewValue == ScrollingDirection.Up)
+ {
+ sprite.Origin = Anchor.BottomCentre;
+ sprite.Scale = new Vector2(1, -1);
+ }
+ else
+ {
+ sprite.Origin = Anchor.TopCentre;
+ sprite.Scale = Vector2.One;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs
new file mode 100644
index 0000000000..8cd0272b52
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs
@@ -0,0 +1,134 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Bindings;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Skinning;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Skinning
+{
+ public class LegacyColumnBackground : LegacyManiaColumnElement, IKeyBindingHandler
+ {
+ private readonly IBindable direction = new Bindable();
+ private readonly bool isLastColumn;
+
+ private Container lightContainer;
+ private Sprite light;
+
+ public LegacyColumnBackground(bool isLastColumn)
+ {
+ this.isLastColumn = isLastColumn;
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
+ {
+ string lightImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LightImage, 0)?.Value
+ ?? "mania-stage-light";
+
+ float leftLineWidth = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LeftLineWidth)
+ ?.Value ?? 1;
+ float rightLineWidth = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.RightLineWidth)
+ ?.Value ?? 1;
+
+ bool hasLeftLine = leftLineWidth > 0;
+ bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m
+ || isLastColumn;
+
+ float lightPosition = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LightPosition)?.Value
+ ?? 0;
+
+ Color4 lineColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value
+ ?? Color4.White;
+
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black
+ },
+ new Box
+ {
+ RelativeSizeAxes = Axes.Y,
+ Width = leftLineWidth,
+ Colour = lineColour,
+ Alpha = hasLeftLine ? 1 : 0
+ },
+ new Box
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ RelativeSizeAxes = Axes.Y,
+ Width = rightLineWidth,
+ Colour = lineColour,
+ Alpha = hasRightLine ? 1 : 0
+ },
+ lightContainer = new Container
+ {
+ Origin = Anchor.BottomCentre,
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Bottom = lightPosition },
+ Child = light = new Sprite
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ Texture = skin.GetTexture(lightImage),
+ RelativeSizeAxes = Axes.X,
+ Width = 1,
+ Alpha = 0
+ }
+ }
+ };
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ if (direction.NewValue == ScrollingDirection.Up)
+ {
+ lightContainer.Anchor = Anchor.TopCentre;
+ lightContainer.Scale = new Vector2(1, -1);
+ }
+ else
+ {
+ lightContainer.Anchor = Anchor.BottomCentre;
+ lightContainer.Scale = Vector2.One;
+ }
+ }
+
+ public bool OnPressed(ManiaAction action)
+ {
+ if (action == Column.Action.Value)
+ {
+ light.FadeIn();
+ light.ScaleTo(Vector2.One);
+ }
+
+ return false;
+ }
+
+ public void OnReleased(ManiaAction action)
+ {
+ // Todo: Should be 400 * 100 / CurrentBPM
+ const double animation_length = 250;
+
+ if (action == Column.Action.Value)
+ {
+ light.FadeTo(0, animation_length);
+ light.ScaleTo(new Vector2(1, 0), animation_length);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs
new file mode 100644
index 0000000000..c87a1d438b
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.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;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Animations;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Mania.Skinning
+{
+ public class LegacyHitExplosion : LegacyManiaColumnElement
+ {
+ private readonly IBindable direction = new Bindable();
+
+ private Drawable explosion;
+
+ public LegacyHitExplosion()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
+ {
+ string imageName = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ExplosionImage)?.Value
+ ?? "lightingN";
+
+ float explosionScale = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ExplosionScale)?.Value
+ ?? 1;
+
+ // Create a temporary animation to retrieve the number of frames, in an effort to calculate the intended frame length.
+ // This animation is discarded and re-queried with the appropriate frame length afterwards.
+ var tmp = skin.GetAnimation(imageName, true, false);
+ double frameLength = 0;
+ if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0)
+ frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount);
+
+ explosion = skin.GetAnimation(imageName, true, false, startAtCurrentTime: true, frameLength: frameLength).With(d =>
+ {
+ if (d == null)
+ return;
+
+ d.Origin = Anchor.Centre;
+ d.Blending = BlendingParameters.Additive;
+ d.Scale = new Vector2(explosionScale);
+ });
+
+ if (explosion != null)
+ InternalChild = explosion;
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ if (explosion != null)
+ explosion.Anchor = direction.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ explosion?.FadeInFromZero(80)
+ .Then().FadeOut(120);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs
new file mode 100644
index 0000000000..53e4f3cd14
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs
@@ -0,0 +1,78 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Mania.Skinning
+{
+ public class LegacyHitTarget : LegacyManiaElement
+ {
+ private readonly IBindable direction = new Bindable();
+
+ private Container directionContainer;
+
+ public LegacyHitTarget()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
+ {
+ string targetImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HitTargetImage)?.Value
+ ?? "mania-stage-hint";
+
+ bool showJudgementLine = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ShowJudgementLine)?.Value
+ ?? true;
+
+ InternalChild = directionContainer = new Container
+ {
+ Origin = Anchor.CentreLeft,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new Drawable[]
+ {
+ new Sprite
+ {
+ Texture = skin.GetTexture(targetImage),
+ Scale = new Vector2(1, 0.9f * 1.6025f),
+ RelativeSizeAxes = Axes.X,
+ Width = 1
+ },
+ new Box
+ {
+ Anchor = Anchor.CentreLeft,
+ RelativeSizeAxes = Axes.X,
+ Height = 1,
+ Alpha = showJudgementLine ? 0.9f : 0
+ }
+ }
+ };
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ if (direction.NewValue == ScrollingDirection.Up)
+ {
+ directionContainer.Anchor = Anchor.TopLeft;
+ directionContainer.Scale = new Vector2(1, -1);
+ }
+ else
+ {
+ directionContainer.Anchor = Anchor.BottomLeft;
+ directionContainer.Scale = Vector2.One;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteHeadPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteHeadPiece.cs
new file mode 100644
index 0000000000..c5aa062d0f
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteHeadPiece.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics.Textures;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Mania.Skinning
+{
+ public class LegacyHoldNoteHeadPiece : LegacyNotePiece
+ {
+ protected override Texture GetTexture(ISkinSource skin)
+ {
+ // TODO: Should fallback to the head from default legacy skin instead of note.
+ return GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage)
+ ?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs
new file mode 100644
index 0000000000..2e8259f10a
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs
@@ -0,0 +1,29 @@
+// 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.Bindables;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Mania.Skinning
+{
+ public class LegacyHoldNoteTailPiece : LegacyNotePiece
+ {
+ protected override void OnDirectionChanged(ValueChangedEvent direction)
+ {
+ // Invert the direction
+ base.OnDirectionChanged(direction.NewValue == ScrollingDirection.Up
+ ? new ValueChangedEvent(ScrollingDirection.Down, ScrollingDirection.Down)
+ : new ValueChangedEvent(ScrollingDirection.Up, ScrollingDirection.Up));
+ }
+
+ protected override Texture GetTexture(ISkinSource skin)
+ {
+ // TODO: Should fallback to the head from default legacy skin instead of note.
+ return GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteTailImage)
+ ?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage)
+ ?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs
new file mode 100644
index 0000000000..7c8d1cd303
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs
@@ -0,0 +1,106 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Bindings;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Mania.Skinning
+{
+ public class LegacyKeyArea : LegacyManiaColumnElement, IKeyBindingHandler
+ {
+ private readonly IBindable direction = new Bindable();
+
+ private Container directionContainer;
+ private Sprite upSprite;
+ private Sprite downSprite;
+
+ [Resolved]
+ private Column column { get; set; }
+
+ public LegacyKeyArea()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
+ {
+ string upImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.KeyImage)?.Value
+ ?? $"mania-key{FallbackColumnIndex}";
+
+ string downImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.KeyImageDown)?.Value
+ ?? $"mania-key{FallbackColumnIndex}D";
+
+ InternalChild = directionContainer = new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new Drawable[]
+ {
+ upSprite = new Sprite
+ {
+ Origin = Anchor.BottomCentre,
+ Texture = skin.GetTexture(upImage),
+ RelativeSizeAxes = Axes.X,
+ Width = 1
+ },
+ downSprite = new Sprite
+ {
+ Origin = Anchor.BottomCentre,
+ Texture = skin.GetTexture(downImage),
+ RelativeSizeAxes = Axes.X,
+ Width = 1,
+ Alpha = 0
+ }
+ }
+ };
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ if (direction.NewValue == ScrollingDirection.Up)
+ {
+ directionContainer.Anchor = directionContainer.Origin = Anchor.TopCentre;
+ upSprite.Anchor = downSprite.Anchor = Anchor.TopCentre;
+ upSprite.Scale = downSprite.Scale = new Vector2(1, -1);
+ }
+ else
+ {
+ directionContainer.Anchor = directionContainer.Origin = Anchor.BottomCentre;
+ upSprite.Anchor = downSprite.Anchor = Anchor.BottomCentre;
+ upSprite.Scale = downSprite.Scale = Vector2.One;
+ }
+ }
+
+ public bool OnPressed(ManiaAction action)
+ {
+ if (action == column.Action.Value)
+ {
+ upSprite.FadeTo(0);
+ downSprite.FadeTo(1);
+ }
+
+ return false;
+ }
+
+ public void OnReleased(ManiaAction action)
+ {
+ if (action == column.Action.Value)
+ {
+ upSprite.FadeTo(1);
+ downSprite.FadeTo(0);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs
new file mode 100644
index 0000000000..05b731ec5d
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs
@@ -0,0 +1,48 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Mania.Skinning
+{
+ ///
+ /// A which is placed somewhere within a .
+ ///
+ public class LegacyManiaColumnElement : LegacyManiaElement
+ {
+ [Resolved]
+ protected Column Column { get; private set; }
+
+ ///
+ /// The column type identifier to use for texture lookups, in the case of no user-provided configuration.
+ ///
+ protected string FallbackColumnIndex { get; private set; }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ switch (Column.ColumnType)
+ {
+ case ColumnType.Special:
+ FallbackColumnIndex = "S";
+ break;
+
+ case ColumnType.Odd:
+ FallbackColumnIndex = "1";
+ break;
+
+ case ColumnType.Even:
+ FallbackColumnIndex = "2";
+ break;
+ }
+ }
+
+ protected override IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null)
+ => base.GetManiaSkinConfig(skin, lookup, index ?? Column.Index);
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs
new file mode 100644
index 0000000000..11fdd663a1
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs
@@ -0,0 +1,25 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Mania.Skinning
+{
+ ///
+ /// A mania legacy skin element.
+ ///
+ public class LegacyManiaElement : CompositeDrawable
+ {
+ ///
+ /// Retrieve a per-column-count skin configuration.
+ ///
+ /// The skin from which configuration is retrieved.
+ /// The value to retrieve.
+ /// If not null, denotes the index of the column to which the entry applies.
+ protected virtual IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null)
+ => skin.GetConfig(
+ new ManiaSkinConfigurationLookup(lookup, index));
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs
new file mode 100644
index 0000000000..d2ceb06d0b
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs
@@ -0,0 +1,92 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Mania.Skinning
+{
+ public class LegacyNotePiece : LegacyManiaColumnElement
+ {
+ private readonly IBindable direction = new Bindable();
+
+ private Container directionContainer;
+ private Sprite noteSprite;
+
+ public LegacyNotePiece()
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
+ {
+ InternalChild = directionContainer = new Container
+ {
+ Origin = Anchor.BottomCentre,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Child = noteSprite = new Sprite { Texture = GetTexture(skin) }
+ };
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(OnDirectionChanged, true);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (noteSprite.Texture != null)
+ {
+ var scale = DrawWidth / noteSprite.Texture.DisplayWidth;
+ noteSprite.Scale = new Vector2(scale);
+ }
+ }
+
+ protected virtual void OnDirectionChanged(ValueChangedEvent direction)
+ {
+ if (direction.NewValue == ScrollingDirection.Up)
+ {
+ directionContainer.Anchor = Anchor.TopCentre;
+ directionContainer.Scale = new Vector2(1, -1);
+ }
+ else
+ {
+ directionContainer.Anchor = Anchor.BottomCentre;
+ directionContainer.Scale = Vector2.One;
+ }
+ }
+
+ protected virtual Texture GetTexture(ISkinSource skin) => GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
+
+ protected Texture GetTextureFromLookup(ISkin skin, LegacyManiaSkinConfigurationLookups lookup)
+ {
+ string suffix = string.Empty;
+
+ switch (lookup)
+ {
+ case LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage:
+ suffix = "H";
+ break;
+
+ case LegacyManiaSkinConfigurationLookups.HoldNoteTailImage:
+ suffix = "T";
+ break;
+ }
+
+ string noteImage = GetManiaSkinConfig(skin, lookup)?.Value
+ ?? $"mania-note{FallbackColumnIndex}{suffix}";
+
+ return skin.GetTexture(noteImage);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
index f3739ce7c2..cbe2036343 100644
--- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
@@ -1,12 +1,15 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Audio;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Skinning
@@ -14,10 +17,32 @@ namespace osu.Game.Rulesets.Mania.Skinning
public class ManiaLegacySkinTransformer : ISkin
{
private readonly ISkin source;
+ private readonly ManiaBeatmap beatmap;
- public ManiaLegacySkinTransformer(ISkin source)
+ private Lazy isLegacySkin;
+
+ ///
+ /// Whether texture for the keys exists.
+ /// Used to determine if the mania ruleset is skinned.
+ ///
+ private Lazy hasKeyTexture;
+
+ public ManiaLegacySkinTransformer(ISkinSource source, IBeatmap beatmap)
{
this.source = source;
+ this.beatmap = (ManiaBeatmap)beatmap;
+
+ source.SourceChanged += sourceChanged;
+ sourceChanged();
+ }
+
+ private void sourceChanged()
+ {
+ isLegacySkin = new Lazy(() => source.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null);
+ hasKeyTexture = new Lazy(() => source.GetAnimation(
+ source.GetConfig(
+ new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value
+ ?? "mania-key1", true, true) != null);
}
public Drawable GetDrawableComponent(ISkinComponent component)
@@ -26,6 +51,39 @@ namespace osu.Game.Rulesets.Mania.Skinning
{
case GameplaySkinComponent resultComponent:
return getResult(resultComponent);
+
+ case ManiaSkinComponent maniaComponent:
+ if (!isLegacySkin.Value || !hasKeyTexture.Value)
+ return null;
+
+ switch (maniaComponent.Component)
+ {
+ case ManiaSkinComponents.ColumnBackground:
+ return new LegacyColumnBackground(maniaComponent.TargetColumn == beatmap.TotalColumns - 1);
+
+ case ManiaSkinComponents.HitTarget:
+ return new LegacyHitTarget();
+
+ case ManiaSkinComponents.KeyArea:
+ return new LegacyKeyArea();
+
+ case ManiaSkinComponents.Note:
+ return new LegacyNotePiece();
+
+ case ManiaSkinComponents.HoldNoteHead:
+ return new LegacyHoldNoteHeadPiece();
+
+ case ManiaSkinComponents.HoldNoteTail:
+ return new LegacyHoldNoteTailPiece();
+
+ case ManiaSkinComponents.HoldNoteBody:
+ return new LegacyBodyPiece();
+
+ case ManiaSkinComponents.HitExplosion:
+ return new LegacyHitExplosion();
+ }
+
+ break;
}
return null;
@@ -61,7 +119,12 @@ namespace osu.Game.Rulesets.Mania.Skinning
public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
- public IBindable GetConfig(TLookup lookup) =>
- source.GetConfig(lookup);
+ public IBindable GetConfig(TLookup lookup)
+ {
+ if (lookup is ManiaSkinConfigurationLookup maniaLookup)
+ return source.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn));
+
+ return source.GetConfig(lookup);
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs
new file mode 100644
index 0000000000..f07a5518b7
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs
@@ -0,0 +1,33 @@
+// 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 osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Mania.Skinning
+{
+ public class ManiaSkinConfigurationLookup
+ {
+ ///
+ /// The configuration lookup value.
+ ///
+ public readonly LegacyManiaSkinConfigurationLookups Lookup;
+
+ ///
+ /// The intended index for the configuration.
+ /// May be null if the configuration does not apply to a .
+ ///
+ public readonly int? TargetColumn;
+
+ ///
+ /// Creates a new .
+ ///
+ /// The lookup value.
+ /// The intended index for the configuration. May be null if the configuration does not apply to a .
+ public ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups lookup, int? targetColumn = null)
+ {
+ Lookup = lookup;
+ TargetColumn = targetColumn;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index 63c573d344..d2f58d7255 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -12,17 +12,19 @@ using osu.Framework.Bindables;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Objects.Drawables;
-using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Skinning;
using osuTK;
+using osu.Game.Rulesets.Mania.Beatmaps;
namespace osu.Game.Rulesets.Mania.UI
{
+ [Cached]
public class Column : ScrollingPlayfield, IKeyBindingHandler, IHasAccentColour
{
public const float COLUMN_WIDTH = 80;
- private const float special_column_width = 70;
+ public const float SPECIAL_COLUMN_WIDTH = 70;
///
/// The index of this column as part of the whole playfield.
@@ -31,109 +33,44 @@ namespace osu.Game.Rulesets.Mania.UI
public readonly Bindable Action = new Bindable();
- private readonly ColumnBackground background;
- private readonly ColumnKeyArea keyArea;
private readonly ColumnHitObjectArea hitObjectArea;
internal readonly Container TopLevelContainer;
- private readonly Container explosionContainer;
public Column(int index)
{
Index = index;
RelativeSizeAxes = Axes.Y;
- Width = COLUMN_WIDTH;
- background = new ColumnBackground { RelativeSizeAxes = Axes.Both };
-
- Container hitTargetContainer;
+ Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, Index), _ => new DefaultColumnBackground())
+ {
+ RelativeSizeAxes = Axes.Both
+ };
InternalChildren = new[]
{
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
background.CreateProxy(),
- hitTargetContainer = new Container
+ hitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
+ new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, Index), _ => new DefaultKeyArea())
{
- Name = "Hit target + hit objects",
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- hitObjectArea = new ColumnHitObjectArea(HitObjectContainer)
- {
- RelativeSizeAxes = Axes.Both,
- },
- explosionContainer = new Container
- {
- Name = "Hit explosions",
- RelativeSizeAxes = Axes.Both,
- }
- }
- },
- keyArea = new ColumnKeyArea
- {
- RelativeSizeAxes = Axes.X,
- Height = ManiaStage.HIT_TARGET_POSITION,
+ RelativeSizeAxes = Axes.Both
},
background,
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
};
- TopLevelContainer.Add(explosionContainer.CreateProxy());
-
- Direction.BindValueChanged(dir =>
- {
- hitTargetContainer.Padding = new MarginPadding
- {
- Top = dir.NewValue == ScrollingDirection.Up ? ManiaStage.HIT_TARGET_POSITION : 0,
- Bottom = dir.NewValue == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0,
- };
-
- explosionContainer.Padding = new MarginPadding
- {
- Top = dir.NewValue == ScrollingDirection.Up ? NotePiece.NOTE_HEIGHT / 2 : 0,
- Bottom = dir.NewValue == ScrollingDirection.Down ? NotePiece.NOTE_HEIGHT / 2 : 0
- };
-
- keyArea.Anchor = keyArea.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
- }, true);
+ TopLevelContainer.Add(hitObjectArea.Explosions.CreateProxy());
}
public override Axes RelativeSizeAxes => Axes.Y;
- private bool isSpecial;
+ public ColumnType ColumnType { get; set; }
- public bool IsSpecial
- {
- get => isSpecial;
- set
- {
- if (isSpecial == value)
- return;
+ public bool IsSpecial => ColumnType == ColumnType.Special;
- isSpecial = value;
-
- Width = isSpecial ? special_column_width : COLUMN_WIDTH;
- }
- }
-
- private Color4 accentColour;
-
- public Color4 AccentColour
- {
- get => accentColour;
- set
- {
- if (accentColour == value)
- return;
-
- accentColour = value;
-
- background.AccentColour = value;
- keyArea.AccentColour = value;
- hitObjectArea.AccentColour = value;
- }
- }
+ public Color4 AccentColour { get; set; }
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
@@ -168,11 +105,15 @@ namespace osu.Game.Rulesets.Mania.UI
if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value)
return;
- explosionContainer.Add(new HitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick)
+ var explosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, Index), _ =>
+ new DefaultHitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick))
{
- Anchor = Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre,
- Origin = Anchor.Centre
- });
+ RelativeSizeAxes = Axes.Both
+ };
+
+ hitObjectArea.Explosions.Add(explosion);
+
+ explosion.Delay(200).Expire(true);
}
public bool OnPressed(ManiaAction action)
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
index 90e78c3899..cb79bf7f43 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
@@ -1,145 +1,45 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Effects;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
-using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
-using osuTK.Graphics;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.UI.Components
{
- public class ColumnHitObjectArea : CompositeDrawable, IHasAccentColour
+ public class ColumnHitObjectArea : HitObjectArea
{
- private readonly IBindable direction = new Bindable();
-
+ public readonly Container Explosions;
private readonly Drawable hitTarget;
- public ColumnHitObjectArea(HitObjectContainer hitObjectContainer)
+ public ColumnHitObjectArea(int columnIndex, HitObjectContainer hitObjectContainer)
+ : base(hitObjectContainer)
{
- InternalChildren = new[]
+ AddRangeInternal(new[]
{
- hitTarget = new DefaultHitTarget
+ hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget, columnIndex), _ => new DefaultHitTarget())
{
RelativeSizeAxes = Axes.X,
+ Depth = 1
},
- hitObjectContainer
- };
- }
-
- [BackgroundDependencyLoader]
- private void load(IScrollingInfo scrollingInfo)
- {
- direction.BindTo(scrollingInfo.Direction);
- direction.BindValueChanged(dir =>
- {
- Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
-
- hitTarget.Anchor = hitTarget.Origin = anchor;
- }, true);
- }
-
- private Color4 accentColour;
-
- public Color4 AccentColour
- {
- get => accentColour;
- set
- {
- if (accentColour == value)
- return;
-
- accentColour = value;
-
- if (hitTarget is IHasAccentColour colouredHitTarget)
- colouredHitTarget.AccentColour = accentColour;
- }
- }
-
- private class DefaultHitTarget : CompositeDrawable, IHasAccentColour
- {
- private const float hit_target_bar_height = 2;
-
- private readonly IBindable direction = new Bindable();
-
- private readonly Container hitTargetLine;
- private readonly Drawable hitTargetBar;
-
- public DefaultHitTarget()
- {
- InternalChildren = new[]
+ Explosions = new Container
{
- hitTargetBar = new Box
- {
- RelativeSizeAxes = Axes.X,
- Height = NotePiece.NOTE_HEIGHT,
- Alpha = 0.6f,
- Colour = Color4.Black
- },
- hitTargetLine = new Container
- {
- RelativeSizeAxes = Axes.X,
- Height = hit_target_bar_height,
- Masking = true,
- Child = new Box { RelativeSizeAxes = Axes.Both }
- },
- };
- }
-
- [BackgroundDependencyLoader]
- private void load(IScrollingInfo scrollingInfo)
- {
- direction.BindTo(scrollingInfo.Direction);
- direction.BindValueChanged(dir =>
- {
- Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
-
- hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
- hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
- }, true);
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
- updateColours();
- }
-
- private Color4 accentColour;
-
- public Color4 AccentColour
- {
- get => accentColour;
- set
- {
- if (accentColour == value)
- return;
-
- accentColour = value;
-
- updateColours();
+ RelativeSizeAxes = Axes.Both,
+ Depth = -1,
}
- }
+ });
+ }
- private void updateColours()
- {
- if (!IsLoaded)
- return;
+ protected override void UpdateHitPosition()
+ {
+ base.UpdateHitPosition();
- hitTargetLine.EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Radius = 5,
- Colour = accentColour.Opacity(0.5f),
- };
- }
+ if (Direction.Value == ScrollingDirection.Up)
+ hitTarget.Anchor = hitTarget.Origin = Anchor.TopLeft;
+ else
+ hitTarget.Anchor = hitTarget.Origin = Anchor.BottomLeft;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs
deleted file mode 100644
index 60fc2713b3..0000000000
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Colour;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Effects;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Input.Bindings;
-using osu.Game.Graphics;
-using osu.Game.Rulesets.UI.Scrolling;
-using osuTK;
-using osuTK.Graphics;
-
-namespace osu.Game.Rulesets.Mania.UI.Components
-{
- public class ColumnKeyArea : CompositeDrawable, IKeyBindingHandler, IHasAccentColour
- {
- private const float key_icon_size = 10;
- private const float key_icon_corner_radius = 3;
-
- private readonly IBindable action = new Bindable();
- private readonly IBindable direction = new Bindable();
-
- private Container keyIcon;
-
- [BackgroundDependencyLoader]
- private void load(IBindable action, IScrollingInfo scrollingInfo)
- {
- this.action.BindTo(action);
-
- Drawable gradient;
-
- InternalChildren = new[]
- {
- gradient = new Box
- {
- Name = "Key gradient",
- RelativeSizeAxes = Axes.Both,
- Alpha = 0.5f
- },
- keyIcon = new Container
- {
- Name = "Key icon",
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(key_icon_size),
- Masking = true,
- CornerRadius = key_icon_corner_radius,
- BorderThickness = 2,
- BorderColour = Color4.White, // Not true
- Children = new[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = 0,
- AlwaysPresent = true
- }
- }
- }
- };
-
- direction.BindTo(scrollingInfo.Direction);
- direction.BindValueChanged(dir =>
- {
- gradient.Colour = ColourInfo.GradientVertical(
- dir.NewValue == ScrollingDirection.Up ? Color4.Black : Color4.Black.Opacity(0),
- dir.NewValue == ScrollingDirection.Up ? Color4.Black.Opacity(0) : Color4.Black);
- }, true);
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
- updateColours();
- }
-
- private Color4 accentColour;
-
- public Color4 AccentColour
- {
- get => accentColour;
- set
- {
- if (accentColour == value)
- return;
-
- accentColour = value;
-
- updateColours();
- }
- }
-
- private void updateColours()
- {
- if (!IsLoaded)
- return;
-
- keyIcon.EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Radius = 5,
- Colour = accentColour.Opacity(0.5f),
- };
- }
-
- public bool OnPressed(ManiaAction action)
- {
- if (action == this.action.Value)
- keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint);
- return false;
- }
-
- public void OnReleased(ManiaAction action)
- {
- if (action == this.action.Value)
- keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs
new file mode 100644
index 0000000000..4b4bc157d5
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs
@@ -0,0 +1,90 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Bindings;
+using osu.Game.Rulesets.UI.Scrolling;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.UI.Components
+{
+ public class DefaultColumnBackground : CompositeDrawable, IKeyBindingHandler
+ {
+ private readonly IBindable direction = new Bindable();
+
+ private Color4 brightColour;
+ private Color4 dimColour;
+
+ private Box background;
+ private Box backgroundOverlay;
+
+ [Resolved]
+ private Column column { get; set; }
+
+ public DefaultColumnBackground()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ InternalChildren = new[]
+ {
+ background = new Box
+ {
+ Name = "Background",
+ RelativeSizeAxes = Axes.Both,
+ },
+ backgroundOverlay = new Box
+ {
+ Name = "Background Gradient Overlay",
+ RelativeSizeAxes = Axes.Both,
+ Height = 0.5f,
+ Blending = BlendingParameters.Additive,
+ Alpha = 0
+ }
+ };
+
+ background.Colour = column.AccentColour.Darken(5);
+ brightColour = column.AccentColour.Opacity(0.6f);
+ dimColour = column.AccentColour.Opacity(0);
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ if (direction.NewValue == ScrollingDirection.Up)
+ {
+ backgroundOverlay.Anchor = backgroundOverlay.Origin = Anchor.TopLeft;
+ backgroundOverlay.Colour = ColourInfo.GradientVertical(brightColour, dimColour);
+ }
+ else
+ {
+ backgroundOverlay.Anchor = backgroundOverlay.Origin = Anchor.BottomLeft;
+ backgroundOverlay.Colour = ColourInfo.GradientVertical(dimColour, brightColour);
+ }
+ }
+
+ public bool OnPressed(ManiaAction action)
+ {
+ if (action == column.Action.Value)
+ backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
+ return false;
+ }
+
+ public void OnReleased(ManiaAction action)
+ {
+ if (action == column.Action.Value)
+ backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs
new file mode 100644
index 0000000000..e0b099ab9b
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs
@@ -0,0 +1,80 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.UI.Scrolling;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.UI.Components
+{
+ public class DefaultHitTarget : CompositeDrawable
+ {
+ private const float hit_target_bar_height = 2;
+
+ private readonly IBindable direction = new Bindable();
+
+ private Container hitTargetLine;
+ private Drawable hitTargetBar;
+
+ [Resolved]
+ private Column column { get; set; }
+
+ public DefaultHitTarget()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ InternalChildren = new[]
+ {
+ hitTargetBar = new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = DefaultNotePiece.NOTE_HEIGHT,
+ Alpha = 0.6f,
+ Colour = Color4.Black
+ },
+ hitTargetLine = new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = hit_target_bar_height,
+ Masking = true,
+ Child = new Box { RelativeSizeAxes = Axes.Both }
+ },
+ };
+
+ hitTargetLine.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Radius = 5,
+ Colour = column.AccentColour.Opacity(0.5f),
+ };
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ if (direction.NewValue == ScrollingDirection.Up)
+ {
+ hitTargetBar.Anchor = hitTargetBar.Origin = Anchor.TopLeft;
+ hitTargetLine.Anchor = hitTargetLine.Origin = Anchor.TopLeft;
+ }
+ else
+ {
+ hitTargetBar.Anchor = hitTargetBar.Origin = Anchor.BottomLeft;
+ hitTargetLine.Anchor = hitTargetLine.Origin = Anchor.BottomLeft;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs
new file mode 100644
index 0000000000..982a18cb60
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs
@@ -0,0 +1,114 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Bindings;
+using osu.Game.Rulesets.UI.Scrolling;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.UI.Components
+{
+ public class DefaultKeyArea : CompositeDrawable, IKeyBindingHandler
+ {
+ private const float key_icon_size = 10;
+ private const float key_icon_corner_radius = 3;
+
+ private readonly IBindable direction = new Bindable();
+
+ private Container directionContainer;
+ private Container keyIcon;
+ private Drawable gradient;
+
+ [Resolved]
+ private Column column { get; set; }
+
+ public DefaultKeyArea()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ InternalChild = directionContainer = new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = ManiaStage.HIT_TARGET_POSITION,
+ Children = new[]
+ {
+ gradient = new Box
+ {
+ Name = "Key gradient",
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.5f
+ },
+ keyIcon = new Container
+ {
+ Name = "Key icon",
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(key_icon_size),
+ Masking = true,
+ CornerRadius = key_icon_corner_radius,
+ BorderThickness = 2,
+ BorderColour = Color4.White, // Not true
+ Children = new[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true
+ }
+ }
+ }
+ }
+ };
+
+ keyIcon.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Radius = 5,
+ Colour = column.AccentColour.Opacity(0.5f),
+ };
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ if (direction.NewValue == ScrollingDirection.Up)
+ {
+ directionContainer.Anchor = directionContainer.Origin = Anchor.TopLeft;
+ gradient.Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0));
+ }
+ else
+ {
+ directionContainer.Anchor = directionContainer.Origin = Anchor.BottomLeft;
+ gradient.Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Black);
+ }
+ }
+
+ public bool OnPressed(ManiaAction action)
+ {
+ if (action == column.Action.Value)
+ keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint);
+ return false;
+ }
+
+ public void OnReleased(ManiaAction action)
+ {
+ if (action == column.Action.Value)
+ keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs
new file mode 100644
index 0000000000..bca7c3ff08
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs
@@ -0,0 +1,55 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Mania.Skinning;
+using osu.Game.Rulesets.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Mania.UI.Components
+{
+ public class HitObjectArea : SkinReloadableDrawable
+ {
+ protected readonly IBindable Direction = new Bindable();
+
+ public HitObjectArea(HitObjectContainer hitObjectContainer)
+ {
+ InternalChildren = new[]
+ {
+ hitObjectContainer,
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ Direction.BindTo(scrollingInfo.Direction);
+ Direction.BindValueChanged(onDirectionChanged, true);
+ }
+
+ protected override void SkinChanged(ISkinSource skin, bool allowFallback)
+ {
+ base.SkinChanged(skin, allowFallback);
+ UpdateHitPosition();
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ UpdateHitPosition();
+ }
+
+ protected virtual void UpdateHitPosition()
+ {
+ float hitPosition = CurrentSkin.GetConfig(
+ new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value
+ ?? ManiaStage.HIT_TARGET_POSITION;
+
+ Padding = Direction.Value == ScrollingDirection.Up
+ ? new MarginPadding { Top = hitPosition }
+ : new MarginPadding { Bottom = hitPosition };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs
similarity index 80%
rename from osu.Game.Rulesets.Mania/UI/HitExplosion.cs
rename to osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs
index 35de47e208..7a047ed121 100644
--- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs
@@ -1,28 +1,35 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Utils;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI
{
- internal class HitExplosion : CompositeDrawable
+ public class DefaultHitExplosion : CompositeDrawable
{
public override bool RemoveWhenNotAlive => true;
+ private readonly IBindable direction = new Bindable();
+
private readonly CircularContainer largeFaint;
private readonly CircularContainer mainGlow1;
- public HitExplosion(Color4 objectColour, bool isSmall = false)
+ public DefaultHitExplosion(Color4 objectColour, bool isSmall = false)
{
+ Origin = Anchor.Centre;
+
RelativeSizeAxes = Axes.X;
- Height = NotePiece.NOTE_HEIGHT;
+ Height = DefaultNotePiece.NOTE_HEIGHT;
// scale roughly in-line with visual appearance of notes
Scale = new Vector2(1f, 0.6f);
@@ -109,6 +116,13 @@ namespace osu.Game.Rulesets.Mania.UI
};
}
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
+ }
+
protected override void LoadComplete()
{
const double duration = 200;
@@ -122,7 +136,20 @@ namespace osu.Game.Rulesets.Mania.UI
mainGlow1.ScaleTo(1.4f, duration, Easing.OutQuint);
this.FadeOut(duration, Easing.Out);
- Expire(true);
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ if (direction.NewValue == ScrollingDirection.Up)
+ {
+ Anchor = Anchor.TopCentre;
+ Y = DefaultNotePiece.NOTE_HEIGHT / 2;
+ }
+ else
+ {
+ Anchor = Anchor.BottomCentre;
+ Y = -DefaultNotePiece.NOTE_HEIGHT / 2;
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
index e5ec054fa7..c8c537964f 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
@@ -5,8 +5,10 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Input.Handlers;
using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Beatmaps;
@@ -25,6 +27,16 @@ namespace osu.Game.Rulesets.Mania.UI
{
public class DrawableManiaRuleset : DrawableScrollingRuleset
{
+ ///
+ /// The minimum time range. This occurs at a of 40.
+ ///
+ public const double MIN_TIME_RANGE = 150;
+
+ ///
+ /// The maximum time range. This occurs at a of 1.
+ ///
+ public const double MAX_TIME_RANGE = 6000;
+
protected new ManiaPlayfield Playfield => (ManiaPlayfield)base.Playfield;
public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap;
@@ -46,6 +58,18 @@ namespace osu.Game.Rulesets.Mania.UI
[BackgroundDependencyLoader]
private void load()
{
+ bool isForCurrentRuleset = Beatmap.BeatmapInfo.Ruleset.Equals(Ruleset.RulesetInfo);
+
+ foreach (var p in ControlPoints)
+ {
+ // Mania doesn't care about global velocity
+ p.Velocity = 1;
+
+ // For non-mania beatmap, speed changes should only happen through timing points
+ if (!isForCurrentRuleset)
+ p.DifficultyPoint = new DifficultyControlPoint();
+ }
+
BarLines.ForEach(Playfield.Add);
Config.BindWith(ManiaRulesetSetting.ScrollDirection, configDirection);
@@ -54,6 +78,17 @@ namespace osu.Game.Rulesets.Mania.UI
Config.BindWith(ManiaRulesetSetting.ScrollTime, TimeRange);
}
+ protected override void AdjustScrollSpeed(int amount)
+ {
+ this.TransformTo(nameof(relativeTimeRange), relativeTimeRange + amount, 200, Easing.OutQuint);
+ }
+
+ private double relativeTimeRange
+ {
+ get => MAX_TIME_RANGE / TimeRange.Value;
+ set => TimeRange.Value = MAX_TIME_RANGE / value;
+ }
+
///
/// Retrieves the column that intersects a screen-space position.
///
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs
index d893a3fdde..30e0aafb7d 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs
@@ -3,7 +3,6 @@
using osu.Framework.Graphics;
using osu.Game.Rulesets.UI;
-using osuTK;
namespace osu.Game.Rulesets.Mania.UI
{
@@ -13,8 +12,6 @@ namespace osu.Game.Rulesets.Mania.UI
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
-
- Size = new Vector2(1, 0.8f);
}
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
index bfe9f1085b..adab08eb06 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.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 osu.Framework.Allocation;
@@ -12,9 +11,12 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Mania.Skinning;
+using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -32,15 +34,18 @@ namespace osu.Game.Rulesets.Mania.UI
public IReadOnlyList Columns => columnFlow.Children;
private readonly FillFlowContainer columnFlow;
- private readonly Container barLineContainer;
-
public Container Judgements => judgements;
private readonly JudgementContainer judgements;
+ private readonly Drawable barLineContainer;
private readonly Container topLevelContainer;
- private List normalColumnColours = new List();
- private Color4 specialColumnColour;
+ private readonly Dictionary columnColours = new Dictionary
+ {
+ { ColumnType.Even, new Color4(6, 84, 0, 255) },
+ { ColumnType.Odd, new Color4(94, 0, 57, 255) },
+ { ColumnType.Special, new Color4(0, 48, 63, 255) }
+ };
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Columns.Any(c => c.ReceivePositionalInputAt(screenSpacePos));
@@ -67,31 +72,19 @@ namespace osu.Game.Rulesets.Mania.UI
AutoSizeAxes = Axes.X,
Children = new Drawable[]
{
- new Container
+ new Box
{
- Name = "Columns mask",
+ Name = "Background",
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black
+ },
+ columnFlow = new FillFlowContainer
+ {
+ Name = "Columns",
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
- Masking = true,
- CornerRadius = 5,
- Children = new Drawable[]
- {
- new Box
- {
- Name = "Background",
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black
- },
- columnFlow = new FillFlowContainer
- {
- Name = "Columns",
- RelativeSizeAxes = Axes.Y,
- AutoSizeAxes = Axes.X,
- Direction = FillDirection.Horizontal,
- Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING },
- Spacing = new Vector2(COLUMN_SPACING, 0)
- },
- }
+ Direction = FillDirection.Horizontal,
+ Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING },
},
new Container
{
@@ -102,13 +95,12 @@ namespace osu.Game.Rulesets.Mania.UI
Width = 1366, // Bar lines should only be masked on the vertical axis
BypassAutoSizeAxes = Axes.Both,
Masking = true,
- Child = barLineContainer = new Container
+ Child = barLineContainer = new HitObjectArea(HitObjectContainer)
{
Name = "Bar lines",
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y,
- Child = HitObjectContainer
}
},
judgements = new JudgementContainer
@@ -125,24 +117,52 @@ namespace osu.Game.Rulesets.Mania.UI
for (int i = 0; i < definition.Columns; i++)
{
- var isSpecial = definition.IsSpecialColumn(i);
+ var columnType = definition.GetTypeOfColumn(i);
var column = new Column(firstColumnIndex + i)
{
- IsSpecial = isSpecial,
- Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ }
+ ColumnType = columnType,
+ AccentColour = columnColours[columnType],
+ Action = { Value = columnType == ColumnType.Special ? specialColumnStartAction++ : normalColumnStartAction++ }
};
AddColumn(column);
}
+ }
- Direction.BindValueChanged(dir =>
+ private ISkin currentSkin;
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ currentSkin = skin;
+ skin.SourceChanged += onSkinChanged;
+
+ onSkinChanged();
+ }
+
+ private void onSkinChanged()
+ {
+ foreach (var col in columnFlow)
{
- barLineContainer.Padding = new MarginPadding
+ if (col.Index > 0)
{
- Top = dir.NewValue == ScrollingDirection.Up ? HIT_TARGET_POSITION : 0,
- Bottom = dir.NewValue == ScrollingDirection.Down ? HIT_TARGET_POSITION : 0,
- };
- }, true);
+ float spacing = currentSkin.GetConfig(
+ new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnSpacing, col.Index - 1))
+ ?.Value ?? COLUMN_SPACING;
+
+ col.Margin = new MarginPadding { Left = spacing };
+ }
+
+ float? width = currentSkin.GetConfig(
+ new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, col.Index))
+ ?.Value;
+
+ if (width == null)
+ // only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration)
+ col.Width = col.IsSpecial ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH;
+ else
+ col.Width = width.Value;
+ }
}
public void AddColumn(Column c)
@@ -195,38 +215,6 @@ namespace osu.Game.Rulesets.Mania.UI
});
}
- [BackgroundDependencyLoader]
- private void load()
- {
- normalColumnColours = new List
- {
- new Color4(94, 0, 57, 255),
- new Color4(6, 84, 0, 255)
- };
-
- specialColumnColour = new Color4(0, 48, 63, 255);
-
- // Set the special column + colour + key
- foreach (var column in Columns)
- {
- if (!column.IsSpecial)
- continue;
-
- column.AccentColour = specialColumnColour;
- }
-
- var nonSpecialColumns = Columns.Where(c => !c.IsSpecial).ToList();
-
- // We'll set the colours of the non-special columns in a separate loop, because the non-special
- // column colours are mirrored across their centre and special styles mess with this
- for (int i = 0; i < Math.Ceiling(nonSpecialColumns.Count / 2f); i++)
- {
- Color4 colour = normalColumnColours[i % normalColumnColours.Count];
- nonSpecialColumns[i].AccentColour = colour;
- nonSpecialColumns[nonSpecialColumns.Count - 1 - i].AccentColour = colour;
- }
- }
-
protected override void Update()
{
// Due to masking differences, it is not possible to get the width of the columns container automatically
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderstartcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderstartcircle@2x.png
new file mode 100644
index 0000000000..4d630443cd
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderstartcircle@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderstartcircleoverlay@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderstartcircleoverlay@2x.png
new file mode 100644
index 0000000000..a824784942
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderstartcircleoverlay@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/sliderb0.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/sliderb0.png
new file mode 100644
index 0000000000..316d52c685
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/sliderb0.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/sliderb0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/sliderb0@2x.png
new file mode 100644
index 0000000000..e6f6b3c239
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/sliderb0@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs
new file mode 100644
index 0000000000..e406f9ddff
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs
@@ -0,0 +1,70 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+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
+{
+ [TestFixture]
+ public class TestSceneSpinnerSpunOut : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(SpinnerDisc),
+ typeof(DrawableSpinner),
+ typeof(DrawableOsuHitObject),
+ typeof(OsuModSpunOut)
+ };
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ SelectedMods.Value = new[] { new OsuModSpunOut() };
+ });
+
+ [Test]
+ public void TestSpunOut()
+ {
+ DrawableSpinner spinner = null;
+
+ AddStep("create spinner", () => spinner = createSpinner());
+
+ AddUntilStep("wait for end", () => Time.Current > spinner.LifetimeEnd);
+
+ AddAssert("spinner is completed", () => spinner.Progress >= 1);
+ }
+
+ private DrawableSpinner createSpinner()
+ {
+ var spinner = new Spinner
+ {
+ StartTime = Time.Current + 500,
+ EndTime = Time.Current + 2500
+ };
+ spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ var drawableSpinner = new DrawableSpinner(spinner)
+ {
+ Anchor = Anchor.Centre
+ };
+
+ foreach (var mod in SelectedMods.Value.OfType())
+ mod.ApplyToDrawableHitObjects(new[] { drawableSpinner });
+
+ Add(drawableSpinner);
+ return drawableSpinner;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
index 75de6896a3..8228161008 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.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.Linq;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
@@ -30,6 +31,22 @@ namespace osu.Game.Rulesets.Osu.Mods
Value = 5,
};
+ public override string SettingDescription
+ {
+ get
+ {
+ string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}";
+ string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}";
+
+ return string.Join(", ", new[]
+ {
+ circleSize,
+ base.SettingDescription,
+ approachRate
+ }.Where(s => !string.IsNullOrEmpty(s)));
+ }
+ }
+
protected override void TransferSettings(BeatmapDifficulty difficulty)
{
base.TransferSettings(difficulty);
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index 6286c80d7c..9b0759d9d2 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
@@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Mods
void handleHitCircle(DrawableHitCircle circle)
{
- if (!circle.IsHovered)
+ if (!circle.HitArea.IsHovered)
return;
Debug.Assert(circle.HitObject.HitWindows != null);
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
index 9d5d300a9e..7b54baa99b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
@@ -2,21 +2,46 @@
// 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.Sprites;
+using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModSpunOut : Mod
+ public class OsuModSpunOut : Mod, IApplicableToDrawableHitObjects
{
public override string Name => "Spun Out";
public override string Acronym => "SO";
public override IconUsage? Icon => OsuIcon.ModSpunout;
- public override ModType Type => ModType.DifficultyReduction;
+ public override ModType Type => ModType.Automation;
public override string Description => @"Spinners will be automatically completed.";
public override double ScoreMultiplier => 0.9;
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot) };
+
+ public void ApplyToDrawableHitObjects(IEnumerable drawables)
+ {
+ foreach (var hitObject in drawables)
+ {
+ if (hitObject is DrawableSpinner spinner)
+ {
+ spinner.HandleUserInput = false;
+ spinner.OnUpdate += onSpinnerUpdate;
+ }
+ }
+ }
+
+ private void onSpinnerUpdate(Drawable drawable)
+ {
+ var spinner = (DrawableSpinner)drawable;
+
+ spinner.Disc.Tracking = true;
+ spinner.Disc.Rotate(MathUtils.RadiansToDegrees((float)spinner.Clock.ElapsedFrameTime * 0.03f));
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
index 7e530ca047..8bb324d02e 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
///
/// A single follow point positioned between two adjacent s.
///
- public class FollowPoint : Container
+ public class FollowPoint : Container, IAnimationTimeReference
{
private const float width = 8;
@@ -45,5 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
}
}, confineMode: ConfineMode.NoScaling);
}
+
+ public double AnimationStartTime { get; set; }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
index d0935e46f7..6f09bbcd57 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
@@ -116,6 +116,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
int point = 0;
+ ClearInternal();
+
for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing)
{
float fraction = (float)d / distance;
@@ -126,13 +128,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
FollowPoint fp;
- if (InternalChildren.Count > point)
- {
- fp = (FollowPoint)InternalChildren[point];
- fp.ClearTransforms();
- }
- else
- AddInternal(fp = new FollowPoint());
+ AddInternal(fp = new FollowPoint());
fp.Position = pointStartPosition;
fp.Rotation = rotation;
@@ -142,6 +138,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
if (firstTransformStartTime == null)
firstTransformStartTime = fadeInTime;
+ fp.AnimationStartTime = fadeInTime;
+
using (fp.BeginAbsoluteSequence(fadeInTime))
{
fp.FadeIn(osuEnd.TimeFadeIn);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index da1e666aba..5202327245 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public readonly SkinnableDrawable CirclePiece;
private readonly Container scaleContainer;
+ protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle;
+
public DrawableHitCircle(HitCircle h)
: base(h)
{
@@ -57,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return true;
},
},
- CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece()),
+ CirclePiece = new SkinnableDrawable(new OsuSkinComponent(CirclePieceComponent), _ => new MainCirclePiece()),
ApproachCircle = new ApproachCircle
{
Alpha = 0,
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 2d5b9d874c..5c7f4a42b3 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -11,6 +11,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Rulesets.Scoring;
using osuTK.Graphics;
using osu.Game.Skinning;
@@ -196,6 +197,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ApplyResult(r => r.Type = r.Judgement.MaxResult);
}
+ public override void PlaySamples()
+ {
+ // rather than doing it this way, we should probably attach the sample to the tail circle.
+ // this can only be done after we stop using LegacyLastTick.
+ if (TailCircle.Result.Type != HitResult.Miss)
+ base.PlaySamples();
+ }
+
protected override void UpdateStateTransforms(ArmedState state)
{
base.UpdateStateTransforms(state);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
index c5609b01e0..a360071f26 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
@@ -14,6 +14,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly IBindable positionBindable = new Bindable();
private readonly IBindable pathVersion = new Bindable();
+ protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle;
+
private readonly Slider slider;
public DrawableSliderHead(Slider slider, HitCircle h)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
index b9cee71ca1..b04d484195 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
@@ -87,6 +87,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public void UpdateSnakingPosition(Vector2 start, Vector2 end)
{
+ // When the repeat is hit, the arrow should fade out on spot rather than following the slider
+ if (IsHit) return;
+
bool isRepeatAtEnd = sliderRepeat.RepeatIndex % 2 == 0;
List curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index 0ec7f2ebfe..3c8ab0f5ab 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -176,17 +176,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void Update()
{
- Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false;
- if (!SpmCounter.IsPresent && Disc.Tracking)
- SpmCounter.FadeIn(HitObject.TimeFadeIn);
-
base.Update();
+ if (HandleUserInput)
+ Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false;
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
+ if (!SpmCounter.IsPresent && Disc.Tracking)
+ SpmCounter.FadeIn(HitObject.TimeFadeIn);
+
circle.Rotation = Disc.Rotation;
Ticks.Rotation = Disc.Rotation;
SpmCounter.SetRotation(Disc.RotationAbsolute);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
index 3de30d51d9..d4ef039b79 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
@@ -73,6 +73,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}
}
+ ///
+ /// Whether currently in the correct time range to allow spinning.
+ ///
+ private bool isSpinnableTime => spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current;
+
protected override bool OnMouseMove(MouseMoveEvent e)
{
mousePosition = Parent.ToLocalSpace(e.ScreenSpaceMousePosition);
@@ -93,27 +98,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
protected override void Update()
{
base.Update();
-
var thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2));
- bool validAndTracking = tracking && spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current;
+ var delta = thisAngle - lastAngle;
- if (validAndTracking)
- {
- if (!rotationTransferred)
- {
- currentRotation = Rotation * 2;
- rotationTransferred = true;
- }
-
- if (thisAngle - lastAngle > 180)
- lastAngle += 360;
- else if (lastAngle - thisAngle > 180)
- lastAngle -= 360;
-
- currentRotation += thisAngle - lastAngle;
- RotationAbsolute += Math.Abs(thisAngle - lastAngle) * Math.Sign(Clock.ElapsedFrameTime);
- }
+ if (tracking)
+ Rotate(delta);
lastAngle = thisAngle;
@@ -128,5 +118,38 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Rotation = (float)Interpolation.Lerp(Rotation, currentRotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1));
}
+
+ ///
+ /// Rotate the disc by the provided angle (in addition to any existing rotation).
+ ///
+ ///
+ /// Will be a no-op if not a valid time to spin.
+ ///
+ /// The delta angle.
+ public void Rotate(float angle)
+ {
+ if (!isSpinnableTime)
+ return;
+
+ if (!rotationTransferred)
+ {
+ currentRotation = Rotation * 2;
+ rotationTransferred = true;
+ }
+
+ if (angle > 180)
+ {
+ lastAngle += 360;
+ angle -= 360;
+ }
+ else if (-angle > 180)
+ {
+ lastAngle -= 360;
+ angle += 360;
+ }
+
+ currentRotation += angle;
+ RotationAbsolute += Math.Abs(angle) * Math.Sign(Clock.ElapsedFrameTime);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs
index a8fd3764c5..ac6c6905e4 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs
@@ -34,8 +34,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public class SliderRepeatJudgement : OsuJudgement
{
- public override bool IsBonus => true;
-
protected override int NumericResultFor(HitResult result) => result == MaxResult ? 30 : 0;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
index 212a84c04a..22f3f559db 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
@@ -36,8 +36,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public class SliderTickJudgement : OsuJudgement
{
- public override bool IsBonus => true;
-
protected override int NumericResultFor(HitResult result) => result == MaxResult ? 10 : 0;
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index a2c0e051d0..689a7b35ea 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -113,7 +113,6 @@ namespace osu.Game.Rulesets.Osu
new OsuModEasy(),
new OsuModNoFail(),
new MultiMod(new OsuModHalfTime(), new OsuModDaycore()),
- new OsuModSpunOut(),
};
case ModType.DifficultyIncrease:
@@ -139,6 +138,7 @@ namespace osu.Game.Rulesets.Osu
new MultiMod(new OsuModAutoplay(), new OsuModCinema()),
new OsuModRelax(),
new OsuModAutopilot(),
+ new OsuModSpunOut(),
};
case ModType.Fun:
@@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Osu
public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this);
- public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkinTransformer(source);
+ public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new OsuLegacySkinTransformer(source);
public int LegacyID => 0;
diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
index 4ea4220faf..b2cdc8ccbf 100644
--- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
@@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Osu
ApproachCircle,
ReverseArrow,
HitCircleText,
+ SliderHeadHitCircle,
SliderFollowCircle,
SliderBall,
SliderBody,
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs
index 93ae0371df..e7486ef9b0 100644
--- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs
@@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Objects.Drawables;
@@ -18,8 +19,12 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
public class LegacyMainCirclePiece : CompositeDrawable
{
- public LegacyMainCirclePiece()
+ private readonly string priorityLookup;
+
+ public LegacyMainCirclePiece(string priorityLookup = null)
{
+ this.priorityLookup = priorityLookup;
+
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
}
@@ -39,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
hitCircleSprite = new Sprite
{
- Texture = skin.GetTexture("hitcircle"),
+ Texture = getTextureWithFallback(string.Empty),
Colour = drawableObject.AccentColour.Value,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -51,12 +56,17 @@ namespace osu.Game.Rulesets.Osu.Skinning
}, confineMode: ConfineMode.NoScaling),
new Sprite
{
- Texture = skin.GetTexture("hitcircleoverlay"),
+ Texture = getTextureWithFallback("overlay"),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
};
+ bool overlayAboveNumber = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true;
+
+ if (!overlayAboveNumber)
+ ChangeInternalChildDepth(hitCircleText, -float.MaxValue);
+
state.BindTo(drawableObject.State);
state.BindValueChanged(updateState, true);
@@ -65,6 +75,16 @@ namespace osu.Game.Rulesets.Osu.Skinning
indexInCurrentCombo.BindTo(osuObject.IndexInCurrentComboBindable);
indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true);
+
+ Texture getTextureWithFallback(string name)
+ {
+ Texture tex = null;
+
+ if (!string.IsNullOrEmpty(priorityLookup))
+ tex = skin.GetTexture($"{priorityLookup}{name}");
+
+ return tex ?? skin.GetTexture($"hitcircle{name}");
+ }
}
private void updateState(ValueChangedEvent state)
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
index 81c02199d0..b4ed75d97c 100644
--- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
@@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
public LegacySliderBall(Drawable animationContent)
{
this.animationContent = animationContent;
+
+ AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
index d6c3f443eb..0d67846b8e 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
@@ -62,17 +62,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
// Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME);
if (sliderBallContent != null)
- {
- var size = sliderBallContent.Size;
-
- sliderBallContent.RelativeSizeAxes = Axes.Both;
- sliderBallContent.Size = Vector2.One;
-
- return new LegacySliderBall(sliderBallContent)
- {
- Size = size
- };
- }
+ return new LegacySliderBall(sliderBallContent);
return null;
@@ -82,6 +72,12 @@ namespace osu.Game.Rulesets.Osu.Skinning
return null;
+ case OsuSkinComponents.SliderHeadHitCircle:
+ if (hasHitCircle.Value)
+ return new LegacyMainCirclePiece("sliderstartcircle");
+
+ return null;
+
case OsuSkinComponents.HitCircle:
if (hasHitCircle.Value)
return new LegacyMainCirclePiece();
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
index 5d99960f10..c6920bd03e 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
@@ -11,6 +11,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
SliderPathRadius,
AllowSliderBallTint,
CursorExpand,
- CursorRotate
+ CursorRotate,
+ HitCircleOverlayAboveNumber
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-bar-left@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-bar-left@2x.png
new file mode 100644
index 0000000000..dc3d7f4c70
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-bar-left@2x.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-drum-inner@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-drum-inner@2x.png
new file mode 100644
index 0000000000..15a89ade1b
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-drum-inner@2x.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-drum-outer@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-drum-outer@2x.png
new file mode 100644
index 0000000000..a01583c6fb
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-drum-outer@2x.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs
index 8c1b0c4c62..c79088056f 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs
@@ -4,31 +4,28 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osuTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Rulesets.Taiko.Audio;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
[TestFixture]
- public class TestSceneInputDrum : OsuTestScene
+ public class TestSceneInputDrum : SkinnableTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
typeof(InputDrum),
- typeof(DrumSampleMapping),
- typeof(HitSampleInfo),
- typeof(SampleControlPoint)
};
- public TestSceneInputDrum()
+ [BackgroundDependencyLoader]
+ private void load()
{
- Add(new TaikoInputManager(new RulesetInfo { ID = 1 })
+ SetContents(() => new TaikoInputManager(new RulesetInfo { ID = 1 })
{
RelativeSizeAxes = Axes.Both,
Child = new Container
diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs
new file mode 100644
index 0000000000..8fe7c5e566
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs
@@ -0,0 +1,144 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Bindings;
+using osu.Game.Rulesets.Taiko.Audio;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Taiko.Skinning
+{
+ ///
+ /// A component of the playfield that captures input and displays input as a drum.
+ ///
+ internal class LegacyInputDrum : Container
+ {
+ public LegacyInputDrum()
+ {
+ AutoSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ Children = new Drawable[]
+ {
+ new Sprite
+ {
+ Texture = skin.GetTexture("taiko-bar-left")
+ },
+ new LegacyHalfDrum(false)
+ {
+ Name = "Left Half",
+ RelativeSizeAxes = Axes.Both,
+ Width = 0.5f,
+ RimAction = TaikoAction.LeftRim,
+ CentreAction = TaikoAction.LeftCentre
+ },
+ new LegacyHalfDrum(true)
+ {
+ Name = "Right Half",
+ Anchor = Anchor.TopRight,
+ RelativeSizeAxes = Axes.Both,
+ Width = 0.5f,
+ Scale = new Vector2(-1, 1),
+ RimAction = TaikoAction.RightRim,
+ CentreAction = TaikoAction.RightCentre
+ }
+ };
+ }
+
+ ///
+ /// A half-drum. Contains one centre and one rim hit.
+ ///
+ private class LegacyHalfDrum : Container, IKeyBindingHandler
+ {
+ ///
+ /// The key to be used for the rim of the half-drum.
+ ///
+ public TaikoAction RimAction;
+
+ ///
+ /// The key to be used for the centre of the half-drum.
+ ///
+ public TaikoAction CentreAction;
+
+ private readonly Sprite rimHit;
+ private readonly Sprite centreHit;
+
+ [Resolved]
+ private DrumSampleMapping sampleMappings { get; set; }
+
+ public LegacyHalfDrum(bool flipped)
+ {
+ Masking = true;
+
+ Children = new Drawable[]
+ {
+ rimHit = new Sprite
+ {
+ Anchor = flipped ? Anchor.CentreRight : Anchor.CentreLeft,
+ Origin = flipped ? Anchor.CentreLeft : Anchor.CentreRight,
+ Scale = new Vector2(-1, 1),
+ Alpha = 0,
+ },
+ centreHit = new Sprite
+ {
+ Anchor = flipped ? Anchor.CentreRight : Anchor.CentreLeft,
+ Origin = flipped ? Anchor.CentreRight : Anchor.CentreLeft,
+ Alpha = 0,
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ rimHit.Texture = skin.GetTexture(@"taiko-drum-outer");
+ centreHit.Texture = skin.GetTexture(@"taiko-drum-inner");
+ }
+
+ public bool OnPressed(TaikoAction action)
+ {
+ Drawable target = null;
+ var drumSample = sampleMappings.SampleAt(Time.Current);
+
+ if (action == CentreAction)
+ {
+ target = centreHit;
+ drumSample.Centre?.Play();
+ }
+ else if (action == RimAction)
+ {
+ target = rimHit;
+ drumSample.Rim?.Play();
+ }
+
+ if (target != null)
+ {
+ const float alpha_amount = 1;
+
+ const float down_time = 80;
+ const float up_time = 50;
+
+ target.Animate(
+ t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time)
+ ).Then(
+ t => t.FadeOut(up_time)
+ );
+ }
+
+ return false;
+ }
+
+ public void OnReleased(TaikoAction action)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
index 381cd14cd4..78eec94590 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
@@ -20,7 +20,22 @@ namespace osu.Game.Rulesets.Taiko.Skinning
this.source = source;
}
- public Drawable GetDrawableComponent(ISkinComponent component) => source.GetDrawableComponent(component);
+ public Drawable GetDrawableComponent(ISkinComponent component)
+ {
+ if (!(component is TaikoSkinComponent taikoComponent))
+ return null;
+
+ switch (taikoComponent.Component)
+ {
+ case TaikoSkinComponents.InputDrum:
+ if (GetTexture("taiko-bar-left") != null)
+ return new LegacyInputDrum();
+
+ return null;
+ }
+
+ return source.GetDrawableComponent(component);
+ }
public Texture GetTexture(string componentName) => source.GetTexture(componentName);
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index a6c9a33569..74d9e68ad3 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap, this);
- public override ISkin CreateLegacySkinProvider(ISkinSource source) => new TaikoLegacySkinTransformer(source);
+ public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new TaikoLegacySkinTransformer(source);
public const string SHORT_NAME = "taiko";
diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
index 04aca534c6..6d4581db80 100644
--- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
@@ -5,5 +5,6 @@ namespace osu.Game.Rulesets.Taiko
{
public enum TaikoSkinComponents
{
+ InputDrum,
}
}
diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
index d26ccfe867..422ea2f929 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.Skinning;
namespace osu.Game.Rulesets.Taiko.UI
{
@@ -22,11 +23,12 @@ namespace osu.Game.Rulesets.Taiko.UI
{
private const float middle_split = 0.025f;
- private readonly ControlPointInfo controlPoints;
+ [Cached]
+ private DrumSampleMapping sampleMapping;
public InputDrum(ControlPointInfo controlPoints)
{
- this.controlPoints = controlPoints;
+ sampleMapping = new DrumSampleMapping(controlPoints);
RelativeSizeAxes = Axes.Both;
FillMode = FillMode.Fit;
@@ -35,35 +37,37 @@ namespace osu.Game.Rulesets.Taiko.UI
[BackgroundDependencyLoader]
private void load()
{
- var sampleMappings = new DrumSampleMapping(controlPoints);
-
- Children = new Drawable[]
+ Child = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container
{
- new TaikoHalfDrum(false, sampleMappings)
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
{
- Name = "Left Half",
- Anchor = Anchor.Centre,
- Origin = Anchor.CentreRight,
- RelativeSizeAxes = Axes.Both,
- RelativePositionAxes = Axes.X,
- X = -middle_split / 2,
- RimAction = TaikoAction.LeftRim,
- CentreAction = TaikoAction.LeftCentre
- },
- new TaikoHalfDrum(true, sampleMappings)
- {
- Name = "Right Half",
- Anchor = Anchor.Centre,
- Origin = Anchor.CentreLeft,
- RelativeSizeAxes = Axes.Both,
- RelativePositionAxes = Axes.X,
- X = middle_split / 2,
- RimAction = TaikoAction.RightRim,
- CentreAction = TaikoAction.RightCentre
+ new TaikoHalfDrum(false)
+ {
+ Name = "Left Half",
+ Anchor = Anchor.Centre,
+ Origin = Anchor.CentreRight,
+ RelativeSizeAxes = Axes.Both,
+ RelativePositionAxes = Axes.X,
+ X = -middle_split / 2,
+ RimAction = TaikoAction.LeftRim,
+ CentreAction = TaikoAction.LeftCentre
+ },
+ new TaikoHalfDrum(true)
+ {
+ Name = "Right Half",
+ Anchor = Anchor.Centre,
+ Origin = Anchor.CentreLeft,
+ RelativeSizeAxes = Axes.Both,
+ RelativePositionAxes = Axes.X,
+ X = middle_split / 2,
+ RimAction = TaikoAction.RightRim,
+ CentreAction = TaikoAction.RightCentre
+ }
}
- };
+ });
- AddRangeInternal(sampleMappings.Sounds);
+ AddRangeInternal(sampleMapping.Sounds);
}
///
@@ -86,12 +90,11 @@ namespace osu.Game.Rulesets.Taiko.UI
private readonly Sprite centre;
private readonly Sprite centreHit;
- private readonly DrumSampleMapping sampleMappings;
+ [Resolved]
+ private DrumSampleMapping sampleMappings { get; set; }
- public TaikoHalfDrum(bool flipped, DrumSampleMapping sampleMappings)
+ public TaikoHalfDrum(bool flipped)
{
- this.sampleMappings = sampleMappings;
-
Masking = true;
Children = new Drawable[]
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
index 63346b8c9d..b034e66616 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
@@ -127,6 +127,31 @@ namespace osu.Game.Tests.Beatmaps.Formats
.Assert();
}
+ [Test]
+ public void TestGetJsonDecoder()
+ {
+ Decoder decoder;
+
+ using (var stream = TestResources.OpenResource(normal))
+ using (var sr = new LineBufferedReader(stream))
+ {
+ var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr);
+
+ using (var memStream = new MemoryStream())
+ using (var memWriter = new StreamWriter(memStream))
+ using (var memReader = new LineBufferedReader(memStream))
+ {
+ memWriter.Write(legacyDecoded.Serialize());
+ memWriter.Flush();
+
+ memStream.Position = 0;
+ decoder = Decoder.GetDecoder(memReader);
+ }
+ }
+
+ Assert.IsInstanceOf(typeof(JsonBeatmapDecoder), decoder);
+ }
+
///
/// Reads a .osu file first with a , serializes the resulting to JSON
/// and then deserializes the result back into a through an .
diff --git a/osu.Game.Tests/Resources/mania-skin-colours.ini b/osu.Game.Tests/Resources/mania-skin-colours.ini
new file mode 100644
index 0000000000..91d9696e0c
--- /dev/null
+++ b/osu.Game.Tests/Resources/mania-skin-colours.ini
@@ -0,0 +1,3 @@
+[Mania]
+Keys: 4
+ColourBarline: 50,50,50,50
\ No newline at end of file
diff --git a/osu.Game.Tests/Resources/mania-skin-duplicate.ini b/osu.Game.Tests/Resources/mania-skin-duplicate.ini
new file mode 100644
index 0000000000..2f4fa92c52
--- /dev/null
+++ b/osu.Game.Tests/Resources/mania-skin-duplicate.ini
@@ -0,0 +1,9 @@
+[Mania]
+Keys: 4
+ColumnWidth: 10,10,10,10
+HitPosition: 470
+
+[Mania]
+Keys: 4
+ColumnWidth: 20,20,20,20
+HitPosition: 460
\ No newline at end of file
diff --git a/osu.Game.Tests/Resources/mania-skin-extra-data.ini b/osu.Game.Tests/Resources/mania-skin-extra-data.ini
new file mode 100644
index 0000000000..e538b5335a
--- /dev/null
+++ b/osu.Game.Tests/Resources/mania-skin-extra-data.ini
@@ -0,0 +1,4 @@
+[Mania]
+Keys: 4
+ColumnWidth: 10,10,10,10,10,10,10
+HitPosition: 470
\ No newline at end of file
diff --git a/osu.Game.Tests/Resources/mania-skin-multiple.ini b/osu.Game.Tests/Resources/mania-skin-multiple.ini
new file mode 100644
index 0000000000..247c7738a0
--- /dev/null
+++ b/osu.Game.Tests/Resources/mania-skin-multiple.ini
@@ -0,0 +1,9 @@
+[Mania]
+Keys: 4
+ColumnWidth: 10,10,10,10
+HitPosition: 470
+
+[Mania]
+Keys: 2
+ColumnWidth: 20,20
+HitPosition: 460
\ No newline at end of file
diff --git a/osu.Game.Tests/Resources/mania-skin-single.ini b/osu.Game.Tests/Resources/mania-skin-single.ini
new file mode 100644
index 0000000000..3ae38fd75e
--- /dev/null
+++ b/osu.Game.Tests/Resources/mania-skin-single.ini
@@ -0,0 +1,4 @@
+[Mania]
+Keys: 4
+ColumnWidth: 10,10,10,10
+HitPosition: 470
\ No newline at end of file
diff --git a/osu.Game.Tests/Resources/storyboard_no_video.osu b/osu.Game.Tests/Resources/storyboard_no_video.osu
new file mode 100644
index 0000000000..25f1ff6361
--- /dev/null
+++ b/osu.Game.Tests/Resources/storyboard_no_video.osu
@@ -0,0 +1,31 @@
+osu file format v14
+
+[Events]
+//Background and Video events
+0,0,"BG.jpg",0,0
+Video,0,"video.avi"
+//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]
+1674,333.333333333333,4,2,1,70,1,0
+1674,-100,4,2,1,70,0,0
+3340,-100,4,2,1,70,0,0
+3507,-100,4,2,1,70,0,0
+3673,-100,4,2,1,70,0,0
+
+[Colours]
+Combo1 : 240,80,80
+Combo2 : 171,252,203
+Combo3 : 128,128,255
+Combo4 : 249,254,186
+
+[HitObjects]
+148,303,1674,5,6,3:2:0:0:
+378,252,1840,1,0,0:0:0:0:
+389,270,2340,5,2,0:1:0:0:
diff --git a/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs
new file mode 100644
index 0000000000..83fd4878aa
--- /dev/null
+++ b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs
@@ -0,0 +1,103 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.IO;
+using osu.Game.Skinning;
+using osu.Game.Tests.Resources;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Skins
+{
+ [TestFixture]
+ public class LegacyManiaSkinDecoderTest
+ {
+ [Test]
+ public void TestParseSingleConfig()
+ {
+ var decoder = new LegacyManiaSkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("mania-skin-single.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var configs = decoder.Decode(stream);
+
+ Assert.That(configs.Count, Is.EqualTo(1));
+ Assert.That(configs[0].Keys, Is.EqualTo(4));
+ Assert.That(configs[0].ColumnWidth, Is.EquivalentTo(new float[] { 16, 16, 16, 16 }));
+ Assert.That(configs[0].HitPosition, Is.EqualTo(16));
+ }
+ }
+
+ [Test]
+ public void TestParseMultipleConfig()
+ {
+ var decoder = new LegacyManiaSkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("mania-skin-multiple.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var configs = decoder.Decode(stream);
+
+ Assert.That(configs.Count, Is.EqualTo(2));
+
+ Assert.That(configs[0].Keys, Is.EqualTo(4));
+ Assert.That(configs[0].ColumnWidth, Is.EquivalentTo(new float[] { 16, 16, 16, 16 }));
+ Assert.That(configs[0].HitPosition, Is.EqualTo(16));
+
+ Assert.That(configs[1].Keys, Is.EqualTo(2));
+ Assert.That(configs[1].ColumnWidth, Is.EquivalentTo(new float[] { 32, 32 }));
+ Assert.That(configs[1].HitPosition, Is.EqualTo(32));
+ }
+ }
+
+ [Test]
+ public void TestParseDuplicateConfig()
+ {
+ var decoder = new LegacyManiaSkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("mania-skin-single.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var configs = decoder.Decode(stream);
+
+ Assert.That(configs.Count, Is.EqualTo(1));
+ Assert.That(configs[0].Keys, Is.EqualTo(4));
+ Assert.That(configs[0].ColumnWidth, Is.EquivalentTo(new float[] { 16, 16, 16, 16 }));
+ Assert.That(configs[0].HitPosition, Is.EqualTo(16));
+ }
+ }
+
+ [Test]
+ public void TestParseWithUnnecessaryExtraData()
+ {
+ var decoder = new LegacyManiaSkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("mania-skin-extra-data.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var configs = decoder.Decode(stream);
+
+ Assert.That(configs.Count, Is.EqualTo(1));
+ Assert.That(configs[0].Keys, Is.EqualTo(4));
+ Assert.That(configs[0].ColumnWidth, Is.EquivalentTo(new float[] { 16, 16, 16, 16 }));
+ Assert.That(configs[0].HitPosition, Is.EqualTo(16));
+ }
+ }
+
+ [Test]
+ public void TestParseColours()
+ {
+ var decoder = new LegacyManiaSkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("mania-skin-colours.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var configs = decoder.Decode(stream);
+
+ Assert.That(configs.Count, Is.EqualTo(1));
+ Assert.That(configs[0].CustomColours, Contains.Key("ColourBarline").And.ContainValue(new Color4(50, 50, 50, 50)));
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs
index 83a7b896d2..b7dcad3825 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs
@@ -4,7 +4,6 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
@@ -74,9 +73,6 @@ namespace osu.Game.Tests.Visual.Gameplay
Beatmap.Value = working;
SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) };
- Player?.Exit();
- Player = null;
-
Player = CreatePlayer(ruleset);
LoadScreen(Player);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
index afeda5fb7c..4b1c2ec256 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
@@ -3,8 +3,14 @@
using System.ComponentModel;
using System.Linq;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.Break;
+using osu.Game.Screens.Ranking;
namespace osu.Game.Tests.Visual.Gameplay
{
@@ -15,20 +21,38 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Player CreatePlayer(Ruleset ruleset)
{
- SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
- return new TestPlayer(false, false);
+ SelectedMods.Value = new[] { ruleset.GetAutoplayMod() };
+ return new TestPlayer(false);
}
protected override void AddCheckSteps()
{
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2));
- AddStep("seek to break time", () => Player.GameplayClockContainer.Seek(Player.BreakOverlay.Breaks.First().StartTime));
- AddUntilStep("wait for seek to complete", () =>
- Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= Player.BreakOverlay.Breaks.First().StartTime);
- AddAssert("test keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting);
+ seekToBreak(0);
+ AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting);
+ AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType().Single().AccuracyDisplay.Current.Value == 1);
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
+
+ seekToBreak(0);
+ seekToBreak(1);
+
+ AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
+ AddUntilStep("results displayed", () => getResultsScreen() != null);
+
+ AddAssert("score has combo", () => getResultsScreen().Score.Combo > 100);
+ AddAssert("score has no misses", () => getResultsScreen().Score.Statistics[HitResult.Miss] == 0);
+
+ ResultsScreen getResultsScreen() => Stack.CurrentScreen as ResultsScreen;
+ }
+
+ private void seekToBreak(int breakIndex)
+ {
+ AddStep($"seek to break {breakIndex}", () => Player.GameplayClockContainer.Seek(destBreak().StartTime));
+ AddUntilStep("wait for seek to complete", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= destBreak().StartTime);
+
+ BreakPeriod destBreak() => Player.ChildrenOfType().First().Breaks.ElementAt(breakIndex);
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs
similarity index 80%
rename from osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs
rename to osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs
index 19dce303ea..ff25e609c1 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Graphics;
using osu.Framework.Timing;
using osu.Game.Beatmaps.Timing;
using osu.Game.Screens.Play;
@@ -12,14 +13,16 @@ using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
- public class TestSceneBreakOverlay : OsuTestScene
+ public class TestSceneBreakTracker : OsuTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
typeof(BreakOverlay),
};
- private readonly TestBreakOverlay breakOverlay;
+ private readonly BreakOverlay breakOverlay;
+
+ private readonly TestBreakTracker breakTracker;
private readonly IReadOnlyList testBreaks = new List
{
@@ -35,9 +38,23 @@ namespace osu.Game.Tests.Visual.Gameplay
},
};
- public TestSceneBreakOverlay()
+ public TestSceneBreakTracker()
{
- Add(breakOverlay = new TestBreakOverlay(true));
+ AddRange(new Drawable[]
+ {
+ breakTracker = new TestBreakTracker(),
+ breakOverlay = new BreakOverlay(true, null)
+ {
+ ProcessCustomClock = false,
+ }
+ });
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ breakOverlay.Clock = breakTracker.Clock;
}
[Test]
@@ -88,7 +105,7 @@ namespace osu.Game.Tests.Visual.Gameplay
loadBreaksStep("multiple breaks", testBreaks);
seekAndAssertBreak("seek to break start", testBreaks[1].StartTime, true);
- AddAssert("is skipped to break #2", () => breakOverlay.CurrentBreakIndex == 1);
+ AddAssert("is skipped to break #2", () => breakTracker.CurrentBreakIndex == 1);
seekAndAssertBreak("seek to break middle", testBreaks[1].StartTime + testBreaks[1].Duration / 2, true);
seekAndAssertBreak("seek to break end", testBreaks[1].EndTime, false);
@@ -110,7 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void addShowBreakStep(double seconds)
{
- AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List
+ AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = breakTracker.Breaks = new List
{
new BreakPeriod
{
@@ -122,12 +139,12 @@ namespace osu.Game.Tests.Visual.Gameplay
private void setClock(bool useManual)
{
- AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakOverlay.SwitchClock(useManual));
+ AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakTracker.SwitchClock(useManual));
}
private void loadBreaksStep(string breakDescription, IReadOnlyList breaks)
{
- AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breaks);
+ AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breakTracker.Breaks = breaks);
seekAndAssertBreak("seek back to 0", 0, false);
}
@@ -151,17 +168,18 @@ namespace osu.Game.Tests.Visual.Gameplay
private void seekAndAssertBreak(string seekStepDescription, double time, bool shouldBeBreak)
{
- AddStep(seekStepDescription, () => breakOverlay.ManualClockTime = time);
+ AddStep(seekStepDescription, () => breakTracker.ManualClockTime = time);
AddAssert($"is{(!shouldBeBreak ? " not" : string.Empty)} break time", () =>
{
- breakOverlay.ProgressTime();
- return breakOverlay.IsBreakTime.Value == shouldBeBreak;
+ breakTracker.ProgressTime();
+ return breakTracker.IsBreakTime.Value == shouldBeBreak;
});
}
- private class TestBreakOverlay : BreakOverlay
+ private class TestBreakTracker : BreakTracker
{
- private readonly FramedClock framedManualClock;
+ public readonly FramedClock FramedManualClock;
+
private readonly ManualClock manualClock;
private IFrameBasedClock originalClock;
@@ -173,20 +191,19 @@ namespace osu.Game.Tests.Visual.Gameplay
set => manualClock.CurrentTime = value;
}
- public TestBreakOverlay(bool letterboxing)
- : base(letterboxing)
+ public TestBreakTracker()
{
- framedManualClock = new FramedClock(manualClock = new ManualClock());
+ FramedManualClock = new FramedClock(manualClock = new ManualClock());
ProcessCustomClock = false;
}
public void ProgressTime()
{
- framedManualClock.ProcessFrame();
+ FramedManualClock.ProcessFrame();
Update();
}
- public void SwitchClock(bool setManual) => Clock = setManual ? framedManualClock : originalClock;
+ public void SwitchClock(bool setManual) => Clock = setManual ? FramedManualClock : originalClock;
protected override void LoadComplete()
{
diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
similarity index 99%
rename from osu.Game.Tests/Gameplay/TestSceneReplayRecorder.cs
rename to osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
index 734991b868..c7455583e4 100644
--- a/osu.Game.Tests/Gameplay/TestSceneReplayRecorder.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
@@ -17,13 +17,12 @@ using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
-using osu.Game.Tests.Visual;
using osu.Game.Tests.Visual.UserInterface;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
-namespace osu.Game.Tests.Gameplay
+namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneReplayRecorder : OsuManualInputManagerTestScene
{
diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
similarity index 99%
rename from osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs
rename to osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
index 057d026132..7822f07957 100644
--- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
@@ -13,12 +13,11 @@ using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
-using osu.Game.Tests.Visual;
using osu.Game.Tests.Visual.UserInterface;
using osuTK;
using osuTK.Graphics;
-namespace osu.Game.Tests.Gameplay
+namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneReplayRecording : OsuTestScene
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
index ec94053679..3b91243fee 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
@@ -43,16 +43,15 @@ namespace osu.Game.Tests.Visual.Gameplay
{
new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true),
- new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling)
}
},
};
});
- AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 }));
+ AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 50 }));
AddStep("adjust scale", () => fill.Scale = new Vector2(2));
- AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 }));
+ AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 50 }));
}
[Test]
@@ -74,7 +73,6 @@ namespace osu.Game.Tests.Visual.Gameplay
Children = new[]
{
new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true),
- new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling)
}
@@ -82,9 +80,9 @@ namespace osu.Game.Tests.Visual.Gameplay
};
});
- AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 }));
+ AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 50, 30 }));
AddStep("adjust scale", () => fill.Scale = new Vector2(2));
- AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 }));
+ AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 50, 30 }));
}
[Test]
@@ -182,7 +180,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public new Drawable Drawable => base.Drawable;
public ExposedSkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null,
- ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ ConfineMode confineMode = ConfineMode.ScaleToFit)
: base(new TestSkinComponent(name), defaultImplementation, allowFallback, confineMode)
{
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
index ff8437311e..9f1492a25f 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
@@ -9,8 +9,12 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Formats;
+using osu.Game.IO;
using osu.Game.Overlays;
+using osu.Game.Storyboards;
using osu.Game.Storyboards.Drawables;
+using osu.Game.Tests.Resources;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Gameplay
@@ -54,7 +58,11 @@ namespace osu.Game.Tests.Visual.Gameplay
State = { Value = Visibility.Visible },
}
});
+ }
+ [Test]
+ public void TestStoryboard()
+ {
AddStep("Restart", restart);
AddToggleStep("Passing", passing =>
{
@@ -62,6 +70,12 @@ namespace osu.Game.Tests.Visual.Gameplay
});
}
+ [Test]
+ public void TestStoryboardMissingVideo()
+ {
+ AddStep("Load storyboard with missing video", loadStoryboardNoVideo);
+ }
+
[BackgroundDependencyLoader]
private void load()
{
@@ -94,5 +108,28 @@ namespace osu.Game.Tests.Visual.Gameplay
storyboardContainer.Add(storyboard);
decoupledClock.ChangeSource(working.Track);
}
+
+ private void loadStoryboardNoVideo()
+ {
+ if (storyboard != null)
+ storyboardContainer.Remove(storyboard);
+
+ var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true };
+ storyboardContainer.Clock = decoupledClock;
+
+ Storyboard sb;
+
+ using (var str = TestResources.OpenResource("storyboard_no_video.osu"))
+ using (var bfr = new LineBufferedReader(str))
+ {
+ var decoder = new LegacyStoryboardDecoder();
+ sb = decoder.Decode(bfr);
+ }
+
+ storyboard = sb.CreateDrawable(Beatmap.Value);
+
+ storyboardContainer.Add(storyboard);
+ decoupledClock.ChangeSource(Beatmap.Value.Track);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs
index 1ad4d9dca9..33811f9529 100644
--- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs
+++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs
@@ -22,7 +22,6 @@ namespace osu.Game.Tests.Visual.Menus
{
typeof(StartupScreen),
typeof(IntroScreen),
- typeof(OsuScreen),
typeof(IntroTestScene),
};
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs
index b3064ba9be..c44363d9ea 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs
@@ -36,8 +36,6 @@ namespace osu.Game.Tests.Visual.Menus
[Test]
public void TestInstantLoad()
{
- // visual only, very impossible to test this using asserts.
-
AddStep("load immediately", () =>
{
loader = new TestLoader();
@@ -46,12 +44,17 @@ namespace osu.Game.Tests.Visual.Menus
LoadScreen(loader);
});
- AddAssert("spinner did not display", () => loader.LoadingSpinner?.Alpha == 0);
+ spinnerNotPresentOrHidden();
AddUntilStep("loaded", () => loader.ScreenLoaded);
AddUntilStep("not current", () => !loader.IsCurrentScreen());
+
+ spinnerNotPresentOrHidden();
}
+ private void spinnerNotPresentOrHidden() =>
+ AddAssert("spinner did not display", () => loader.LoadingSpinner == null || loader.LoadingSpinner.Alpha == 0);
+
[Test]
public void TestDelayedLoad()
{
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
index 0cc37bbd57..76a8ee9914 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
@@ -83,6 +83,82 @@ namespace osu.Game.Tests.Visual.SongSelect
waitForSelection(set_count, 3);
}
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestTraversalBeyondVisible(bool forwards)
+ {
+ var sets = new List();
+
+ const int total_set_count = 200;
+
+ for (int i = 0; i < total_set_count; i++)
+ sets.Add(createTestBeatmapSet(i + 1));
+
+ loadBeatmaps(sets);
+
+ for (int i = 1; i < total_set_count; i += i)
+ selectNextAndAssert(i);
+
+ void selectNextAndAssert(int amount)
+ {
+ setSelected(forwards ? 1 : total_set_count, 1);
+
+ AddStep($"{(forwards ? "Next" : "Previous")} beatmap {amount} times", () =>
+ {
+ for (int i = 0; i < amount; i++)
+ {
+ carousel.SelectNext(forwards ? 1 : -1);
+ }
+ });
+
+ waitForSelection(forwards ? amount + 1 : total_set_count - amount);
+ }
+ }
+
+ [Test]
+ public void TestTraversalBeyondVisibleDifficulties()
+ {
+ var sets = new List();
+
+ const int total_set_count = 20;
+
+ for (int i = 0; i < total_set_count; i++)
+ sets.Add(createTestBeatmapSet(i + 1));
+
+ loadBeatmaps(sets);
+
+ // Selects next set once, difficulty index doesn't change
+ selectNextAndAssert(3, true, 2, 1);
+
+ // Selects next set 16 times (50 \ 3 == 16), difficulty index changes twice (50 % 3 == 2)
+ selectNextAndAssert(50, true, 17, 3);
+
+ // Travels around the carousel thrice (200 \ 60 == 3)
+ // continues to select 20 times (200 \ 60 == 20)
+ // selects next set 6 times (20 \ 3 == 6)
+ // difficulty index changes twice (20 % 3 == 2)
+ selectNextAndAssert(200, true, 7, 3);
+
+ // All same but in reverse
+ selectNextAndAssert(3, false, 19, 3);
+ selectNextAndAssert(50, false, 4, 1);
+ selectNextAndAssert(200, false, 14, 1);
+
+ void selectNextAndAssert(int amount, bool forwards, int expectedSet, int expectedDiff)
+ {
+ // Select very first or very last difficulty
+ setSelected(forwards ? 1 : 20, forwards ? 1 : 3);
+
+ AddStep($"{(forwards ? "Next" : "Previous")} difficulty {amount} times", () =>
+ {
+ for (int i = 0; i < amount; i++)
+ carousel.SelectNext(forwards ? 1 : -1, false);
+ });
+
+ waitForSelection(expectedSet, expectedDiff);
+ }
+ }
+
///
/// Test filtering
///
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
index d56324dbe8..03a19b6690 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
@@ -91,13 +91,14 @@ namespace osu.Game.Tests.Visual.UserInterface
var easierMods = osu.GetModsFor(ModType.DifficultyReduction);
var harderMods = osu.GetModsFor(ModType.DifficultyIncrease);
+ var conversionMods = osu.GetModsFor(ModType.Conversion);
var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail);
var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden);
var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime));
- var spunOutMod = easierMods.FirstOrDefault(m => m is OsuModSpunOut);
+ var targetMod = conversionMods.FirstOrDefault(m => m is OsuModTarget);
var easy = easierMods.FirstOrDefault(m => m is OsuModEasy);
var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock);
@@ -109,7 +110,7 @@ namespace osu.Game.Tests.Visual.UserInterface
testMultiplierTextColour(noFailMod, () => modSelect.LowMultiplierColour);
testMultiplierTextColour(hiddenMod, () => modSelect.HighMultiplierColour);
- testUnimplementedMod(spunOutMod);
+ testUnimplementedMod(targetMod);
}
[Test]
diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs
index bc66fad8c1..317c5f6a56 100644
--- a/osu.Game.Tournament/Components/TourneyVideo.cs
+++ b/osu.Game.Tournament/Components/TourneyVideo.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Tournament.Components
{
private readonly string filename;
private readonly bool drawFallbackGradient;
- private VideoSprite video;
+ private Video video;
private ManualClock manualClock;
@@ -33,7 +33,7 @@ namespace osu.Game.Tournament.Components
if (stream != null)
{
- InternalChild = video = new VideoSprite(stream, false)
+ InternalChild = video = new Video(stream, false)
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index abb3f8ac42..6542866936 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -87,7 +87,7 @@ namespace osu.Game.Beatmaps
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz";
- protected override Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
+ protected override async Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
{
if (archive != null)
beatmapSet.Beatmaps = createBeatmapDifficulties(beatmapSet.Files);
@@ -103,7 +103,19 @@ namespace osu.Game.Beatmaps
validateOnlineIds(beatmapSet);
- return updateQueue.UpdateAsync(beatmapSet, cancellationToken);
+ bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0);
+
+ await updateQueue.UpdateAsync(beatmapSet, cancellationToken);
+
+ // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID.
+ if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0))
+ {
+ if (beatmapSet.OnlineBeatmapSetID != null)
+ {
+ beatmapSet.OnlineBeatmapSetID = null;
+ LogForModel(beatmapSet, "Disassociating beatmap set ID due to loss of all beatmap IDs");
+ }
+ }
}
protected override void PreImport(BeatmapSetInfo beatmapSet)
@@ -447,12 +459,15 @@ namespace osu.Game.Beatmaps
var res = req.Result;
- beatmap.Status = res.Status;
- beatmap.BeatmapSet.Status = res.BeatmapSet.Status;
- beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
- beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
+ if (res != null)
+ {
+ beatmap.Status = res.Status;
+ beatmap.BeatmapSet.Status = res.BeatmapSet.Status;
+ beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
+ beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
- LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.");
+ LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.");
+ }
}
catch (Exception e)
{
diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs
index 45122f6312..845ac20db0 100644
--- a/osu.Game/Beatmaps/Formats/Decoder.cs
+++ b/osu.Game/Beatmaps/Formats/Decoder.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Beatmaps.Formats
if (line == null)
throw new IOException("Unknown file format (null)");
- var decoder = typedDecoders.Select(d => line.StartsWith(d.Key, StringComparison.InvariantCulture) ? d.Value : null).FirstOrDefault();
+ var decoder = typedDecoders.Where(d => line.StartsWith(d.Key, StringComparison.InvariantCulture)).Select(d => d.Value).FirstOrDefault();
// it's important the magic does NOT get consumed here, since sometimes it's part of the structure
// (see JsonBeatmapDecoder - the magic string is the opening brace)
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index e28e235788..561707f9ef 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -41,6 +41,7 @@ namespace osu.Game.Beatmaps.Formats
section = Section.None;
}
+ OnBeginNewSection(section);
continue;
}
@@ -57,6 +58,14 @@ namespace osu.Game.Beatmaps.Formats
protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.AsSpan().TrimStart().StartsWith("//".AsSpan(), StringComparison.Ordinal);
+ ///
+ /// Invoked when a new has been entered.
+ ///
+ /// The entered .
+ protected virtual void OnBeginNewSection(Section section)
+ {
+ }
+
protected virtual void ParseLine(T output, Section section, string line)
{
line = StripComments(line);
@@ -64,7 +73,7 @@ namespace osu.Game.Beatmaps.Formats
switch (section)
{
case Section.Colours:
- handleColours(output, line);
+ HandleColours(output, line);
return;
}
}
@@ -78,7 +87,7 @@ namespace osu.Game.Beatmaps.Formats
return line;
}
- private void handleColours(T output, string line)
+ protected void HandleColours(TModel output, string line)
{
var pair = SplitKeyVal(line);
@@ -139,7 +148,8 @@ namespace osu.Game.Beatmaps.Formats
Colours,
HitObjects,
Variables,
- Fonts
+ Fonts,
+ Mania
}
internal class LegacyDifficultyControlPoint : DifficultyControlPoint
diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs
new file mode 100644
index 0000000000..1790eb608e
--- /dev/null
+++ b/osu.Game/Extensions/DrawableExtensions.cs
@@ -0,0 +1,32 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Threading;
+
+namespace osu.Game.Extensions
+{
+ public static class DrawableExtensions
+ {
+ ///
+ /// Helper method that is used while doesn't support repetitions of .
+ /// Simulates repetitions by continually invoking a delegate according to the default key repeat rate.
+ ///
+ ///
+ /// The returned delegate can be cancelled to stop repeat events from firing (usually in ).
+ ///
+ /// The which is handling the repeat.
+ /// The to schedule repetitions on.
+ /// The to be invoked once immediately and with every repetition.
+ /// A which can be cancelled to stop the repeat events from firing.
+ public static ScheduledDelegate BeginKeyRepeat(this IKeyBindingHandler handler, Scheduler scheduler, Action action)
+ {
+ action();
+
+ ScheduledDelegate repeatDelegate = new ScheduledDelegate(action, handler.Time.Current + 250, 70);
+ scheduler.Add(repeatDelegate);
+ return repeatDelegate;
+ }
+ }
+}
diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs
index 4485ce3447..39c1fdad52 100644
--- a/osu.Game/Graphics/Containers/UserDimContainer.cs
+++ b/osu.Game/Graphics/Containers/UserDimContainer.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Graphics.Containers
///
/// Whether player is in break time.
- /// Must be bound to to allow for dim adjustments in gameplay.
+ /// Must be bound to to allow for dim adjustments in gameplay.
///
public readonly IBindable IsBreakTime = new Bindable();
diff --git a/osu.Game/IO/LineBufferedReader.cs b/osu.Game/IO/LineBufferedReader.cs
index aab761afd8..018321dc9a 100644
--- a/osu.Game/IO/LineBufferedReader.cs
+++ b/osu.Game/IO/LineBufferedReader.cs
@@ -17,9 +17,9 @@ namespace osu.Game.IO
private readonly StreamReader streamReader;
private readonly Queue lineBuffer;
- public LineBufferedReader(Stream stream)
+ public LineBufferedReader(Stream stream, bool leaveOpen = false)
{
- streamReader = new StreamReader(stream);
+ streamReader = new StreamReader(stream, Encoding.UTF8, true, 1024, leaveOpen);
lineBuffer = new Queue();
}
diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs
index 30c1018c1e..6a6c7b72a8 100644
--- a/osu.Game/Online/API/APIRequest.cs
+++ b/osu.Game/Online/API/APIRequest.cs
@@ -12,11 +12,11 @@ namespace osu.Game.Online.API
/// An API request with a well-defined response type.
///
/// Type of the response (used for deserialisation).
- public abstract class APIRequest : APIRequest
+ public abstract class APIRequest : APIRequest where T : class
{
protected override WebRequest CreateWebRequest() => new OsuJsonWebRequest(Uri);
- public T Result => ((OsuJsonWebRequest)WebRequest).ResponseObject;
+ public T Result => ((OsuJsonWebRequest)WebRequest)?.ResponseObject;
protected APIRequest()
{
diff --git a/osu.Game/Online/API/Requests/GetRankingsRequest.cs b/osu.Game/Online/API/Requests/GetRankingsRequest.cs
index 941691c4c1..ddc3298ca7 100644
--- a/osu.Game/Online/API/Requests/GetRankingsRequest.cs
+++ b/osu.Game/Online/API/Requests/GetRankingsRequest.cs
@@ -6,7 +6,7 @@ using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests
{
- public abstract class GetRankingsRequest : APIRequest
+ public abstract class GetRankingsRequest : APIRequest where TModel : class
{
private readonly RulesetInfo ruleset;
private readonly int page;
diff --git a/osu.Game/Online/API/Requests/PaginatedAPIRequest.cs b/osu.Game/Online/API/Requests/PaginatedAPIRequest.cs
index 52e12f04ee..bddc34a0dc 100644
--- a/osu.Game/Online/API/Requests/PaginatedAPIRequest.cs
+++ b/osu.Game/Online/API/Requests/PaginatedAPIRequest.cs
@@ -6,7 +6,7 @@ using osu.Framework.IO.Network;
namespace osu.Game.Online.API.Requests
{
- public abstract class PaginatedAPIRequest : APIRequest
+ public abstract class PaginatedAPIRequest : APIRequest where T : class
{
private readonly int page;
private readonly int itemsPerPage;
diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
index 59d39a1c3c..e7f2f21465 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.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 osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
@@ -56,24 +57,32 @@ namespace osu.Game.Overlays.Settings.Sections.Input
},
};
- rawInputToggle.ValueChanged += enabled =>
+ if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows)
{
- // this is temporary until we support per-handler settings.
- const string raw_mouse_handler = @"OsuTKRawMouseHandler";
- const string standard_mouse_handler = @"OsuTKMouseHandler";
-
- ignoredInputHandler.Value = enabled.NewValue ? standard_mouse_handler : raw_mouse_handler;
- };
-
- ignoredInputHandler = config.GetBindable(FrameworkSetting.IgnoredInputHandlers);
- ignoredInputHandler.ValueChanged += handler =>
+ rawInputToggle.Disabled = true;
+ sensitivity.Bindable.Disabled = true;
+ }
+ else
{
- bool raw = !handler.NewValue.Contains("Raw");
- rawInputToggle.Value = raw;
- sensitivity.Bindable.Disabled = !raw;
- };
+ rawInputToggle.ValueChanged += enabled =>
+ {
+ // this is temporary until we support per-handler settings.
+ const string raw_mouse_handler = @"OsuTKRawMouseHandler";
+ const string standard_mouse_handler = @"OsuTKMouseHandler";
- ignoredInputHandler.TriggerChange();
+ ignoredInputHandler.Value = enabled.NewValue ? standard_mouse_handler : raw_mouse_handler;
+ };
+
+ ignoredInputHandler = config.GetBindable(FrameworkSetting.IgnoredInputHandlers);
+ ignoredInputHandler.ValueChanged += handler =>
+ {
+ bool raw = !handler.NewValue.Contains("Raw");
+ rawInputToggle.Value = raw;
+ sensitivity.Bindable.Disabled = !raw;
+ };
+
+ ignoredInputHandler.TriggerChange();
+ }
}
private class SensitivitySetting : SettingsSlider
diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs
index 46c0c1da07..0e5fe3fc9c 100644
--- a/osu.Game/Rulesets/Mods/Mod.cs
+++ b/osu.Game/Rulesets/Mods/Mod.cs
@@ -2,9 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
using Newtonsoft.Json;
+using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Configuration;
using osu.Game.IO.Serialization;
+using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mods
{
@@ -42,6 +48,51 @@ namespace osu.Game.Rulesets.Mods
[JsonIgnore]
public virtual string Description => string.Empty;
+ ///
+ /// The tooltip to display for this mod when used in a .
+ ///
+ ///
+ /// Differs from , as the value of attributes (AR, CS, etc) changeable via the mod
+ /// are displayed in the tooltip.
+ ///
+ [JsonIgnore]
+ public string IconTooltip
+ {
+ get
+ {
+ string description = SettingDescription;
+
+ return string.IsNullOrEmpty(description) ? Name : $"{Name} ({description})";
+ }
+ }
+
+ ///
+ /// The description of editable settings of a mod to use in the .
+ ///
+ ///
+ /// Parentheses are added to the tooltip, surrounding the value of this property. If this property is string.Empty,
+ /// the tooltip will not have parentheses.
+ ///
+ public virtual string SettingDescription
+ {
+ get
+ {
+ var tooltipTexts = new List();
+
+ foreach ((SettingSourceAttribute attr, PropertyInfo property) in this.GetOrderedSettingsSourceProperties())
+ {
+ object bindableObj = property.GetValue(this);
+
+ if ((bindableObj as IHasDefaultValue)?.IsDefault == true)
+ continue;
+
+ tooltipTexts.Add($"{attr.Label} {bindableObj}");
+ }
+
+ return string.Join(", ", tooltipTexts.Where(s => !string.IsNullOrEmpty(s)));
+ }
+ }
+
///
/// The score multiplier of this mod.
///
diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
index 2083671072..c3a8efdd66 100644
--- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
+++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
@@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites;
using System;
using System.Collections.Generic;
using osu.Game.Configuration;
+using System.Linq;
namespace osu.Game.Rulesets.Mods
{
@@ -52,6 +53,21 @@ namespace osu.Game.Rulesets.Mods
Value = 5,
};
+ public override string SettingDescription
+ {
+ get
+ {
+ string drainRate = DrainRate.IsDefault ? string.Empty : $"HP {DrainRate.Value:N1}";
+ string overallDifficulty = OverallDifficulty.IsDefault ? string.Empty : $"OD {OverallDifficulty.Value:N1}";
+
+ return string.Join(", ", new[]
+ {
+ drainRate,
+ overallDifficulty
+ }.Where(s => !string.IsNullOrEmpty(s)));
+ }
+ }
+
private BeatmapDifficulty difficulty;
public void ReadFromDifficulty(BeatmapDifficulty difficulty)
@@ -79,7 +95,7 @@ namespace osu.Game.Rulesets.Mods
///
/// Transfer a setting from to a configuration bindable.
- /// Only performs the transfer if the user it not currently overriding..
+ /// Only performs the transfer if the user is not currently overriding.
///
protected void TransferSetting(BindableNumber bindable, T beatmapDefault)
where T : struct, IComparable, IConvertible, IEquatable
diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs
index b56be95dfe..c1c4124b98 100644
--- a/osu.Game/Rulesets/Mods/ModEasy.cs
+++ b/osu.Game/Rulesets/Mods/ModEasy.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using Humanizer;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
@@ -28,6 +29,8 @@ namespace osu.Game.Rulesets.Mods
MaxValue = 10
};
+ public override string SettingDescription => Retries.IsDefault ? string.Empty : $"{"lives".ToQuantity(Retries.Value)}";
+
private int retries;
private BindableNumber health;
diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs
index 1739524bcd..cb2ff149f1 100644
--- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs
+++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs
@@ -15,5 +15,7 @@ namespace osu.Game.Rulesets.Mods
{
track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange);
}
+
+ public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x";
}
}
diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
index 9e63142b42..c1f3e357a1 100644
--- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs
+++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
@@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Mods
[SettingSource("Final rate", "The final speed to ramp to")]
public abstract BindableNumber FinalRate { get; }
+ public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x";
+
private double finalRateTime;
private double beginRampTime;
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index aa29e42fac..0011faefbb 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -38,6 +38,15 @@ namespace osu.Game.Rulesets.Objects.Drawables
private readonly Lazy> nestedHitObjects = new Lazy>();
public IReadOnlyList NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : (IReadOnlyList)Array.Empty();
+ ///
+ /// Whether this object should handle any user input events.
+ ///
+ public bool HandleUserInput { get; set; } = true;
+
+ public override bool PropagatePositionalInputSubTree => HandleUserInput;
+
+ public override bool PropagateNonPositionalInputSubTree => HandleUserInput;
+
///
/// Invoked when a has been applied by this or a nested .
///
@@ -344,7 +353,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// Plays all the hit sounds for this .
/// This is invoked automatically when this is hit.
///
- public void PlaySamples() => Samples?.Play();
+ public virtual void PlaySamples() => Samples?.Play();
protected override void Update()
{
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index 58f598a203..bee11accca 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -102,7 +102,7 @@ namespace osu.Game.Rulesets
public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().First();
- public virtual ISkin CreateLegacySkinProvider(ISkinSource source) => null;
+ public virtual ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => null;
protected Ruleset()
{
diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs
index 27993ff173..5062c92afe 100644
--- a/osu.Game/Rulesets/UI/DrawableRuleset.cs
+++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs
@@ -72,9 +72,9 @@ namespace osu.Game.Rulesets.UI
///
public override Playfield Playfield => playfield.Value;
- private Container overlays;
+ public override Container Overlays { get; } = new Container { RelativeSizeAxes = Axes.Both };
- public override Container Overlays => overlays;
+ public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both };
public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock;
@@ -187,11 +187,12 @@ namespace osu.Game.Rulesets.UI
FrameStablePlayback = FrameStablePlayback,
Children = new Drawable[]
{
+ FrameStableComponents,
KeyBindingInputManager
.WithChild(CreatePlayfieldAdjustmentContainer()
.WithChild(Playfield)
),
- overlays = new Container { RelativeSizeAxes = Axes.Both }
+ Overlays,
}
},
};
@@ -406,10 +407,15 @@ namespace osu.Game.Rulesets.UI
public abstract Playfield Playfield { get; }
///
- /// Place to put drawables above hit objects but below UI.
+ /// Content to be placed above hitobjects. Will be affected by frame stability.
///
public abstract Container Overlays { get; }
+ ///
+ /// Components to be run potentially multiple times in line with frame-stable gameplay.
+ ///
+ public abstract Container FrameStableComponents { get; }
+
///
/// The frame-stable clock which is being used for playfield display.
///
diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
index e569bb8459..3ba28aad45 100644
--- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
+++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.UI
{
///
/// A container which consumes a parent gameplay clock and standardises frame counts for children.
- /// Will ensure a minimum of 40 frames per clock second is maintained, regardless of any system lag or seeks.
+ /// Will ensure a minimum of 50 frames per clock second is maintained, regardless of any system lag or seeks.
///
public class FrameStabilityContainer : Container, IHasReplayHandler
{
diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs
index 3edab0745d..8ea6c74349 100644
--- a/osu.Game/Rulesets/UI/ModIcon.cs
+++ b/osu.Game/Rulesets/UI/ModIcon.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.UI
private readonly ModType type;
- public virtual string TooltipText { get; }
+ public virtual string TooltipText => mod.IconTooltip;
private Mod mod;
@@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.UI
type = mod.Type;
- TooltipText = mod.Name;
-
Size = new Vector2(size);
Children = new Drawable[]
diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs
index 8141108aef..c52183f3f2 100644
--- a/osu.Game/Rulesets/UI/Playfield.cs
+++ b/osu.Game/Rulesets/UI/Playfield.cs
@@ -10,7 +10,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Containers;
-using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osuTK;
@@ -62,10 +61,7 @@ namespace osu.Game.Rulesets.UI
hitObjectContainerLazy = new Lazy(CreateHitObjectContainer);
}
- [Resolved]
- private IBindable beatmap { get; set; }
-
- [Resolved]
+ [Resolved(CanBeNull = true)]
private IReadOnlyList mods { get; set; }
[BackgroundDependencyLoader]
@@ -137,7 +133,7 @@ namespace osu.Game.Rulesets.UI
{
base.Update();
- if (beatmap != null)
+ if (mods != null)
{
foreach (var mod in mods)
{
diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
index 8bcdfff2fd..0955f32790 100644
--- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
@@ -9,9 +9,11 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
using osu.Framework.Lists;
+using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
+using osu.Game.Extensions;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@@ -74,11 +76,9 @@ namespace osu.Game.Rulesets.UI.Scrolling
protected virtual bool RelativeScaleBeatLengths => false;
///
- /// Provides the default s that adjust the scrolling rate of s
- /// inside this .
+ /// The s that adjust the scrolling rate of s inside this .
///
- ///
- private readonly SortedList controlPoints = new SortedList(Comparer.Default);
+ protected readonly SortedList ControlPoints = new SortedList(Comparer.Default);
protected IScrollingInfo ScrollingInfo => scrollingInfo;
@@ -95,11 +95,11 @@ namespace osu.Game.Rulesets.UI.Scrolling
switch (VisualisationMethod)
{
case ScrollVisualisationMethod.Sequential:
- scrollingInfo.Algorithm = new SequentialScrollAlgorithm(controlPoints);
+ scrollingInfo.Algorithm = new SequentialScrollAlgorithm(ControlPoints);
break;
case ScrollVisualisationMethod.Overlapping:
- scrollingInfo.Algorithm = new OverlappingScrollAlgorithm(controlPoints);
+ scrollingInfo.Algorithm = new OverlappingScrollAlgorithm(ControlPoints);
break;
case ScrollVisualisationMethod.Constant:
@@ -168,29 +168,10 @@ namespace osu.Game.Rulesets.UI.Scrolling
// Collapse sections with the same start time
.GroupBy(s => s.StartTime).Select(g => g.Last()).OrderBy(s => s.StartTime);
- controlPoints.AddRange(timingChanges);
+ ControlPoints.AddRange(timingChanges);
- if (controlPoints.Count == 0)
- controlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier });
- }
-
- public bool OnPressed(GlobalAction action)
- {
- if (!UserScrollSpeedAdjustment)
- return false;
-
- switch (action)
- {
- case GlobalAction.IncreaseScrollSpeed:
- this.TransformBindableTo(TimeRange, TimeRange.Value - time_span_step, 200, Easing.OutQuint);
- return true;
-
- case GlobalAction.DecreaseScrollSpeed:
- this.TransformBindableTo(TimeRange, TimeRange.Value + time_span_step, 200, Easing.OutQuint);
- return true;
- }
-
- return false;
+ if (ControlPoints.Count == 0)
+ ControlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier });
}
protected override void LoadComplete()
@@ -201,8 +182,43 @@ namespace osu.Game.Rulesets.UI.Scrolling
throw new ArgumentException($"{nameof(Playfield)} must be a {nameof(ScrollingPlayfield)} when using {nameof(DrawableScrollingRuleset)}.");
}
+ ///
+ /// Adjusts the scroll speed of s.
+ ///
+ /// The amount to adjust by. Greater than 0 if the scroll speed should be increased, less than 0 if it should be decreased.
+ protected virtual void AdjustScrollSpeed(int amount) => this.TransformBindableTo(TimeRange, TimeRange.Value - amount * time_span_step, 200, Easing.OutQuint);
+
+ public bool OnPressed(GlobalAction action)
+ {
+ if (!UserScrollSpeedAdjustment)
+ return false;
+
+ switch (action)
+ {
+ case GlobalAction.IncreaseScrollSpeed:
+ scheduleScrollSpeedAdjustment(1);
+ return true;
+
+ case GlobalAction.DecreaseScrollSpeed:
+ scheduleScrollSpeedAdjustment(-1);
+ return true;
+ }
+
+ return false;
+ }
+
+ private ScheduledDelegate scheduledScrollSpeedAdjustment;
+
public void OnReleased(GlobalAction action)
{
+ scheduledScrollSpeedAdjustment?.Cancel();
+ scheduledScrollSpeedAdjustment = null;
+ }
+
+ private void scheduleScrollSpeedAdjustment(int amount)
+ {
+ scheduledScrollSpeedAdjustment?.Cancel();
+ scheduledScrollSpeedAdjustment = this.BeginKeyRepeat(Scheduler, () => AdjustScrollSpeed(amount));
}
private class LocalScrollingInfo : IScrollingInfo
diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
index 58b64e1b8f..a4a560c8e4 100644
--- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
+++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
@@ -28,10 +28,11 @@ namespace osu.Game.Scoring.Legacy
{
var score = new Score
{
- ScoreInfo = new ScoreInfo(),
Replay = new Replay()
};
+ WorkingBeatmap workingBeatmap;
+
using (SerializationReader sr = new SerializationReader(stream))
{
currentRuleset = GetRuleset(sr.ReadByte());
@@ -41,13 +42,10 @@ namespace osu.Game.Scoring.Legacy
var version = sr.ReadInt32();
- var workingBeatmap = GetBeatmap(sr.ReadString());
+ workingBeatmap = GetBeatmap(sr.ReadString());
if (workingBeatmap is DummyWorkingBeatmap)
throw new BeatmapNotFoundException();
- currentBeatmap = workingBeatmap.Beatmap;
- scoreInfo.Beatmap = currentBeatmap.BeatmapInfo;
-
scoreInfo.User = new User { Username = sr.ReadString() };
// MD5Hash
@@ -68,6 +66,9 @@ namespace osu.Game.Scoring.Legacy
scoreInfo.Mods = currentRuleset.ConvertFromLegacyMods((LegacyMods)sr.ReadInt32()).ToArray();
+ currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods);
+ scoreInfo.Beatmap = currentBeatmap.BeatmapInfo;
+
/* score.HpGraphString = */
sr.ReadString();
@@ -113,6 +114,10 @@ namespace osu.Game.Scoring.Legacy
CalculateAccuracy(score.ScoreInfo);
+ // before returning for database import, we must restore the database-sourced BeatmapInfo.
+ // if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception.
+ score.ScoreInfo.Beatmap = workingBeatmap.BeatmapInfo;
+
return score;
}
diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
index cdea200e10..04983ca597 100644
--- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
+++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Screens.Edit.Compose
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
// full access to all skin sources.
- var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider));
+ var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, EditorBeatmap.PlayableBeatmap));
// load the skinning hierarchy first.
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs
index be5762e68d..b44b6ea993 100644
--- a/osu.Game/Screens/Menu/IntroTriangles.cs
+++ b/osu.Game/Screens/Menu/IntroTriangles.cs
@@ -270,10 +270,9 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader]
private void load()
{
- InternalChild = new VideoSprite(videoStream, false)
+ InternalChild = new Video(videoStream, false)
{
RelativeSizeAxes = Axes.Both,
- Clock = new FramedOffsetClock(Clock) { Offset = -logo_1 }
};
}
}
diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs
index ee8be87352..c978f4e96d 100644
--- a/osu.Game/Screens/Play/BreakOverlay.cs
+++ b/osu.Game/Screens/Play/BreakOverlay.cs
@@ -2,8 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -16,8 +14,6 @@ namespace osu.Game.Screens.Play
{
public class BreakOverlay : Container
{
- private readonly ScoreProcessor scoreProcessor;
-
///
/// The duration of the break overlay fading.
///
@@ -37,10 +33,6 @@ namespace osu.Game.Screens.Play
{
breaks = value;
- // reset index in case the new breaks list is smaller than last one
- isBreakTime.Value = false;
- CurrentBreakIndex = 0;
-
if (IsLoaded)
initializeBreaks();
}
@@ -48,27 +40,17 @@ namespace osu.Game.Screens.Play
public override bool RemoveCompletedTransforms => false;
- ///
- /// Whether the gameplay is currently in a break.
- ///
- public IBindable IsBreakTime => isBreakTime;
-
- protected int CurrentBreakIndex;
-
- private readonly BindableBool isBreakTime = new BindableBool();
-
private readonly Container remainingTimeAdjustmentBox;
private readonly Container remainingTimeBox;
private readonly RemainingTimeCounter remainingTimeCounter;
- private readonly BreakInfo info;
private readonly BreakArrows breakArrows;
- private readonly double gameplayStartTime;
- public BreakOverlay(bool letterboxing, double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null)
+ public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor)
{
- this.gameplayStartTime = gameplayStartTime;
- this.scoreProcessor = scoreProcessor;
RelativeSizeAxes = Axes.Both;
+
+ BreakInfo info;
+
Child = fadeContainer = new Container
{
Alpha = 0,
@@ -119,13 +101,11 @@ namespace osu.Game.Screens.Play
}
};
- if (scoreProcessor != null) bindProcessor(scoreProcessor);
- }
-
- [BackgroundDependencyLoader(true)]
- private void load(GameplayClock clock)
- {
- if (clock != null) Clock = clock;
+ if (scoreProcessor != null)
+ {
+ info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy);
+ info.GradeDisplay.Current.BindTo(scoreProcessor.Rank);
+ }
}
protected override void LoadComplete()
@@ -134,42 +114,6 @@ namespace osu.Game.Screens.Play
initializeBreaks();
}
- protected override void Update()
- {
- base.Update();
- updateBreakTimeBindable();
- }
-
- private void updateBreakTimeBindable() =>
- isBreakTime.Value = getCurrentBreak()?.HasEffect == true
- || Clock.CurrentTime < gameplayStartTime
- || scoreProcessor?.HasCompleted == true;
-
- private BreakPeriod getCurrentBreak()
- {
- if (breaks?.Count > 0)
- {
- var time = Clock.CurrentTime;
-
- if (time > breaks[CurrentBreakIndex].EndTime)
- {
- while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1)
- CurrentBreakIndex++;
- }
- else
- {
- while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0)
- CurrentBreakIndex--;
- }
-
- var closest = breaks[CurrentBreakIndex];
-
- return closest.Contains(time) ? closest : null;
- }
-
- return null;
- }
-
private void initializeBreaks()
{
FinishTransforms(true);
@@ -207,11 +151,5 @@ namespace osu.Game.Screens.Play
}
}
}
-
- private void bindProcessor(ScoreProcessor processor)
- {
- info.AccuracyDisplay.Current.BindTo(processor.Accuracy);
- info.GradeDisplay.Current.BindTo(processor.Rank);
- }
}
}
diff --git a/osu.Game/Screens/Play/BreakTracker.cs b/osu.Game/Screens/Play/BreakTracker.cs
new file mode 100644
index 0000000000..64262d52b5
--- /dev/null
+++ b/osu.Game/Screens/Play/BreakTracker.cs
@@ -0,0 +1,82 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Screens.Play
+{
+ public class BreakTracker : Component
+ {
+ private readonly ScoreProcessor scoreProcessor;
+
+ private readonly double gameplayStartTime;
+
+ ///
+ /// Whether the gameplay is currently in a break.
+ ///
+ public IBindable IsBreakTime => isBreakTime;
+
+ protected int CurrentBreakIndex;
+
+ private readonly BindableBool isBreakTime = new BindableBool();
+
+ private IReadOnlyList breaks;
+
+ public IReadOnlyList Breaks
+ {
+ get => breaks;
+ set
+ {
+ breaks = value;
+
+ // reset index in case the new breaks list is smaller than last one
+ isBreakTime.Value = false;
+ CurrentBreakIndex = 0;
+ }
+ }
+
+ public BreakTracker(double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null)
+ {
+ this.gameplayStartTime = gameplayStartTime;
+ this.scoreProcessor = scoreProcessor;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ isBreakTime.Value = getCurrentBreak()?.HasEffect == true
+ || Clock.CurrentTime < gameplayStartTime
+ || scoreProcessor?.HasCompleted == true;
+ }
+
+ private BreakPeriod getCurrentBreak()
+ {
+ if (breaks?.Count > 0)
+ {
+ var time = Clock.CurrentTime;
+
+ if (time > breaks[CurrentBreakIndex].EndTime)
+ {
+ while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1)
+ CurrentBreakIndex++;
+ }
+ else
+ {
+ while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0)
+ CurrentBreakIndex--;
+ }
+
+ var closest = breaks[CurrentBreakIndex];
+
+ return closest.Contains(time) ? closest : null;
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index dc5bac9fd1..4597ae760c 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -76,6 +76,8 @@ namespace osu.Game.Screens.Play
public BreakOverlay BreakOverlay;
+ private BreakTracker breakTracker;
+
protected ScoreProcessor ScoreProcessor { get; private set; }
protected HealthProcessor HealthProcessor { get; private set; }
@@ -174,7 +176,7 @@ namespace osu.Game.Screens.Play
dependencies.CacheAs(gameplayBeatmap);
addUnderlayComponents(GameplayClockContainer);
- addGameplayComponents(GameplayClockContainer, Beatmap.Value);
+ addGameplayComponents(GameplayClockContainer, Beatmap.Value, playableBeatmap);
addOverlayComponents(GameplayClockContainer, Beatmap.Value);
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true);
@@ -204,7 +206,7 @@ namespace osu.Game.Screens.Play
foreach (var mod in Mods.Value.OfType())
mod.ApplyToHealthProcessor(HealthProcessor);
- BreakOverlay.IsBreakTime.BindValueChanged(onBreakTimeChanged, true);
+ breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true);
}
private void addUnderlayComponents(Container target)
@@ -212,13 +214,13 @@ namespace osu.Game.Screens.Play
target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both });
}
- private void addGameplayComponents(Container target, WorkingBeatmap working)
+ private void addGameplayComponents(Container target, WorkingBeatmap working, IBeatmap playableBeatmap)
{
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(working.Skin);
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
// full access to all skin sources.
- var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider));
+ var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap));
// load the skinning hierarchy first.
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
@@ -231,12 +233,30 @@ namespace osu.Game.Screens.Play
DrawableRuleset,
new ComboEffects(ScoreProcessor)
});
+
+ DrawableRuleset.FrameStableComponents.AddRange(new Drawable[]
+ {
+ ScoreProcessor,
+ HealthProcessor,
+ breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor)
+ {
+ Breaks = working.Beatmap.Breaks
+ }
+ });
+
+ HealthProcessor.IsBreakTime.BindTo(breakTracker.IsBreakTime);
}
private void addOverlayComponents(Container target, WorkingBeatmap working)
{
target.AddRange(new[]
{
+ BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
+ {
+ Clock = DrawableRuleset.FrameStableClock,
+ ProcessCustomClock = false,
+ Breaks = working.Beatmap.Breaks
+ },
// display the cursor above some HUD elements.
DrawableRuleset.Cursor?.CreateProxy() ?? new Container(),
DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(),
@@ -293,20 +313,8 @@ namespace osu.Game.Screens.Play
performImmediateExit();
},
},
- failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }
+ failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, },
});
-
- DrawableRuleset.Overlays.Add(BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Breaks = working.Beatmap.Breaks
- });
-
- DrawableRuleset.Overlays.Add(ScoreProcessor);
- DrawableRuleset.Overlays.Add(HealthProcessor);
-
- HealthProcessor.IsBreakTime.BindTo(BreakOverlay.IsBreakTime);
}
private void onBreakTimeChanged(ValueChangedEvent isBreakTime)
@@ -318,7 +326,7 @@ namespace osu.Game.Screens.Play
private void updatePauseOnFocusLostState() =>
HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost
&& !DrawableRuleset.HasReplayLoaded.Value
- && !BreakOverlay.IsBreakTime.Value;
+ && !breakTracker.IsBreakTime.Value;
private IBeatmap loadPlayableBeatmap()
{
@@ -540,7 +548,7 @@ namespace osu.Game.Screens.Play
PauseOverlay.Hide();
// breaks and time-based conditions may allow instant resume.
- if (BreakOverlay.IsBreakTime.Value)
+ if (breakTracker.IsBreakTime.Value)
completeResume();
else
DrawableRuleset.RequestResume(completeResume);
@@ -574,8 +582,8 @@ namespace osu.Game.Screens.Play
Background.BlurAmount.Value = 0;
// bind component bindables.
- Background.IsBreakTime.BindTo(BreakOverlay.IsBreakTime);
- DimmableStoryboard.IsBreakTime.BindTo(BreakOverlay.IsBreakTime);
+ Background.IsBreakTime.BindTo(breakTracker.IsBreakTime);
+ DimmableStoryboard.IsBreakTime.BindTo(breakTracker.IsBreakTime);
Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
@@ -629,6 +637,39 @@ namespace osu.Game.Screens.Play
return base.OnExiting(next);
}
+ protected virtual void GotoRanking()
+ {
+ if (DrawableRuleset.ReplayScore != null)
+ {
+ // if a replay is present, we likely don't want to import into the local database.
+ this.Push(CreateResults(CreateScore()));
+ return;
+ }
+
+ LegacyByteArrayReader replayReader = null;
+
+ var score = new Score { ScoreInfo = CreateScore() };
+
+ if (recordingReplay?.Frames.Count > 0)
+ {
+ score.Replay = recordingReplay;
+
+ using (var stream = new MemoryStream())
+ {
+ new LegacyScoreEncoder(score, gameplayBeatmap.PlayableBeatmap).Encode(stream);
+ replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr");
+ }
+ }
+
+ scoreManager.Import(score.ScoreInfo, replayReader)
+ .ContinueWith(imported => Schedule(() =>
+ {
+ // screen may be in the exiting transition phase.
+ if (this.IsCurrentScreen())
+ this.Push(CreateResults(imported.Result));
+ }));
+ }
+
private void fadeOut(bool instant = false)
{
float fadeOutDuration = instant ? 0 : 250;
@@ -641,36 +682,7 @@ namespace osu.Game.Screens.Play
private void scheduleGotoRanking()
{
completionProgressDelegate?.Cancel();
- completionProgressDelegate = Schedule(delegate
- {
- if (DrawableRuleset.ReplayScore != null)
- this.Push(CreateResults(DrawableRuleset.ReplayScore.ScoreInfo));
- else
- {
- var score = new Score { ScoreInfo = CreateScore() };
-
- LegacyByteArrayReader replayReader = null;
-
- if (recordingReplay?.Frames.Count > 0)
- {
- score.Replay = recordingReplay;
-
- using (var stream = new MemoryStream())
- {
- new LegacyScoreEncoder(score, gameplayBeatmap).Encode(stream);
- replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr");
- }
- }
-
- scoreManager.Import(score.ScoreInfo, replayReader)
- .ContinueWith(imported => Schedule(() =>
- {
- // screen may be in the exiting transition phase.
- if (this.IsCurrentScreen())
- this.Push(CreateResults(imported.Result));
- }));
- }
- });
+ completionProgressDelegate = Schedule(GotoRanking);
}
#endregion
diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs
index 8708b5f634..0d2ddb7b01 100644
--- a/osu.Game/Screens/Play/ReplayPlayer.cs
+++ b/osu.Game/Screens/Play/ReplayPlayer.cs
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Screens;
using osu.Game.Scoring;
+using osu.Game.Screens.Ranking;
namespace osu.Game.Screens.Play
{
@@ -23,6 +25,13 @@ namespace osu.Game.Screens.Play
DrawableRuleset?.SetReplayScore(score);
}
+ protected override void GotoRanking()
+ {
+ this.Push(CreateResults(DrawableRuleset.ReplayScore.ScoreInfo));
+ }
+
+ protected override ResultsScreen CreateResults(ScoreInfo score) => new ResultsScreen(score, false);
+
protected override ScoreInfo CreateScore() => score.ScoreInfo;
}
}
diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs
index 803b33a998..cfba1e6e3e 100644
--- a/osu.Game/Screens/Ranking/ResultsScreen.cs
+++ b/osu.Game/Screens/Ranking/ResultsScreen.cs
@@ -31,23 +31,28 @@ namespace osu.Game.Screens.Ranking
[Resolved(CanBeNull = true)]
private Player player { get; set; }
- private readonly ScoreInfo score;
+ public readonly ScoreInfo Score;
+
+ private readonly bool allowRetry;
private Drawable bottomPanel;
- public ResultsScreen(ScoreInfo score)
+ public ResultsScreen(ScoreInfo score, bool allowRetry = true)
{
- this.score = score;
+ Score = score;
+ this.allowRetry = allowRetry;
}
[BackgroundDependencyLoader]
private void load()
{
+ FillFlowContainer buttons;
+
InternalChildren = new[]
{
new ResultsScrollContainer
{
- Child = new ScorePanel(score)
+ Child = new ScorePanel(Score)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -68,7 +73,7 @@ namespace osu.Game.Screens.Ranking
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#333")
},
- new FillFlowContainer
+ buttons = new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -77,16 +82,17 @@ namespace osu.Game.Screens.Ranking
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
- new ReplayDownloadButton(score) { Width = 300 },
- new RetryButton { Width = 300 },
+ new ReplayDownloadButton(Score) { Width = 300 },
}
}
}
}
};
- if (player != null)
+ if (player != null && allowRetry)
{
+ buttons.Add(new RetryButton { Width = 300 });
+
AddInternal(new HotkeyRetryOverlay
{
Action = () =>
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index fa8974f55a..59dddc2baa 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -253,46 +253,37 @@ namespace osu.Game.Screens.Select
/// Whether to skip individual difficulties and only increment over full groups.
public void SelectNext(int direction = 1, bool skipDifficulties = true)
{
- var visibleItems = Items.Where(s => !s.Item.Filtered.Value).ToList();
-
- if (!visibleItems.Any())
+ if (beatmapSets.All(s => s.Filtered.Value))
return;
- DrawableCarouselItem drawable = null;
+ if (skipDifficulties)
+ selectNextSet(direction, true);
+ else
+ selectNextDifficulty(direction);
+ }
- if (selectedBeatmap != null && (drawable = selectedBeatmap.Drawables.FirstOrDefault()) == null)
- // if the selected beatmap isn't present yet, we can't correctly change selection.
- // we can fix this by changing this method to not reference drawables / Items in the first place.
- return;
+ private void selectNextSet(int direction, bool skipDifficulties)
+ {
+ var unfilteredSets = beatmapSets.Where(s => !s.Filtered.Value).ToList();
- int originalIndex = visibleItems.IndexOf(drawable);
- int currentIndex = originalIndex;
+ var nextSet = unfilteredSets[(unfilteredSets.IndexOf(selectedBeatmapSet) + direction + unfilteredSets.Count) % unfilteredSets.Count];
- // local function to increment the index in the required direction, wrapping over extremities.
- int incrementIndex() => currentIndex = (currentIndex + direction + visibleItems.Count) % visibleItems.Count;
+ if (skipDifficulties)
+ select(nextSet);
+ else
+ select(direction > 0 ? nextSet.Beatmaps.First(b => !b.Filtered.Value) : nextSet.Beatmaps.Last(b => !b.Filtered.Value));
+ }
- while (incrementIndex() != originalIndex)
- {
- var item = visibleItems[currentIndex].Item;
+ private void selectNextDifficulty(int direction)
+ {
+ var unfilteredDifficulties = selectedBeatmapSet.Children.Where(s => !s.Filtered.Value).ToList();
- if (item.Filtered.Value || item.State.Value == CarouselItemState.Selected) continue;
+ int index = unfilteredDifficulties.IndexOf(selectedBeatmap);
- switch (item)
- {
- case CarouselBeatmap beatmap:
- if (skipDifficulties) continue;
-
- select(beatmap);
- return;
-
- case CarouselBeatmapSet set:
- if (skipDifficulties)
- select(set);
- else
- select(direction > 0 ? set.Beatmaps.First(b => !b.Filtered.Value) : set.Beatmaps.Last(b => !b.Filtered.Value));
- return;
- }
- }
+ if (index + direction < 0 || index + direction >= unfilteredDifficulties.Count)
+ selectNextSet(direction, false);
+ else
+ select(unfilteredDifficulties[index + direction]);
}
///
diff --git a/osu.Game/Skinning/IAnimationTimeReference.cs b/osu.Game/Skinning/IAnimationTimeReference.cs
new file mode 100644
index 0000000000..4ed5ef64c3
--- /dev/null
+++ b/osu.Game/Skinning/IAnimationTimeReference.cs
@@ -0,0 +1,29 @@
+// 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.Timing;
+
+namespace osu.Game.Skinning
+{
+ ///
+ /// Denotes an object which provides a reference time to start animations from.
+ ///
+ ///
+ /// This should not be used to start an animation immediately at the current time.
+ /// To do so, use with startAtCurrentTime = true
instead.
+ ///
+ [Cached]
+ public interface IAnimationTimeReference
+ {
+ ///
+ /// The reference clock.
+ ///
+ IFrameBasedClock Clock { get; }
+
+ ///
+ /// The time which animations should be started from, relative to .
+ ///
+ double AnimationStartTime { get; }
+ }
+}
diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs
index fa7e895a28..1c39fc41bb 100644
--- a/osu.Game/Skinning/LegacyBeatmapSkin.cs
+++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs
@@ -9,6 +9,8 @@ namespace osu.Game.Skinning
{
public class LegacyBeatmapSkin : LegacySkin
{
+ protected override bool AllowManiaSkin => false;
+
public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore storage, AudioManager audioManager)
: base(createSkinInfo(beatmap), new LegacySkinResourceStore(beatmap.BeatmapSet, storage), audioManager, beatmap.Path)
{
diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
new file mode 100644
index 0000000000..ac257b8c80
--- /dev/null
+++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.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;
+using System.Collections.Generic;
+using osu.Game.Beatmaps.Formats;
+using osuTK.Graphics;
+
+namespace osu.Game.Skinning
+{
+ public class LegacyManiaSkinConfiguration : IHasCustomColours
+ {
+ ///
+ /// Conversion factor from converting legacy positioning values (based in x480 dimensions) to x768.
+ ///
+ public const float POSITION_SCALE_FACTOR = 1.6f;
+
+ ///
+ /// Size of a legacy column in the default skin, used for determining relative scale factors.
+ ///
+ public const float DEFAULT_COLUMN_SIZE = 30 * POSITION_SCALE_FACTOR;
+
+ public readonly int Keys;
+
+ public Dictionary CustomColours { get; set; } = new Dictionary();
+
+ public readonly float[] ColumnLineWidth;
+ public readonly float[] ColumnSpacing;
+ public readonly float[] ColumnWidth;
+ public readonly float[] ExplosionWidth;
+
+ public float HitPosition = (480 - 402) * POSITION_SCALE_FACTOR;
+ public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR;
+ public bool ShowJudgementLine = true;
+
+ public LegacyManiaSkinConfiguration(int keys)
+ {
+ Keys = keys;
+
+ ColumnLineWidth = new float[keys + 1];
+ ColumnSpacing = new float[keys - 1];
+ ColumnWidth = new float[keys];
+ ExplosionWidth = new float[keys];
+
+ ColumnLineWidth.AsSpan().Fill(2);
+ ColumnWidth.AsSpan().Fill(DEFAULT_COLUMN_SIZE);
+ }
+ }
+}
diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
new file mode 100644
index 0000000000..853d07c060
--- /dev/null
+++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
@@ -0,0 +1,41 @@
+// 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.Skinning
+{
+ public class LegacyManiaSkinConfigurationLookup
+ {
+ public readonly int Keys;
+ public readonly LegacyManiaSkinConfigurationLookups Lookup;
+ public readonly int? TargetColumn;
+
+ public LegacyManiaSkinConfigurationLookup(int keys, LegacyManiaSkinConfigurationLookups lookup, int? targetColumn = null)
+ {
+ Keys = keys;
+ Lookup = lookup;
+ TargetColumn = targetColumn;
+ }
+ }
+
+ public enum LegacyManiaSkinConfigurationLookups
+ {
+ ColumnWidth,
+ ColumnSpacing,
+ LightImage,
+ LeftLineWidth,
+ RightLineWidth,
+ HitPosition,
+ LightPosition,
+ HitTargetImage,
+ ShowJudgementLine,
+ KeyImage,
+ KeyImageDown,
+ NoteImage,
+ HoldNoteHeadImage,
+ HoldNoteTailImage,
+ HoldNoteBodyImage,
+ ExplosionImage,
+ ExplosionScale,
+ ColumnLineColour
+ }
+}
diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
new file mode 100644
index 0000000000..3393fe09b3
--- /dev/null
+++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
@@ -0,0 +1,126 @@
+// 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.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using osu.Game.Beatmaps.Formats;
+
+namespace osu.Game.Skinning
+{
+ public class LegacyManiaSkinDecoder : LegacyDecoder>
+ {
+ public LegacyManiaSkinDecoder()
+ : base(1)
+ {
+ }
+
+ private readonly List pendingLines = new List();
+ private LegacyManiaSkinConfiguration currentConfig;
+
+ protected override void OnBeginNewSection(Section section)
+ {
+ base.OnBeginNewSection(section);
+
+ // If a new section is reached with pending lines remaining, they can all be discarded as there isn't a valid configuration to parse them into.
+ pendingLines.Clear();
+ currentConfig = null;
+ }
+
+ protected override void ParseLine(List output, Section section, string line)
+ {
+ line = StripComments(line);
+
+ switch (section)
+ {
+ case Section.Mania:
+ var pair = SplitKeyVal(line);
+
+ switch (pair.Key)
+ {
+ case "Keys":
+ currentConfig = new LegacyManiaSkinConfiguration(int.Parse(pair.Value, CultureInfo.InvariantCulture));
+
+ // Silently ignore duplicate configurations.
+ if (output.All(c => c.Keys != currentConfig.Keys))
+ output.Add(currentConfig);
+
+ // All existing lines can be flushed now that we have a valid configuration.
+ flushPendingLines();
+ break;
+
+ default:
+ pendingLines.Add(line);
+
+ // Hold all lines until a "Keys" item is found.
+ if (currentConfig != null)
+ flushPendingLines();
+ break;
+ }
+
+ break;
+ }
+ }
+
+ private void flushPendingLines()
+ {
+ Debug.Assert(currentConfig != null);
+
+ foreach (var line in pendingLines)
+ {
+ var pair = SplitKeyVal(line);
+
+ if (pair.Key.StartsWith("Colour"))
+ {
+ HandleColours(currentConfig, line);
+ continue;
+ }
+
+ switch (pair.Key)
+ {
+ case "ColumnLineWidth":
+ parseArrayValue(pair.Value, currentConfig.ColumnLineWidth);
+ break;
+
+ case "ColumnSpacing":
+ parseArrayValue(pair.Value, currentConfig.ColumnSpacing);
+ break;
+
+ case "ColumnWidth":
+ parseArrayValue(pair.Value, currentConfig.ColumnWidth);
+ break;
+
+ case "HitPosition":
+ currentConfig.HitPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
+ break;
+
+ case "LightPosition":
+ currentConfig.LightPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
+ break;
+
+ case "JudgementLine":
+ currentConfig.ShowJudgementLine = pair.Value == "1";
+ break;
+
+ case "LightingNWidth":
+ parseArrayValue(pair.Value, currentConfig.ExplosionWidth);
+ break;
+ }
+ }
+ }
+
+ private void parseArrayValue(string value, float[] output)
+ {
+ string[] values = value.Split(',');
+
+ for (int i = 0; i < values.Length; i++)
+ {
+ if (i >= output.Length)
+ break;
+
+ output[i] = float.Parse(values[i], CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
+ }
+ }
+ }
+}
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index c71a321e74..3d3eac97f6 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -1,7 +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 System.Diagnostics;
using System.IO;
using System.Linq;
using JetBrains.Annotations;
@@ -12,6 +14,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Game.Audio;
+using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.Rulesets.Scoring;
using osuTK.Graphics;
@@ -26,12 +29,22 @@ namespace osu.Game.Skinning
[CanBeNull]
protected IResourceStore Samples;
+ ///
+ /// Whether texture for the keys exists.
+ /// Used to determine if the mania ruleset is skinned.
+ ///
+ private readonly Lazy hasKeyTexture;
+
+ protected virtual bool AllowManiaSkin => hasKeyTexture.Value;
+
public new LegacySkinConfiguration Configuration
{
get => base.Configuration as LegacySkinConfiguration;
set => base.Configuration = value;
}
+ private readonly Dictionary maniaConfigurations = new Dictionary();
+
public LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager)
: this(skin, new LegacySkinResourceStore(skin, storage), audioManager, "skin.ini")
{
@@ -40,15 +53,26 @@ namespace osu.Game.Skinning
protected LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager, string filename)
: base(skin)
{
- Stream stream = storage?.GetStream(filename);
-
- if (stream != null)
+ using (var stream = storage?.GetStream(filename))
{
- using (LineBufferedReader reader = new LineBufferedReader(stream))
- Configuration = new LegacySkinDecoder().Decode(reader);
+ if (stream != null)
+ {
+ using (LineBufferedReader reader = new LineBufferedReader(stream, true))
+ Configuration = new LegacySkinDecoder().Decode(reader);
+
+ stream.Seek(0, SeekOrigin.Begin);
+
+ using (LineBufferedReader reader = new LineBufferedReader(stream))
+ {
+ var maniaList = new LegacyManiaSkinDecoder().Decode(reader);
+
+ foreach (var config in maniaList)
+ maniaConfigurations[config.Keys] = config;
+ }
+ }
+ else
+ Configuration = new LegacySkinConfiguration { LegacyVersion = LegacySkinConfiguration.LATEST_VERSION };
}
- else
- Configuration = new LegacySkinConfiguration { LegacyVersion = LegacySkinConfiguration.LATEST_VERSION };
if (storage != null)
{
@@ -61,6 +85,10 @@ namespace osu.Game.Skinning
(storage as ResourceStore)?.AddExtension("ogg");
}
+
+ // todo: this shouldn't really be duplicated here (from ManiaLegacySkinTransformer). we need to come up with a better solution.
+ hasKeyTexture = new Lazy(() => this.GetAnimation(
+ lookupForMania(new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true, true) != null);
}
protected override void Dispose(bool isDisposing)
@@ -85,7 +113,7 @@ namespace osu.Game.Skinning
break;
default:
- return SkinUtils.As(getCustomColour(colour.ToString()));
+ return SkinUtils.As(getCustomColour(Configuration, colour.ToString()));
}
break;
@@ -103,7 +131,17 @@ namespace osu.Game.Skinning
break;
case SkinCustomColourLookup customColour:
- return SkinUtils.As(getCustomColour(customColour.Lookup.ToString()));
+ return SkinUtils.As(getCustomColour(Configuration, customColour.Lookup.ToString()));
+
+ case LegacyManiaSkinConfigurationLookup maniaLookup:
+ if (!AllowManiaSkin)
+ return null;
+
+ var result = lookupForMania(maniaLookup);
+ if (result != null)
+ return result;
+
+ break;
default:
// handles lookups like GlobalSkinConfiguration
@@ -132,7 +170,50 @@ namespace osu.Game.Skinning
return null;
}
- private IBindable getCustomColour(string lookup) => Configuration.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null;
+ private IBindable lookupForMania(LegacyManiaSkinConfigurationLookup maniaLookup)
+ {
+ if (!maniaConfigurations.TryGetValue(maniaLookup.Keys, out var existing))
+ maniaConfigurations[maniaLookup.Keys] = existing = new LegacyManiaSkinConfiguration(maniaLookup.Keys);
+
+ switch (maniaLookup.Lookup)
+ {
+ case LegacyManiaSkinConfigurationLookups.ColumnWidth:
+ Debug.Assert(maniaLookup.TargetColumn != null);
+ return SkinUtils.As(new Bindable(existing.ColumnWidth[maniaLookup.TargetColumn.Value]));
+
+ case LegacyManiaSkinConfigurationLookups.ColumnSpacing:
+ Debug.Assert(maniaLookup.TargetColumn != null);
+ return SkinUtils.As(new Bindable(existing.ColumnSpacing[maniaLookup.TargetColumn.Value]));
+
+ case LegacyManiaSkinConfigurationLookups.HitPosition:
+ return SkinUtils.As(new Bindable(existing.HitPosition));
+
+ case LegacyManiaSkinConfigurationLookups.LightPosition:
+ return SkinUtils.As(new Bindable(existing.LightPosition));
+
+ case LegacyManiaSkinConfigurationLookups.ShowJudgementLine:
+ return SkinUtils.As(new Bindable(existing.ShowJudgementLine));
+
+ case LegacyManiaSkinConfigurationLookups.ExplosionScale:
+ Debug.Assert(maniaLookup.TargetColumn != null);
+
+ if (GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value < 2.5m)
+ return SkinUtils.As(new Bindable(1));
+
+ if (existing.ExplosionWidth[maniaLookup.TargetColumn.Value] != 0)
+ return SkinUtils.As(new Bindable(existing.ExplosionWidth[maniaLookup.TargetColumn.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE));
+
+ return SkinUtils.As(new Bindable(existing.ColumnWidth[maniaLookup.TargetColumn.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE));
+
+ case LegacyManiaSkinConfigurationLookups.ColumnLineColour:
+ return SkinUtils.As(getCustomColour(existing, "ColourColumnLine"));
+ }
+
+ return null;
+ }
+
+ private IBindable getCustomColour(IHasCustomColours source, string lookup)
+ => source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null;
public override Drawable GetDrawableComponent(ISkinComponent component)
{
diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs
index 52328d43b2..9bfde4fdcb 100644
--- a/osu.Game/Skinning/LegacySkinExtensions.cs
+++ b/osu.Game/Skinning/LegacySkinExtensions.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Sprites;
@@ -12,7 +13,8 @@ namespace osu.Game.Skinning
{
public static class LegacySkinExtensions
{
- public static Drawable GetAnimation(this ISkin source, string componentName, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-")
+ public static Drawable GetAnimation(this ISkin source, string componentName, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-",
+ bool startAtCurrentTime = false, double? frameLength = null)
{
Texture texture;
@@ -22,10 +24,10 @@ namespace osu.Game.Skinning
if (textures.Length > 0)
{
- var animation = new TextureAnimation
+ var animation = new SkinnableTextureAnimation(startAtCurrentTime)
{
- DefaultFrameLength = getFrameLength(source, applyConfigFrameRate, textures),
- Repeat = looping,
+ DefaultFrameLength = frameLength ?? getFrameLength(source, applyConfigFrameRate, textures),
+ Loop = looping,
};
foreach (var t in textures)
@@ -53,6 +55,28 @@ namespace osu.Game.Skinning
}
}
+ public class SkinnableTextureAnimation : TextureAnimation
+ {
+ [Resolved(canBeNull: true)]
+ private IAnimationTimeReference timeReference { get; set; }
+
+ public SkinnableTextureAnimation(bool startAtCurrentTime = true)
+ : base(startAtCurrentTime)
+ {
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ if (timeReference != null)
+ {
+ Clock = timeReference.Clock;
+ PlaybackPosition = timeReference.AnimationStartTime - timeReference.Clock.CurrentTime;
+ }
+ }
+ }
+
private const double default_frame_time = 1000 / 60d;
private static double getFrameLength(ISkin source, bool applyConfigFrameRate, Texture[] textures)
diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs
index fda031e6cb..0f0d3da5aa 100644
--- a/osu.Game/Skinning/SkinnableDrawable.cs
+++ b/osu.Game/Skinning/SkinnableDrawable.cs
@@ -18,6 +18,12 @@ namespace osu.Game.Skinning
///
public Drawable Drawable { get; private set; }
+ public new Axes AutoSizeAxes
+ {
+ get => base.AutoSizeAxes;
+ set => base.AutoSizeAxes = value;
+ }
+
private readonly ISkinComponent component;
private readonly ConfineMode confineMode;
@@ -92,20 +98,13 @@ namespace osu.Game.Skinning
switch (confineMode)
{
- case ConfineMode.NoScaling:
- return;
-
- case ConfineMode.ScaleDownToFit:
- if (Drawable.DrawSize.X <= DrawSize.X && Drawable.DrawSize.Y <= DrawSize.Y)
- return;
-
+ case ConfineMode.ScaleToFit:
+ Drawable.RelativeSizeAxes = Axes.Both;
+ Drawable.Size = Vector2.One;
+ Drawable.Scale = Vector2.One;
+ Drawable.FillMode = FillMode.Fit;
break;
}
-
- Drawable.RelativeSizeAxes = Axes.Both;
- Drawable.Size = Vector2.One;
- Drawable.Scale = Vector2.One;
- Drawable.FillMode = FillMode.Fit;
}
finally
{
@@ -121,7 +120,6 @@ namespace osu.Game.Skinning
/// Don't apply any scaling. This allows the user element to be of any size, exceeding specified bounds.
///
NoScaling,
- ScaleDownToFit,
ScaleToFit,
}
}
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs
index bc6e01a729..c4d796e30b 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs
@@ -50,7 +50,7 @@ namespace osu.Game.Storyboards.Drawables
AddInternal(Content = new Container
{
- Size = new Vector2(640, 480),
+ RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs
index eabb78bac5..72e52f6106 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs
@@ -108,7 +108,7 @@ namespace osu.Game.Storyboards.Drawables
Animation = animation;
Origin = animation.Origin;
Position = animation.InitialPosition;
- Repeat = animation.LoopType == AnimationLoopType.LoopForever;
+ Loop = animation.LoopType == AnimationLoopType.LoopForever;
LifetimeStart = animation.StartTime;
LifetimeEnd = animation.EndTime;
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs
index def4eed2ca..2ada83c3b4 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs
@@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers;
namespace osu.Game.Storyboards.Drawables
{
- public class DrawableStoryboardLayer : LifetimeManagementContainer
+ public class DrawableStoryboardLayer : CompositeDrawable
{
public StoryboardLayer Layer { get; }
public bool Enabled;
@@ -23,17 +23,34 @@ namespace osu.Game.Storyboards.Drawables
Origin = Anchor.Centre;
Enabled = layer.VisibleWhenPassing;
Masking = layer.Masking;
+
+ InternalChild = new LayerElementContainer(layer);
}
- [BackgroundDependencyLoader]
- private void load(CancellationToken? cancellationToken)
+ private class LayerElementContainer : LifetimeManagementContainer
{
- foreach (var element in Layer.Elements)
- {
- cancellationToken?.ThrowIfCancellationRequested();
+ private readonly StoryboardLayer storyboardLayer;
- if (element.IsDrawable)
- AddInternal(element.CreateDrawable());
+ public LayerElementContainer(StoryboardLayer layer)
+ {
+ storyboardLayer = layer;
+
+ Width = 640;
+ Height = 480;
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(CancellationToken? cancellationToken)
+ {
+ foreach (var element in storyboardLayer.Elements)
+ {
+ cancellationToken?.ThrowIfCancellationRequested();
+
+ if (element.IsDrawable)
+ AddInternal(element.CreateDrawable());
+ }
}
}
}
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs
index 00df388d09..2e7b66ea4f 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs
@@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Video;
-using osu.Framework.Timing;
using osu.Game.Beatmaps;
namespace osu.Game.Storyboards.Drawables
@@ -16,7 +15,7 @@ namespace osu.Game.Storyboards.Drawables
public class DrawableStoryboardVideo : CompositeDrawable
{
public readonly StoryboardVideo Video;
- private VideoSprite videoSprite;
+ private Video video;
public override bool RemoveWhenNotAlive => false;
@@ -40,14 +39,14 @@ namespace osu.Game.Storyboards.Drawables
if (stream == null)
return;
- InternalChild = videoSprite = new VideoSprite(stream, false)
+ InternalChild = video = new Video(stream, false)
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
- Clock = new FramedOffsetClock(Clock) { Offset = -Video.StartTime }
+ PlaybackPosition = Video.StartTime
};
}
@@ -55,8 +54,10 @@ namespace osu.Game.Storyboards.Drawables
{
base.LoadComplete();
- using (videoSprite.BeginAbsoluteSequence(0))
- videoSprite.FadeIn(500);
+ if (video == null) return;
+
+ using (video.BeginAbsoluteSequence(0))
+ video.FadeIn(500);
}
}
}
diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs
index 0da3ae7f87..64f4d7b95b 100644
--- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs
@@ -26,16 +26,16 @@ namespace osu.Game.Tests.Visual
protected OsuManualInputManagerTestScene()
{
+ MenuCursorContainer cursorContainer;
+
base.Content.AddRange(new Drawable[]
{
InputManager = new ManualInputManager
{
UseParentInput = true,
Child = new GlobalActionContainer(null)
- {
- RelativeSizeAxes = Axes.Both,
- Child = content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }
- },
+ .WithChild((cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both })
+ .WithChild(content = new OsuTooltipContainer(cursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }))
},
new Container
{
diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs
index 7a5328d30c..d0113b3096 100644
--- a/osu.Game/Tests/Visual/SkinnableTestScene.cs
+++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
+using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Skinning;
using osuTK;
@@ -47,16 +48,18 @@ namespace osu.Game.Tests.Visual
{
createdDrawables.Clear();
- Cell(0).Child = createProvider(null, creationFunction);
- Cell(1).Child = createProvider(metricsSkin, creationFunction);
- Cell(2).Child = createProvider(defaultSkin, creationFunction);
- Cell(3).Child = createProvider(specialSkin, creationFunction);
- Cell(4).Child = createProvider(oldSkin, creationFunction);
+ var beatmap = CreateBeatmapForSkinProvider();
+
+ Cell(0).Child = createProvider(null, creationFunction, beatmap);
+ Cell(1).Child = createProvider(metricsSkin, creationFunction, beatmap);
+ Cell(2).Child = createProvider(defaultSkin, creationFunction, beatmap);
+ Cell(3).Child = createProvider(specialSkin, creationFunction, beatmap);
+ Cell(4).Child = createProvider(oldSkin, creationFunction, beatmap);
}
protected IEnumerable CreatedDrawables => createdDrawables;
- private Drawable createProvider(Skin skin, Func creationFunction)
+ private Drawable createProvider(Skin skin, Func creationFunction, IBeatmap beatmap)
{
var created = creationFunction();
createdDrawables.Add(created);
@@ -100,7 +103,7 @@ namespace osu.Game.Tests.Visual
{
new OutlineBox { Alpha = autoSize ? 1 : 0 },
mainProvider.WithChild(
- new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider))
+ new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider, beatmap))
{
Child = created,
RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None,
@@ -113,6 +116,8 @@ namespace osu.Game.Tests.Visual
};
}
+ protected virtual IBeatmap CreateBeatmapForSkinProvider() => CreateWorkingBeatmap(Ruleset.Value).GetPlayableBeatmap(Ruleset.Value);
+
private class OutlineBox : CompositeDrawable
{
public OutlineBox()
diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs
index 2a6f7844a2..f8bb8f4c6a 100644
--- a/osu.Game/Users/User.cs
+++ b/osu.Game/Users/User.cs
@@ -173,8 +173,27 @@ namespace osu.Game.Users
public int Available;
}
+ private UserStatistics statistics;
+
[JsonProperty(@"statistics")]
- public UserStatistics Statistics;
+ public UserStatistics Statistics
+ {
+ get => statistics ??= new UserStatistics();
+ set
+ {
+ if (statistics != null)
+ // we may already have rank history populated
+ value.RankHistory = statistics.RankHistory;
+
+ statistics = value;
+ }
+ }
+
+ [JsonProperty(@"rankHistory")]
+ private RankHistoryData rankHistory
+ {
+ set => statistics.RankHistory = value;
+ }
public class RankHistoryData
{
@@ -185,12 +204,6 @@ namespace osu.Game.Users
public int[] Data;
}
- [JsonProperty(@"rankHistory")]
- private RankHistoryData rankHistory
- {
- set => Statistics.RankHistory = value;
- }
-
[JsonProperty("badges")]
public Badge[] Badges;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 3894c06994..8d31fbf280 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -22,8 +22,8 @@
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 9cc9792ecf..e2b98720be 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,8 +70,8 @@
-
-
+
+
@@ -79,7 +79,7 @@
-
+