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 b147fdd05b..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/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/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index 1197d6f9e6..9bfff209d5 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -39,10 +39,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
{
@@ -381,7 +386,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/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/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/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/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 5ee17aeea2..4b1c2ec256 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
@@ -4,9 +4,13 @@
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
{
@@ -17,21 +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.ChildrenOfType().First().Breaks.First().StartTime));
- AddUntilStep("wait for seek to complete", () =>
- Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= Player.BreakOverlay.Breaks.First().StartTime);
+ 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/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/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/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/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/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 5b5802fa9d..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 .
///
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/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 c356dd246d..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,7 +42,7 @@ 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();
@@ -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/Player.cs b/osu.Game/Screens/Play/Player.cs
index 63ec3b0d2d..4597ae760c 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -176,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);
@@ -214,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.
@@ -637,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;
@@ -649,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.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));
- }));
- }
- });
+ 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