diff --git a/osu.Android.props b/osu.Android.props
index 723844155f..25942863c5 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
new file mode 100644
index 0000000000..a48ecb9b79
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
@@ -0,0 +1,132 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osu.Game.Rulesets.Catch.Skinning;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ public class TestSceneHyperDashColouring : OsuTestScene
+ {
+ [Resolved]
+ private SkinManager skins { get; set; }
+
+ [Test]
+ public void TestDefaultFruitColour()
+ {
+ var skin = new TestSkin();
+
+ checkHyperDashFruitColour(skin, Catcher.DEFAULT_HYPER_DASH_COLOUR);
+ }
+
+ [Test]
+ public void TestCustomFruitColour()
+ {
+ var skin = new TestSkin
+ {
+ HyperDashFruitColour = Color4.Cyan
+ };
+
+ checkHyperDashFruitColour(skin, skin.HyperDashFruitColour);
+ }
+
+ [Test]
+ public void TestCustomFruitColourPriority()
+ {
+ var skin = new TestSkin
+ {
+ HyperDashColour = Color4.Goldenrod,
+ HyperDashFruitColour = Color4.Cyan
+ };
+
+ checkHyperDashFruitColour(skin, skin.HyperDashFruitColour);
+ }
+
+ [Test]
+ public void TestFruitColourFallback()
+ {
+ var skin = new TestSkin
+ {
+ HyperDashColour = Color4.Goldenrod
+ };
+
+ checkHyperDashFruitColour(skin, skin.HyperDashColour);
+ }
+
+ private void checkHyperDashFruitColour(ISkin skin, Color4 expectedColour)
+ {
+ DrawableFruit drawableFruit = null;
+
+ AddStep("create hyper-dash fruit", () =>
+ {
+ var fruit = new Fruit { HyperDashTarget = new Banana() };
+ fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ Child = setupSkinHierarchy(drawableFruit = new DrawableFruit(fruit)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(4f),
+ }, skin);
+ });
+
+ AddAssert("hyper-dash colour is correct", () => checkLegacyFruitHyperDashColour(drawableFruit, expectedColour));
+ }
+
+ private Drawable setupSkinHierarchy(Drawable child, ISkin skin)
+ {
+ var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.Info));
+ var testSkinProvider = new SkinProvidingContainer(skin);
+ var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider));
+
+ return legacySkinProvider
+ .WithChild(testSkinProvider
+ .WithChild(legacySkinTransformer
+ .WithChild(child)));
+ }
+
+ private bool checkLegacyFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour) =>
+ fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Any(c => c.Colour == expectedColour);
+
+ private class TestSkin : LegacySkin
+ {
+ public Color4 HyperDashColour
+ {
+ get => Configuration.CustomColours[CatchSkinColour.HyperDash.ToString()];
+ set => Configuration.CustomColours[CatchSkinColour.HyperDash.ToString()] = value;
+ }
+
+ public Color4 HyperDashAfterImageColour
+ {
+ get => Configuration.CustomColours[CatchSkinColour.HyperDashAfterImage.ToString()];
+ set => Configuration.CustomColours[CatchSkinColour.HyperDashAfterImage.ToString()] = value;
+ }
+
+ public Color4 HyperDashFruitColour
+ {
+ get => Configuration.CustomColours[CatchSkinColour.HyperDashFruit.ToString()];
+ set => Configuration.CustomColours[CatchSkinColour.HyperDashFruit.ToString()] = value;
+ }
+
+ public TestSkin()
+ : base(new SkinInfo(), null, null, string.Empty)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index 4d9dbbbc5f..d99325ff87 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -71,8 +71,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 = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f;
return new Skill[]
{
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
index 1ef235f764..16414261a5 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
@@ -9,17 +9,26 @@ using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Play;
using osuTK;
namespace osu.Game.Rulesets.Catch.Mods
{
- public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset
+ public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset, IApplicableToPlayer
{
public override string Description => @"Use the mouse to control the catcher.";
+ private DrawableRuleset drawableRuleset;
+
public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
- drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield));
+ this.drawableRuleset = drawableRuleset;
+ }
+
+ public void ApplyToPlayer(Player player)
+ {
+ if (!drawableRuleset.HasReplayLoaded.Value)
+ drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield));
}
private class MouseInputHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs
index 5797588ded..7ac9f11ad6 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs
@@ -7,6 +7,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK.Graphics;
@@ -67,7 +68,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- BorderColour = Color4.Red,
+ BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
BorderThickness = 12f * RADIUS_ADJUST,
Children = new Drawable[]
{
@@ -77,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
Alpha = 0.3f,
Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
- Colour = Color4.Red,
+ Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
}
}
});
diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
index 65e6e6f209..4a87eb95e7 100644
--- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
@@ -65,6 +65,15 @@ namespace osu.Game.Rulesets.Catch.Skinning
public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
- public IBindable GetConfig(TLookup lookup) => source.GetConfig(lookup);
+ public IBindable GetConfig(TLookup lookup)
+ {
+ switch (lookup)
+ {
+ case CatchSkinColour colour:
+ return source.GetConfig(new SkinCustomColourLookup(colour));
+ }
+
+ return source.GetConfig(lookup);
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs b/osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs
new file mode 100644
index 0000000000..4506111498
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Catch.Skinning
+{
+ public enum CatchSkinColour
+ {
+ ///
+ /// The colour to be used for the catcher while in hyper-dashing state.
+ ///
+ HyperDash,
+
+ ///
+ /// The colour to be used for fruits that grant the catcher the ability to hyper-dash.
+ ///
+ HyperDashFruit,
+
+ ///
+ /// The colour to be used for the "exploding" catcher sprite on beginning of hyper-dashing.
+ ///
+ HyperDashAfterImage,
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs
index 25ee0811d0..5be54d3882 100644
--- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs
@@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Skinning;
using osuTK;
@@ -55,14 +56,16 @@ namespace osu.Game.Rulesets.Catch.Skinning
{
var hyperDash = new Sprite
{
- Texture = skin.GetTexture(lookupName),
- Colour = Color4.Red,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Blending = BlendingParameters.Additive,
Depth = 1,
Alpha = 0.7f,
- Scale = new Vector2(1.2f)
+ Scale = new Vector2(1.2f),
+ Texture = skin.GetTexture(lookupName),
+ Colour = skin.GetConfig(CatchSkinColour.HyperDashFruit)?.Value ??
+ skin.GetConfig(CatchSkinColour.HyperDash)?.Value ??
+ Catcher.DEFAULT_HYPER_DASH_COLOUR,
};
AddInternal(hyperDash);
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index 7c815370c8..daf9456919 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Catch.UI
{
public class Catcher : Container, IKeyBindingHandler
{
+ public static readonly Color4 DEFAULT_HYPER_DASH_COLOUR = Color4.Red;
+
///
/// Whether we are hyper-dashing or not.
///
@@ -42,11 +44,6 @@ namespace osu.Game.Rulesets.Catch.UI
///
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) * allowed_catch_range;
-
protected bool Dashing
{
get => dashing;
@@ -77,6 +74,11 @@ namespace osu.Game.Rulesets.Catch.UI
}
}
+ ///
+ /// Width of the area that can be used to attempt catches during gameplay.
+ ///
+ private readonly float catchWidth;
+
private Container caughtFruit;
private CatcherSprite catcherIdle;
@@ -104,7 +106,9 @@ namespace osu.Game.Rulesets.Catch.UI
Size = new Vector2(CatcherArea.CATCHER_SIZE);
if (difficulty != null)
- Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
+ Scale = calculateScale(difficulty);
+
+ catchWidth = CalculateCatchWidth(Scale);
}
[BackgroundDependencyLoader]
@@ -137,6 +141,26 @@ namespace osu.Game.Rulesets.Catch.UI
updateCatcher();
}
+ ///
+ /// Calculates the scale of the catcher based off the provided beatmap difficulty.
+ ///
+ private static Vector2 calculateScale(BeatmapDifficulty difficulty)
+ => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
+
+ ///
+ /// Calculates the width of the area used for attempting catches in gameplay.
+ ///
+ /// The scale of the catcher.
+ internal static float CalculateCatchWidth(Vector2 scale)
+ => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * allowed_catch_range;
+
+ ///
+ /// Calculates the width of the area used for attempting catches in gameplay.
+ ///
+ /// The beatmap difficulty.
+ internal static float CalculateCatchWidth(BeatmapDifficulty difficulty)
+ => CalculateCatchWidth(calculateScale(difficulty));
+
///
/// Add a caught fruit to the catcher's stack.
///
@@ -175,7 +199,7 @@ namespace osu.Game.Rulesets.Catch.UI
/// Whether the catch is possible.
public bool AttemptCatch(CatchHitObject fruit)
{
- var halfCatchWidth = CatchWidth * 0.5f;
+ var halfCatchWidth = catchWidth * 0.5f;
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-key1@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-key1@2x.png
new file mode 100644
index 0000000000..aa681f6f22
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-key1@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
new file mode 100644
index 0000000000..56564776b3
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
@@ -0,0 +1,6 @@
+[General]
+Version: 2.4
+
+[Mania]
+Keys: 4
+ColumnLineWidth: 3,1,3,1,1
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs
index d6bacbe59e..bde323f187 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
- Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 0), _ => new DefaultColumnBackground())
+ Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 1), _ => new DefaultColumnBackground())
{
RelativeSizeAxes = Axes.Both
}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
index 7b0cf40d45..0d13b85901 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
@@ -27,7 +27,6 @@ namespace osu.Game.Rulesets.Mania.Tests
private const double time_after_tail = 5250;
private List judgementResults;
- private bool allJudgedFired;
///
/// -----[ ]-----
@@ -283,20 +282,15 @@ namespace osu.Game.Rulesets.Mania.Tests
{
if (currentPlayer == p) judgementResults.Add(result);
};
- p.ScoreProcessor.AllJudged += () =>
- {
- if (currentPlayer == p) allJudgedFired = true;
- };
};
LoadScreen(currentPlayer = p);
- allJudgedFired = false;
judgementResults = new List();
});
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
- AddUntilStep("Wait for all judged", () => allJudgedFired);
+ AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
}
private class ScoreAccessibleReplayPlayer : ReplayPlayer
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index d904474815..4187e39b43 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
{
TargetColumns = (int)Math.Max(1, roundedCircleSize);
- if (TargetColumns >= 10)
+ if (TargetColumns > ManiaRuleset.MAX_STAGE_KEYS)
{
TargetColumns /= 2;
Dual = true;
diff --git a/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs b/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs
new file mode 100644
index 0000000000..8d39e08b26
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs
@@ -0,0 +1,64 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Input.Bindings;
+
+namespace osu.Game.Rulesets.Mania
+{
+ public class DualStageVariantGenerator
+ {
+ private readonly int singleStageVariant;
+ private readonly InputKey[] stage1LeftKeys;
+ private readonly InputKey[] stage1RightKeys;
+ private readonly InputKey[] stage2LeftKeys;
+ private readonly InputKey[] stage2RightKeys;
+
+ public DualStageVariantGenerator(int singleStageVariant)
+ {
+ this.singleStageVariant = singleStageVariant;
+
+ // 10K is special because it expands towards the centre of the keyboard (VM/BN), rather than towards the edges of the keyboard.
+ if (singleStageVariant == 10)
+ {
+ stage1LeftKeys = new[] { InputKey.Q, InputKey.W, InputKey.E, InputKey.R, InputKey.V };
+ stage1RightKeys = new[] { InputKey.M, InputKey.I, InputKey.O, InputKey.P, InputKey.BracketLeft };
+
+ stage2LeftKeys = new[] { InputKey.S, InputKey.D, InputKey.F, InputKey.G, InputKey.B };
+ stage2RightKeys = new[] { InputKey.N, InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon };
+ }
+ else
+ {
+ stage1LeftKeys = new[] { InputKey.Q, InputKey.W, InputKey.E, InputKey.R };
+ stage1RightKeys = new[] { InputKey.I, InputKey.O, InputKey.P, InputKey.BracketLeft };
+
+ stage2LeftKeys = new[] { InputKey.S, InputKey.D, InputKey.F, InputKey.G };
+ stage2RightKeys = new[] { InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon };
+ }
+ }
+
+ public IEnumerable GenerateMappings()
+ {
+ var stage1Bindings = new VariantMappingGenerator
+ {
+ LeftKeys = stage1LeftKeys,
+ RightKeys = stage1RightKeys,
+ SpecialKey = InputKey.V,
+ SpecialAction = ManiaAction.Special1,
+ NormalActionStart = ManiaAction.Key1
+ }.GenerateKeyBindingsFor(singleStageVariant, out var nextNormal);
+
+ var stage2Bindings = new VariantMappingGenerator
+ {
+ LeftKeys = stage2LeftKeys,
+ RightKeys = stage2RightKeys,
+ SpecialKey = InputKey.B,
+ SpecialAction = ManiaAction.Special2,
+ NormalActionStart = nextNormal
+ }.GenerateKeyBindingsFor(singleStageVariant, out _);
+
+ return stage1Bindings.Concat(stage2Bindings);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs
index 292990fd7e..186fc4b15d 100644
--- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs
+++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs
@@ -78,5 +78,11 @@ namespace osu.Game.Rulesets.Mania
[Description("Key 18")]
Key18,
+
+ [Description("Key 19")]
+ Key19,
+
+ [Description("Key 20")]
+ Key20,
}
}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 2bd88fee90..a37aaa8cc4 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -35,6 +35,11 @@ namespace osu.Game.Rulesets.Mania
{
public class ManiaRuleset : Ruleset, ILegacyRuleset
{
+ ///
+ /// The maximum number of supported keys in a single stage.
+ ///
+ public const int MAX_STAGE_KEYS = 10;
+
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableManiaRuleset(this, beatmap, mods);
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
@@ -202,6 +207,7 @@ namespace osu.Game.Rulesets.Mania
new ManiaModKey7(),
new ManiaModKey8(),
new ManiaModKey9(),
+ new ManiaModKey10(),
new ManiaModKey1(),
new ManiaModKey2(),
new ManiaModKey3()),
@@ -250,9 +256,9 @@ namespace osu.Game.Rulesets.Mania
{
get
{
- for (int i = 1; i <= 9; i++)
+ for (int i = 1; i <= MAX_STAGE_KEYS; i++)
yield return (int)PlayfieldType.Single + i;
- for (int i = 2; i <= 18; i += 2)
+ for (int i = 2; i <= MAX_STAGE_KEYS * 2; i += 2)
yield return (int)PlayfieldType.Dual + i;
}
}
@@ -262,73 +268,10 @@ namespace osu.Game.Rulesets.Mania
switch (getPlayfieldType(variant))
{
case PlayfieldType.Single:
- return new VariantMappingGenerator
- {
- LeftKeys = new[]
- {
- InputKey.A,
- InputKey.S,
- InputKey.D,
- InputKey.F
- },
- RightKeys = new[]
- {
- InputKey.J,
- InputKey.K,
- InputKey.L,
- InputKey.Semicolon
- },
- SpecialKey = InputKey.Space,
- SpecialAction = ManiaAction.Special1,
- NormalActionStart = ManiaAction.Key1,
- }.GenerateKeyBindingsFor(variant, out _);
+ return new SingleStageVariantGenerator(variant).GenerateMappings();
case PlayfieldType.Dual:
- int keys = getDualStageKeyCount(variant);
-
- var stage1Bindings = new VariantMappingGenerator
- {
- LeftKeys = new[]
- {
- InputKey.Q,
- InputKey.W,
- InputKey.E,
- InputKey.R,
- },
- RightKeys = new[]
- {
- InputKey.X,
- InputKey.C,
- InputKey.V,
- InputKey.B
- },
- SpecialKey = InputKey.S,
- SpecialAction = ManiaAction.Special1,
- NormalActionStart = ManiaAction.Key1
- }.GenerateKeyBindingsFor(keys, out var nextNormal);
-
- var stage2Bindings = new VariantMappingGenerator
- {
- LeftKeys = new[]
- {
- InputKey.Number7,
- InputKey.Number8,
- InputKey.Number9,
- InputKey.Number0
- },
- RightKeys = new[]
- {
- InputKey.K,
- InputKey.L,
- InputKey.Semicolon,
- InputKey.Quote
- },
- SpecialKey = InputKey.I,
- SpecialAction = ManiaAction.Special2,
- NormalActionStart = nextNormal
- }.GenerateKeyBindingsFor(keys, out _);
-
- return stage1Bindings.Concat(stage2Bindings);
+ return new DualStageVariantGenerator(getDualStageKeyCount(variant)).GenerateMappings();
}
return Array.Empty();
@@ -364,59 +307,6 @@ namespace osu.Game.Rulesets.Mania
{
return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderByDescending(i => i).First(v => variant >= v);
}
-
- private class VariantMappingGenerator
- {
- ///
- /// All the s available to the left hand.
- ///
- public InputKey[] LeftKeys;
-
- ///
- /// All the s available to the right hand.
- ///
- public InputKey[] RightKeys;
-
- ///
- /// The for the special key.
- ///
- public InputKey SpecialKey;
-
- ///
- /// The at which the normal columns should begin.
- ///
- public ManiaAction NormalActionStart;
-
- ///
- /// The for the special column.
- ///
- public ManiaAction SpecialAction;
-
- ///
- /// Generates a list of s for a specific number of columns.
- ///
- /// The number of columns that need to be bound.
- /// The next to use for normal columns.
- /// The keybindings.
- public IEnumerable GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction)
- {
- ManiaAction currentNormalAction = NormalActionStart;
-
- var bindings = new List();
-
- for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++)
- bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++));
-
- if (columns % 2 == 1)
- bindings.Add(new KeyBinding(SpecialKey, SpecialAction));
-
- for (int i = 0; i < columns / 2; i++)
- bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++));
-
- nextNormalAction = currentNormalAction;
- return bindings;
- }
- }
}
public enum PlayfieldType
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs
new file mode 100644
index 0000000000..684370fc3d
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs
@@ -0,0 +1,13 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Mania.Mods
+{
+ public class ManiaModKey10 : ManiaKeyMod
+ {
+ public override int KeyCount => 10;
+ public override string Name => "Ten Keys";
+ public override string Acronym => "10K";
+ public override string Description => @"Play with ten keys.";
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs b/osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs
new file mode 100644
index 0000000000..2069329d9a
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/SingleStageVariantGenerator.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.
+
+using System.Collections.Generic;
+using osu.Framework.Input.Bindings;
+
+namespace osu.Game.Rulesets.Mania
+{
+ public class SingleStageVariantGenerator
+ {
+ private readonly int variant;
+ private readonly InputKey[] leftKeys;
+ private readonly InputKey[] rightKeys;
+
+ public SingleStageVariantGenerator(int variant)
+ {
+ this.variant = variant;
+
+ // 10K is special because it expands towards the centre of the keyboard (V/N), rather than towards the edges of the keyboard.
+ if (variant == 10)
+ {
+ leftKeys = new[] { InputKey.A, InputKey.S, InputKey.D, InputKey.F, InputKey.V };
+ rightKeys = new[] { InputKey.N, InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon };
+ }
+ else
+ {
+ leftKeys = new[] { InputKey.A, InputKey.S, InputKey.D, InputKey.F };
+ rightKeys = new[] { InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon };
+ }
+ }
+
+ public IEnumerable GenerateMappings() => new VariantMappingGenerator
+ {
+ LeftKeys = leftKeys,
+ RightKeys = rightKeys,
+ SpecialKey = InputKey.Space,
+ SpecialAction = ManiaAction.Special1,
+ NormalActionStart = ManiaAction.Key1,
+ }.GenerateKeyBindingsFor(variant, out _);
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs
index 6504321bb2..1a097405ac 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs
@@ -67,6 +67,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
{
RelativeSizeAxes = Axes.Y,
Width = leftLineWidth,
+ Scale = new Vector2(0.740f, 1),
Colour = lineColour,
Alpha = hasLeftLine ? 1 : 0
},
@@ -76,6 +77,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = rightLineWidth,
+ Scale = new Vector2(0.740f, 1),
Colour = lineColour,
Alpha = hasRightLine ? 1 : 0
},
diff --git a/osu.Game.Rulesets.Mania/VariantMappingGenerator.cs b/osu.Game.Rulesets.Mania/VariantMappingGenerator.cs
new file mode 100644
index 0000000000..878d1088a6
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/VariantMappingGenerator.cs
@@ -0,0 +1,61 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Framework.Input.Bindings;
+
+namespace osu.Game.Rulesets.Mania
+{
+ public class VariantMappingGenerator
+ {
+ ///
+ /// All the s available to the left hand.
+ ///
+ public InputKey[] LeftKeys;
+
+ ///
+ /// All the s available to the right hand.
+ ///
+ public InputKey[] RightKeys;
+
+ ///
+ /// The for the special key.
+ ///
+ public InputKey SpecialKey;
+
+ ///
+ /// The at which the normal columns should begin.
+ ///
+ public ManiaAction NormalActionStart;
+
+ ///
+ /// The for the special column.
+ ///
+ public ManiaAction SpecialAction;
+
+ ///
+ /// Generates a list of s for a specific number of columns.
+ ///
+ /// The number of columns that need to be bound.
+ /// The next to use for normal columns.
+ /// The keybindings.
+ public IEnumerable GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction)
+ {
+ ManiaAction currentNormalAction = NormalActionStart;
+
+ var bindings = new List();
+
+ for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++)
+ bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++));
+
+ if (columns % 2 == 1)
+ bindings.Add(new KeyBinding(SpecialKey, SpecialAction));
+
+ for (int i = 0; i < columns / 2; i++)
+ bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++));
+
+ nextNormalAction = currentNormalAction;
+ return bindings;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
new file mode 100644
index 0000000000..8ef2240c66
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.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 System.Collections.Generic;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests.Mods
+{
+ public class TestSceneOsuModHidden : ModTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
+
+ [Test]
+ public void TestDefaultBeatmapTest() => CreateModTest(new ModTestData
+ {
+ Mod = new OsuModHidden(),
+ Autoplay = true,
+ PassCondition = checkSomeHit
+ });
+
+ [Test]
+ public void FirstCircleAfterTwoSpinners() => CreateModTest(new ModTestData
+ {
+ Mod = new OsuModHidden(),
+ Autoplay = true,
+ Beatmap = new Beatmap
+ {
+ HitObjects = new List
+ {
+ new Spinner
+ {
+ Position = new Vector2(256, 192),
+ EndTime = 1000,
+ },
+ new Spinner
+ {
+ Position = new Vector2(256, 192),
+ StartTime = 1200,
+ EndTime = 2200,
+ },
+ new HitCircle
+ {
+ Position = new Vector2(300, 192),
+ StartTime = 3200,
+ },
+ new HitCircle
+ {
+ Position = new Vector2(384, 192),
+ StartTime = 4200,
+ }
+ }
+ },
+ PassCondition = checkSomeHit
+ });
+
+ [Test]
+ public void FirstSliderAfterTwoSpinners() => CreateModTest(new ModTestData
+ {
+ Mod = new OsuModHidden(),
+ Autoplay = true,
+ Beatmap = new Beatmap
+ {
+ HitObjects = new List
+ {
+ new Spinner
+ {
+ Position = new Vector2(256, 192),
+ EndTime = 1000,
+ },
+ new Spinner
+ {
+ Position = new Vector2(256, 192),
+ StartTime = 1200,
+ EndTime = 2200,
+ },
+ new Slider
+ {
+ StartTime = 3200,
+ Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), })
+ },
+ new Slider
+ {
+ StartTime = 5200,
+ Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), })
+ }
+ }
+ },
+ PassCondition = checkSomeHit
+ });
+
+ private bool checkSomeHit()
+ {
+ return Player.ScoreProcessor.JudgedHits >= 4;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs
index d6858f831e..a6c3be7e5a 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs
@@ -296,6 +296,44 @@ namespace osu.Game.Rulesets.Osu.Tests
addJudgementAssert(hitObjects[1], HitResult.Great);
}
+ [Test]
+ public void TestHitSliderHeadBeforeHitCircle()
+ {
+ const double time_circle = 1000;
+ const double time_slider = 1200;
+ Vector2 positionCircle = Vector2.Zero;
+ Vector2 positionSlider = new Vector2(80);
+
+ var hitObjects = new List
+ {
+ new TestHitCircle
+ {
+ StartTime = time_circle,
+ Position = positionCircle
+ },
+ new TestSlider
+ {
+ StartTime = time_slider,
+ Position = positionSlider,
+ Path = new SliderPath(PathType.Linear, new[]
+ {
+ Vector2.Zero,
+ new Vector2(25, 0),
+ })
+ }
+ };
+
+ performTest(hitObjects, new List
+ {
+ new OsuReplayFrame { Time = time_circle - 100, Position = positionSlider, Actions = { OsuAction.LeftButton } },
+ new OsuReplayFrame { Time = time_circle, Position = positionCircle, Actions = { OsuAction.RightButton } },
+ new OsuReplayFrame { Time = time_slider, Position = positionSlider, Actions = { OsuAction.LeftButton } },
+ });
+
+ addJudgementAssert(hitObjects[0], HitResult.Great);
+ addJudgementAssert(hitObjects[1], HitResult.Great);
+ }
+
private void addJudgementAssert(OsuHitObject hitObject, HitResult result)
{
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}",
@@ -316,7 +354,6 @@ namespace osu.Game.Rulesets.Osu.Tests
private ScoreAccessibleReplayPlayer currentPlayer;
private List judgementResults;
- private bool allJudgedFired;
private void performTest(List hitObjects, List frames)
{
@@ -342,20 +379,15 @@ namespace osu.Game.Rulesets.Osu.Tests
{
if (currentPlayer == p) judgementResults.Add(result);
};
- p.ScoreProcessor.AllJudged += () =>
- {
- if (currentPlayer == p) allJudgedFired = true;
- };
};
LoadScreen(currentPlayer = p);
- allJudgedFired = false;
judgementResults = new List();
});
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
- AddUntilStep("Wait for all judged", () => allJudgedFired);
+ AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
}
private class TestHitCircle : HitCircle
@@ -371,6 +403,9 @@ namespace osu.Game.Rulesets.Osu.Tests
{
HeadCircle.HitWindows = new TestHitWindows();
TailCircle.HitWindows = new TestHitWindows();
+
+ HeadCircle.HitWindows.SetDifficulty(0);
+ TailCircle.HitWindows.SetDifficulty(0);
};
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
index 67e1b77770..b0c2e56c3e 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
@@ -47,7 +47,6 @@ namespace osu.Game.Rulesets.Osu.Tests
private const double time_slider_end = 4000;
private List judgementResults;
- private bool allJudgedFired;
///
/// Scenario:
@@ -375,20 +374,15 @@ namespace osu.Game.Rulesets.Osu.Tests
{
if (currentPlayer == p) judgementResults.Add(result);
};
- p.ScoreProcessor.AllJudged += () =>
- {
- if (currentPlayer == p) allJudgedFired = true;
- };
};
LoadScreen(currentPlayer = p);
- allJudgedFired = false;
judgementResults = new List();
});
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
- AddUntilStep("Wait for all judged", () => allJudgedFired);
+ AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
}
private class ScoreAccessibleReplayPlayer : ReplayPlayer
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index 91a4e049e3..fdba03f260 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
@@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Mods
private const double fade_in_duration_multiplier = 0.4;
private const double fade_out_duration_multiplier = 0.3;
+ protected override bool IsFirstHideableObject(DrawableHitObject hitObject) => !(hitObject is DrawableSpinner);
+
public override void ApplyToDrawableHitObjects(IEnumerable drawables)
{
static void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index 9b0759d9d2..7b1941b7f9 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
@@ -11,11 +11,12 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Play;
using static osu.Game.Input.Handlers.ReplayInputHandler;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset
+ public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset, IApplicableToPlayer
{
public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
@@ -33,15 +34,30 @@ namespace osu.Game.Rulesets.Osu.Mods
private ReplayState state;
private double lastStateChangeTime;
+ private bool hasReplay;
+
public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
// grab the input manager for future use.
osuInputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
+ }
+
+ public void ApplyToPlayer(Player player)
+ {
+ if (osuInputManager.ReplayInputHandler != null)
+ {
+ hasReplay = true;
+ return;
+ }
+
osuInputManager.AllowUserPresses = false;
}
public void Update(Playfield playfield)
{
+ if (hasReplay)
+ return;
+
bool requiresHold = false;
bool requiresHit = false;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 522217a916..72502c02cd 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -125,7 +125,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return new DrawableSliderTail(slider, tail);
case SliderHeadCircle head:
- return new DrawableSliderHead(slider, head) { OnShake = Shake };
+ return new DrawableSliderHead(slider, head)
+ {
+ OnShake = Shake,
+ CheckHittable = (d, t) => CheckHittable?.Invoke(d, t) ?? true
+ };
case SliderTick tick:
return new DrawableSliderTick(tick) { Position = tick.Position - slider.Position };
diff --git a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs
index dfca2aff7b..8e4f81347d 100644
--- a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs
+++ b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs
@@ -1,16 +1,17 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Collections.Generic;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.UI
{
///
- /// Ensures that s are hit in-order.
+ /// Ensures that s are hit in-order. Affectionately known as "note lock".
/// If a is hit out of order:
///
/// - The hit is blocked if it occurred earlier than the previous 's start time.
@@ -36,13 +37,9 @@ namespace osu.Game.Rulesets.Osu.UI
{
DrawableHitObject blockingObject = null;
- // Find the last hitobject which blocks future hits.
- foreach (var obj in hitObjectContainer.AliveObjects)
+ foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
{
- if (obj == hitObject)
- break;
-
- if (drawableCanBlockFutureHits(obj))
+ if (hitObjectCanBlockFutureHits(obj))
blockingObject = obj;
}
@@ -54,74 +51,56 @@ namespace osu.Game.Rulesets.Osu.UI
// 1. The last blocking hitobject has been judged.
// 2. The current time is after the last hitobject's start time.
// Hits at exactly the same time as the blocking hitobject are allowed for maps that contain simultaneous hitobjects (e.g. /b/372245).
- if (blockingObject.Judged || time >= blockingObject.HitObject.StartTime)
- return true;
-
- return false;
+ return blockingObject.Judged || time >= blockingObject.HitObject.StartTime;
}
///
/// Handles a being hit to potentially miss all earlier s.
///
/// The that was hit.
- public void HandleHit(HitObject hitObject)
+ public void HandleHit(DrawableHitObject hitObject)
{
// Hitobjects which themselves don't block future hitobjects don't cause misses (e.g. slider ticks, spinners).
if (!hitObjectCanBlockFutureHits(hitObject))
return;
- double maximumTime = hitObject.StartTime;
+ if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset))
+ throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!");
- // Iterate through and apply miss results to all top-level and nested hitobjects which block future hits.
- foreach (var obj in hitObjectContainer.AliveObjects)
+ foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
{
- if (obj.Judged || obj.HitObject.StartTime >= maximumTime)
+ if (obj.Judged)
continue;
- if (hitObjectCanBlockFutureHits(obj.HitObject))
- applyMiss(obj);
-
- foreach (var nested in obj.NestedHitObjects)
- {
- if (nested.Judged || nested.HitObject.StartTime >= maximumTime)
- continue;
-
- if (hitObjectCanBlockFutureHits(nested.HitObject))
- applyMiss(nested);
- }
+ if (hitObjectCanBlockFutureHits(obj))
+ ((DrawableOsuHitObject)obj).MissForcefully();
}
-
- static void applyMiss(DrawableHitObject obj) => ((DrawableOsuHitObject)obj).MissForcefully();
- }
-
- ///
- /// Whether a blocks hits on future s until its start time is reached.
- ///
- ///
- /// This will ONLY match on top-most s.
- ///
- /// The to test.
- private static bool drawableCanBlockFutureHits(DrawableHitObject hitObject)
- {
- // Special considerations for slider tails aren't required since only top-most drawable hitobjects are being iterated over.
- return hitObject is DrawableHitCircle || hitObject is DrawableSlider;
}
///
/// Whether a blocks hits on future s until its start time is reached.
///
- ///
- /// This is more rigorous and may not match on top-most s as does.
- ///
/// The to test.
- private static bool hitObjectCanBlockFutureHits(HitObject hitObject)
- {
- // Unlike the above we will receive slider tails, but they do not block future hits.
- if (hitObject is SliderTailCircle)
- return false;
+ private static bool hitObjectCanBlockFutureHits(DrawableHitObject hitObject)
+ => hitObject is DrawableHitCircle;
- // All other hitcircles continue to block future hits.
- return hitObject is HitCircle;
+ private IEnumerable enumerateHitObjectsUpTo(double targetTime)
+ {
+ foreach (var obj in hitObjectContainer.AliveObjects)
+ {
+ if (obj.HitObject.StartTime >= targetTime)
+ yield break;
+
+ yield return obj;
+
+ foreach (var nestedObj in obj.NestedHitObjects)
+ {
+ if (nestedObj.HitObject.StartTime >= targetTime)
+ break;
+
+ yield return nestedObj;
+ }
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index 2f222f59b4..4b1a2ce43c 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.UI
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
{
// Hitobjects that block future hits should miss previous hitobjects if they're hit out-of-order.
- hitPolicy.HandleHit(result.HitObject);
+ hitPolicy.HandleHit(judgedObject);
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
return;
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/approachcircle@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/approachcircle@2x.png
new file mode 100644
index 0000000000..72ef665478
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/approachcircle@2x.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taikobigcircle@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taikobigcircle@2x.png
new file mode 100644
index 0000000000..440e5b55e5
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taikobigcircle@2x.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/approachcircle.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/approachcircle.png
new file mode 100755
index 0000000000..5aba688756
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/approachcircle.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/approachcircle.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/approachcircle.png
new file mode 100644
index 0000000000..56d6d34c1a
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/approachcircle.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikobigcircle@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikobigcircle@2x.png
new file mode 100644
index 0000000000..5d8b60da9e
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikobigcircle@2x.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TaikoSkinnableTestScene.cs
similarity index 92%
rename from osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs
rename to osu.Game.Rulesets.Taiko.Tests/Skinning/TaikoSkinnableTestScene.cs
index 6db2a6907f..161154b1a7 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TaikoSkinnableTestScene.cs
@@ -6,7 +6,7 @@ using System.Collections.Generic;
using osu.Game.Rulesets.Taiko.Skinning;
using osu.Game.Tests.Visual;
-namespace osu.Game.Rulesets.Taiko.Tests
+namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{
public abstract class TaikoSkinnableTestScene : SkinnableTestScene
{
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs
new file mode 100644
index 0000000000..554894bf68
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.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 System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.Objects.Drawables;
+using osu.Game.Rulesets.Taiko.Skinning;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests.Skinning
+{
+ [TestFixture]
+ public class TestSceneDrawableDrumRoll : TaikoSkinnableTestScene
+ {
+ public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[]
+ {
+ typeof(DrawableDrumRoll),
+ typeof(DrawableDrumRollTick),
+ typeof(LegacyDrumRoll),
+ }).ToList();
+
+ [Cached(typeof(IScrollingInfo))]
+ private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo
+ {
+ Direction = { Value = ScrollingDirection.Left },
+ TimeRange = { Value = 5000 },
+ };
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AddStep("Drum roll", () => SetContents(() =>
+ {
+ var hoc = new ScrollingHitObjectContainer();
+
+ hoc.Add(new DrawableDrumRoll(createDrumRollAtCurrentTime())
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Width = 500,
+ });
+
+ return hoc;
+ }));
+
+ AddStep("Drum roll (strong)", () => SetContents(() =>
+ {
+ var hoc = new ScrollingHitObjectContainer();
+
+ hoc.Add(new DrawableDrumRoll(createDrumRollAtCurrentTime(true))
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Width = 500,
+ });
+
+ return hoc;
+ }));
+ }
+
+ private DrumRoll createDrumRollAtCurrentTime(bool strong = false)
+ {
+ var drumroll = new DrumRoll
+ {
+ IsStrong = strong,
+ StartTime = Time.Current + 1000,
+ Duration = 4000,
+ };
+
+ var cpi = new ControlPointInfo();
+ cpi.Add(0, new TimingControlPoint { BeatLength = 500 });
+
+ drumroll.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ return drumroll;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs
similarity index 96%
rename from osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs
rename to osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs
index 301295253d..6d6da1fb5b 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs
@@ -13,7 +13,7 @@ using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Skinning;
-namespace osu.Game.Rulesets.Taiko.Tests
+namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{
[TestFixture]
public class TestSceneDrawableHit : TaikoSkinnableTestScene
@@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
typeof(DrawableCentreHit),
typeof(DrawableRimHit),
typeof(LegacyHit),
+ typeof(LegacyCirclePiece),
}).ToList();
[BackgroundDependencyLoader]
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs
similarity index 96%
rename from osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs
rename to osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs
index 1928e9f66f..412027ca61 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs
@@ -6,14 +6,14 @@ using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
-using osuTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.Skinning;
using osu.Game.Rulesets.Taiko.UI;
+using osuTK;
-namespace osu.Game.Rulesets.Taiko.Tests
+namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{
[TestFixture]
public class TestSceneInputDrum : TaikoSkinnableTestScene
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs
new file mode 100644
index 0000000000..730eed0e0f
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs
@@ -0,0 +1,42 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Taiko.Skinning;
+using osu.Game.Rulesets.Taiko.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests.Skinning
+{
+ public class TestSceneTaikoPlayfield : TaikoSkinnableTestScene
+ {
+ public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[]
+ {
+ typeof(HitTarget),
+ typeof(LegacyHitTarget),
+ }).ToList();
+
+ [Cached(typeof(IScrollingInfo))]
+ private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo
+ {
+ Direction = { Value = ScrollingDirection.Left },
+ TimeRange = { Value = 5000 },
+ };
+
+ public TestSceneTaikoPlayfield()
+ {
+ AddStep("Load playfield", () => SetContents(() => new TaikoPlayfield(new ControlPointInfo())
+ {
+ Height = 0.4f,
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ }));
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs
similarity index 99%
rename from osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
rename to osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs
index 0d9e813c60..c2ca578dfa 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs
@@ -24,7 +24,7 @@ using osuTK;
namespace osu.Game.Rulesets.Taiko.Tests
{
[TestFixture]
- public class TestSceneTaikoPlayfield : OsuTestScene
+ public class TestSceneHits : OsuTestScene
{
private const double default_duration = 1000;
private const float scroll_time = 1000;
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs
index 965cde0f3f..75049b7467 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
[Test]
public void TestZeroTickTimeOffsets()
{
- AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted);
+ AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted.Value);
AddAssert("all tick offsets are 0", () => Player.Results.Where(r => r.HitObject is SwellTick).All(r => r.TimeOffset == 0));
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs
index f3f4c59a62..a87da44415 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.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 osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using osu.Game.Skinning;
@@ -16,7 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
}
- protected override CompositeDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit),
+ protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit),
_ => new CentreHitCirclePiece(), confineMode: ConfineMode.ScaleToFit);
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
index 0627eb95fd..99f48afff0 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
@@ -14,6 +14,8 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
+using osuTK;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
@@ -29,25 +31,29 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
///
private int rollingHits;
- private readonly Container tickContainer;
+ private Container tickContainer;
private Color4 colourIdle;
private Color4 colourEngaged;
- private ElongatedCirclePiece elongatedPiece;
-
public DrawableDrumRoll(DrumRoll drumRoll)
: base(drumRoll)
{
RelativeSizeAxes = Axes.Y;
- elongatedPiece.Add(tickContainer = new Container { RelativeSizeAxes = Axes.Both });
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- elongatedPiece.AccentColour = colourIdle = colours.YellowDark;
+ colourIdle = colours.YellowDark;
colourEngaged = colours.YellowDarker;
+
+ updateColour();
+
+ Content.Add(tickContainer = new Container { RelativeSizeAxes = Axes.Both });
+
+ if (MainPiece.Drawable is IHasAccentColour accentMain)
+ accentMain.AccentColour = colourIdle;
}
protected override void LoadComplete()
@@ -86,7 +92,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return base.CreateNestedHitObject(hitObject);
}
- protected override CompositeDrawable CreateMainPiece() => elongatedPiece = new ElongatedCirclePiece();
+ protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollBody),
+ _ => new ElongatedCirclePiece());
public override bool OnPressed(TaikoAction action) => false;
@@ -102,8 +109,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
rollingHits = Math.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour);
- Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1);
- (MainPiece as IHasAccentColour)?.FadeAccent(newColour, 100);
+ updateColour();
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
@@ -132,8 +138,22 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
}
}
+ protected override void Update()
+ {
+ base.Update();
+
+ OriginPosition = new Vector2(DrawHeight);
+ Content.X = DrawHeight / 2;
+ }
+
protected override DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => new StrongNestedHit(hitObject, this);
+ private void updateColour()
+ {
+ Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1);
+ (MainPiece.Drawable as IHasAccentColour)?.FadeAccent(newColour, 100);
+ }
+
private class StrongNestedHit : DrawableStrongNestedHit
{
public StrongNestedHit(StrongHitObject strong, DrawableDrumRoll drumRoll)
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
index fea3eea6a9..689a7bfa64 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
@@ -3,10 +3,10 @@
using System;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
@@ -20,10 +20,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public override bool DisplayResult => false;
- protected override CompositeDrawable CreateMainPiece() => new TickPiece
- {
- Filled = HitObject.FirstTick
- };
+ protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollTick),
+ _ => new TickPiece
+ {
+ Filled = HitObject.FirstTick
+ });
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
index 85dfc8d5e0..9333e5f144 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
@@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
// If we're far enough away from the left stage, we should bring outselves in front of it
ProxyContent();
- var flash = (MainPiece as CirclePiece)?.FlashBox;
+ var flash = (MainPiece.Drawable as CirclePiece)?.FlashBox;
flash?.FadeTo(0.9f).FadeOut(300);
const float gravity_time = 300;
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs
index 463a8b746c..f767403c65 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.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 osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using osu.Game.Skinning;
@@ -16,7 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
}
- protected override CompositeDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit),
+ protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit),
_ => new RimHitCirclePiece(), confineMode: ConfineMode.ScaleToFit);
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
index 3a2e44038f..32f7acadc8 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
@@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
@@ -114,12 +115,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
targetRing.BorderColour = colours.YellowDark.Opacity(0.25f);
}
- protected override CompositeDrawable CreateMainPiece() => new SwellCirclePiece
- {
- // to allow for rotation transform
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- };
+ protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Swell),
+ _ => new SwellCirclePiece
+ {
+ // to allow for rotation transform
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ });
protected override void LoadComplete()
{
@@ -184,7 +186,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
.Then()
.FadeTo(completion / 8, 2000, Easing.OutQuint);
- MainPiece.RotateTo((float)(completion * HitObject.Duration / 8), 4000, Easing.OutQuint);
+ MainPiece.Drawable.RotateTo((float)(completion * HitObject.Duration / 8), 4000, Easing.OutQuint);
expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint);
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
index 5a954addfb..1685576f0d 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
@@ -2,9 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
@@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public override bool OnPressed(TaikoAction action) => false;
- protected override CompositeDrawable CreateMainPiece() => new TickPiece();
+ protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollTick),
+ _ => new TickPiece());
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index 2f90f3b96c..1be04f1760 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -11,6 +11,7 @@ using System.Collections.Generic;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Objects;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
@@ -115,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public new TObject HitObject;
protected readonly Vector2 BaseSize;
- protected readonly CompositeDrawable MainPiece;
+ protected readonly SkinnableDrawable MainPiece;
private readonly Container strongHitContainer;
@@ -167,7 +168,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
// Normal and clap samples are handled by the drum
protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
- protected abstract CompositeDrawable CreateMainPiece();
+ protected abstract SkinnableDrawable CreateMainPiece();
///
/// Creates the handler for this 's .
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
index 6ca77e666d..b5471e6976 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
@@ -10,6 +10,7 @@ using osuTK.Graphics;
using osu.Game.Beatmaps.ControlPoints;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Effects;
+using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
@@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
/// for a usage example.
///
///
- public abstract class CirclePiece : BeatSyncedContainer
+ public abstract class CirclePiece : BeatSyncedContainer, IHasAccentColour
{
public const float SYMBOL_SIZE = 0.45f;
public const float SYMBOL_BORDER = 8;
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs
index 7e3272e42b..034ab6dd21 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.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.Allocation;
using osu.Framework.Graphics;
+using osu.Game.Graphics;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
{
@@ -12,18 +14,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
RelativeSizeAxes = Axes.Y;
}
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ AccentColour = colours.YellowDark;
+ }
+
protected override void Update()
{
base.Update();
-
- var padding = Content.DrawHeight * Content.Width / 2;
-
- Content.Padding = new MarginPadding
- {
- Left = padding,
- Right = padding,
- };
-
Width = Parent.DrawSize.X + DrawHeight;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs
new file mode 100644
index 0000000000..bfcf268c3d
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs
@@ -0,0 +1,96 @@
+// 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.Animations;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Taiko.Objects.Drawables;
+using osu.Game.Skinning;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Taiko.Skinning
+{
+ public class LegacyCirclePiece : CompositeDrawable, IHasAccentColour
+ {
+ private Drawable backgroundLayer;
+
+ public LegacyCirclePiece()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin, DrawableHitObject drawableHitObject)
+ {
+ Drawable getDrawableFor(string lookup)
+ {
+ const string normal_hit = "taikohit";
+ const string big_hit = "taikobig";
+
+ string prefix = ((drawableHitObject as DrawableTaikoHitObject)?.HitObject.IsStrong ?? false) ? big_hit : normal_hit;
+
+ return skin.GetAnimation($"{prefix}{lookup}", true, false) ??
+ // fallback to regular size if "big" version doesn't exist.
+ skin.GetAnimation($"{normal_hit}{lookup}", true, false);
+ }
+
+ // backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer.
+ AddInternal(backgroundLayer = getDrawableFor("circle"));
+
+ var foregroundLayer = getDrawableFor("circleoverlay");
+ if (foregroundLayer != null)
+ AddInternal(foregroundLayer);
+
+ // Animations in taiko skins are used in a custom way (>150 combo and animating in time with beat).
+ // For now just stop at first frame for sanity.
+ foreach (var c in InternalChildren)
+ {
+ (c as IFramedAnimation)?.Stop();
+
+ c.Anchor = Anchor.Centre;
+ c.Origin = Anchor.Centre;
+ }
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ updateAccentColour();
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ // Not all skins (including the default osu-stable) have similar sizes for "hitcircle" and "hitcircleoverlay".
+ // This ensures they are scaled relative to each other but also match the expected DrawableHit size.
+ foreach (var c in InternalChildren)
+ c.Scale = new Vector2(DrawHeight / 128);
+ }
+
+ private Color4 accentColour;
+
+ public Color4 AccentColour
+ {
+ get => accentColour;
+ set
+ {
+ if (value == accentColour)
+ return;
+
+ accentColour = value;
+ if (IsLoaded)
+ updateAccentColour();
+ }
+ }
+
+ private void updateAccentColour()
+ {
+ backgroundLayer.Colour = accentColour;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs
new file mode 100644
index 0000000000..8531f3cefd
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs
@@ -0,0 +1,83 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+using osu.Game.Skinning;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Taiko.Skinning
+{
+ public class LegacyDrumRoll : CompositeDrawable, IHasAccentColour
+ {
+ private LegacyCirclePiece headCircle;
+
+ private Sprite body;
+
+ private Sprite end;
+
+ public LegacyDrumRoll()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin, OsuColour colours)
+ {
+ InternalChildren = new Drawable[]
+ {
+ end = new Sprite
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreLeft,
+ RelativeSizeAxes = Axes.Both,
+ Texture = skin.GetTexture("taiko-roll-end"),
+ FillMode = FillMode.Fit,
+ },
+ body = new Sprite
+ {
+ RelativeSizeAxes = Axes.Both,
+ Texture = skin.GetTexture("taiko-roll-middle"),
+ },
+ headCircle = new LegacyCirclePiece
+ {
+ RelativeSizeAxes = Axes.Y,
+ },
+ };
+
+ AccentColour = colours.YellowDark;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ updateAccentColour();
+ }
+
+ private Color4 accentColour;
+
+ public Color4 AccentColour
+ {
+ get => accentColour;
+ set
+ {
+ if (value == accentColour)
+ return;
+
+ accentColour = value;
+ if (IsLoaded)
+ updateAccentColour();
+ }
+ }
+
+ private void updateAccentColour()
+ {
+ headCircle.AccentColour = accentColour;
+ body.Colour = accentColour;
+ end.Colour = accentColour;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs
index 80bf97936d..656728f6e4 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs
@@ -2,90 +2,25 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Animations;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Graphics;
-using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Taiko.Objects.Drawables;
-using osu.Game.Skinning;
-using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Taiko.Skinning
{
- public class LegacyHit : CompositeDrawable, IHasAccentColour
+ public class LegacyHit : LegacyCirclePiece
{
private readonly TaikoSkinComponents component;
- private Drawable backgroundLayer;
-
public LegacyHit(TaikoSkinComponents component)
{
this.component = component;
-
- RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
- private void load(ISkinSource skin, DrawableHitObject drawableHitObject)
+ private void load()
{
- Drawable getDrawableFor(string lookup)
- {
- const string normal_hit = "taikohit";
- const string big_hit = "taikobig";
-
- string prefix = ((drawableHitObject as DrawableTaikoHitObject)?.HitObject.IsStrong ?? false) ? big_hit : normal_hit;
-
- return skin.GetAnimation($"{prefix}{lookup}", true, false) ??
- // fallback to regular size if "big" version doesn't exist.
- skin.GetAnimation($"{normal_hit}{lookup}", true, false);
- }
-
- // backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer.
- AddInternal(backgroundLayer = getDrawableFor("circle"));
-
- var foregroundLayer = getDrawableFor("circleoverlay");
- if (foregroundLayer != null)
- AddInternal(foregroundLayer);
-
- // Animations in taiko skins are used in a custom way (>150 combo and animating in time with beat).
- // For now just stop at first frame for sanity.
- foreach (var c in InternalChildren)
- {
- (c as IFramedAnimation)?.Stop();
-
- c.Anchor = Anchor.Centre;
- c.Origin = Anchor.Centre;
- }
-
AccentColour = component == TaikoSkinComponents.CentreHit
? new Color4(235, 69, 44, 255)
: new Color4(67, 142, 172, 255);
}
-
- protected override void Update()
- {
- base.Update();
-
- // Not all skins (including the default osu-stable) have similar sizes for "hitcircle" and "hitcircleoverlay".
- // This ensures they are scaled relative to each other but also match the expected DrawableHit size.
- foreach (var c in InternalChildren)
- c.Scale = new Vector2(DrawWidth / 128);
- }
-
- private Color4 accentColour;
-
- public Color4 AccentColour
- {
- get => accentColour;
- set
- {
- if (value == accentColour)
- return;
-
- backgroundLayer.Colour = accentColour = value;
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitTarget.cs
new file mode 100644
index 0000000000..51aea9b9ab
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitTarget.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.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Taiko.Skinning
+{
+ public class LegacyHitTarget : CompositeDrawable
+ {
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ InternalChildren = new Drawable[]
+ {
+ new Sprite
+ {
+ Texture = skin.GetTexture("approachcircle"),
+ Scale = new Vector2(0.73f),
+ Alpha = 0.7f,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new Sprite
+ {
+ Texture = skin.GetTexture("taikobigcircle"),
+ Scale = new Vector2(0.7f),
+ Alpha = 0.5f,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
index 9cd625c35f..6b59718173 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
@@ -27,6 +27,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning
switch (taikoComponent.Component)
{
+ case TaikoSkinComponents.DrumRollBody:
+ if (GetTexture("taiko-roll-middle") != null)
+ return new LegacyDrumRoll();
+
+ return null;
+
case TaikoSkinComponents.InputDrum:
if (GetTexture("taiko-bar-left") != null)
return new LegacyInputDrum();
@@ -40,6 +46,15 @@ namespace osu.Game.Rulesets.Taiko.Skinning
return new LegacyHit(taikoComponent.Component);
return null;
+
+ case TaikoSkinComponents.DrumRollTick:
+ return this.GetAnimation("sliderscorepoint", false, false);
+
+ case TaikoSkinComponents.HitTarget:
+ if (GetTexture("taikobigcircle") != null)
+ return new LegacyHitTarget();
+
+ return null;
}
return source.GetDrawableComponent(component);
diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
index babf21b6a9..775eeb4e38 100644
--- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
@@ -7,6 +7,10 @@ namespace osu.Game.Rulesets.Taiko
{
InputDrum,
CentreHit,
- RimHit
+ RimHit,
+ DrumRollBody,
+ DrumRollTick,
+ Swell,
+ HitTarget
}
}
diff --git a/osu.Game.Rulesets.Taiko/UI/HitTarget.cs b/osu.Game.Rulesets.Taiko/UI/HitTarget.cs
index 2bb208bd1d..88886508af 100644
--- a/osu.Game.Rulesets.Taiko/UI/HitTarget.cs
+++ b/osu.Game.Rulesets.Taiko/UI/HitTarget.cs
@@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Taiko.UI
public HitTarget()
{
+ RelativeSizeAxes = Axes.Both;
+
Children = new Drawable[]
{
new Box
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index bde9085c23..375d9995c0 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.UI
private readonly Container hitExplosionContainer;
private readonly Container kiaiExplosionContainer;
private readonly JudgementContainer judgementContainer;
- internal readonly HitTarget HitTarget;
+ internal readonly Drawable HitTarget;
private readonly ProxyContainer topLevelHitContainer;
private readonly ProxyContainer barlineContainer;
@@ -90,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.UI
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = HIT_TARGET_OFFSET },
Masking = true,
- Children = new Drawable[]
+ Children = new[]
{
hitExplosionContainer = new Container
{
@@ -98,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.UI
FillMode = FillMode.Fit,
Blending = BlendingParameters.Additive,
},
- HitTarget = new HitTarget
+ HitTarget = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.HitTarget), _ => new HitTarget())
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 33f484a9aa..acb30a6277 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -241,6 +241,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
var controlPoints = decoder.Decode(stream).ControlPointInfo;
+ Assert.That(controlPoints.TimingPoints.Count, Is.EqualTo(4));
+ Assert.That(controlPoints.DifficultyPoints.Count, Is.EqualTo(3));
+ Assert.That(controlPoints.EffectPoints.Count, Is.EqualTo(3));
+ Assert.That(controlPoints.SamplePoints.Count, Is.EqualTo(3));
+
Assert.That(controlPoints.DifficultyPointAt(500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1));
Assert.That(controlPoints.DifficultyPointAt(1500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1));
Assert.That(controlPoints.DifficultyPointAt(2500).SpeedMultiplier, Is.EqualTo(0.75).Within(0.1));
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
index f2b3a16f68..bcc873b0b7 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
@@ -1,14 +1,23 @@
// 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;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Audio.Track;
+using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.IO.Serialization;
+using osu.Game.Rulesets.Catch;
+using osu.Game.Rulesets.Mania;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Taiko;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Beatmaps.Formats
@@ -16,39 +25,91 @@ namespace osu.Game.Tests.Beatmaps.Formats
[TestFixture]
public class LegacyBeatmapEncoderTest
{
- private const string normal = "Soleily - Renatus (Gamu) [Insane].osu";
-
private static IEnumerable allBeatmaps => TestResources.GetStore().GetAvailableResources().Where(res => res.EndsWith(".osu"));
[TestCaseSource(nameof(allBeatmaps))]
- public void TestDecodeEncodedBeatmap(string name)
+ public void TestBeatmap(string name)
{
- var decoded = decode(normal, out var encoded);
+ var decoded = decode(name, out var encoded);
+
+ sort(decoded);
+ sort(encoded);
- Assert.That(decoded.HitObjects.Count, Is.EqualTo(encoded.HitObjects.Count));
Assert.That(encoded.Serialize(), Is.EqualTo(decoded.Serialize()));
}
- private Beatmap decode(string filename, out Beatmap encoded)
+ private void sort(IBeatmap beatmap)
{
- using (var stream = TestResources.OpenResource(filename))
+ // Sort control points to ensure a sane ordering, as they may be parsed in different orders. This works because each group contains only uniquely-typed control points.
+ foreach (var g in beatmap.ControlPointInfo.Groups)
+ {
+ ArrayList.Adapter((IList)g.ControlPoints).Sort(
+ Comparer.Create((c1, c2) => string.Compare(c1.GetType().ToString(), c2.GetType().ToString(), StringComparison.Ordinal)));
+ }
+ }
+
+ private IBeatmap decode(string filename, out IBeatmap encoded)
+ {
+ using (var stream = TestResources.GetStore().GetStream(filename))
using (var sr = new LineBufferedReader(stream))
{
- var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr);
+ var legacyDecoded = convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr));
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms))
- using (var sr2 = new LineBufferedReader(ms))
+ using (var sr2 = new LineBufferedReader(ms, true))
{
new LegacyBeatmapEncoder(legacyDecoded).Encode(sw);
- sw.Flush();
+ sw.Flush();
ms.Position = 0;
- encoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr2);
+ encoded = convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr2));
+
return legacyDecoded;
}
}
}
+
+ private IBeatmap convert(IBeatmap beatmap)
+ {
+ switch (beatmap.BeatmapInfo.RulesetID)
+ {
+ case 0:
+ beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo;
+ break;
+
+ case 1:
+ beatmap.BeatmapInfo.Ruleset = new TaikoRuleset().RulesetInfo;
+ break;
+
+ case 2:
+ beatmap.BeatmapInfo.Ruleset = new CatchRuleset().RulesetInfo;
+ break;
+
+ case 3:
+ beatmap.BeatmapInfo.Ruleset = new ManiaRuleset().RulesetInfo;
+ break;
+ }
+
+ return new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset);
+ }
+
+ private class TestWorkingBeatmap : WorkingBeatmap
+ {
+ private readonly IBeatmap beatmap;
+
+ public TestWorkingBeatmap(IBeatmap beatmap)
+ : base(beatmap.BeatmapInfo, null)
+ {
+ this.beatmap = beatmap;
+ }
+
+ protected override IBeatmap GetBeatmap() => beatmap;
+
+ protected override Texture GetBackground() => throw new NotImplementedException();
+
+ protected override Track GetTrack() => throw new NotImplementedException();
+ }
}
}
diff --git a/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs b/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs
new file mode 100644
index 0000000000..c477bbd9cf
--- /dev/null
+++ b/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs
@@ -0,0 +1,61 @@
+// 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.Beatmaps;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Beatmaps
+{
+ [TestFixture]
+ public class ToStringFormattingTest
+ {
+ [Test]
+ public void TestArtistTitle()
+ {
+ var beatmap = new BeatmapInfo
+ {
+ Metadata = new BeatmapMetadata
+ {
+ Artist = "artist",
+ Title = "title"
+ }
+ };
+
+ Assert.That(beatmap.ToString(), Is.EqualTo("artist - title"));
+ }
+
+ [Test]
+ public void TestArtistTitleCreator()
+ {
+ var beatmap = new BeatmapInfo
+ {
+ Metadata = new BeatmapMetadata
+ {
+ Artist = "artist",
+ Title = "title",
+ Author = new User { Username = "creator" }
+ }
+ };
+
+ Assert.That(beatmap.ToString(), Is.EqualTo("artist - title (creator)"));
+ }
+
+ [Test]
+ public void TestArtistTitleCreatorDifficulty()
+ {
+ var beatmap = new BeatmapInfo
+ {
+ Metadata = new BeatmapMetadata
+ {
+ Artist = "artist",
+ Title = "title",
+ Author = new User { Username = "creator" }
+ },
+ Version = "difficulty"
+ };
+
+ Assert.That(beatmap.ToString(), Is.EqualTo("artist - title (creator) [difficulty]"));
+ }
+ }
+}
diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
index 2782e902fe..158954106d 100644
--- a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
+++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
@@ -29,11 +29,17 @@ namespace osu.Game.Tests.NonVisual
var cpi = new ControlPointInfo();
cpi.Add(0, new TimingControlPoint()); // is *not* redundant, special exception for first timing point.
- cpi.Add(1000, new TimingControlPoint()); // is redundant
+ cpi.Add(1000, new TimingControlPoint()); // is also not redundant, due to change of offset
- Assert.That(cpi.Groups.Count, Is.EqualTo(1));
- Assert.That(cpi.TimingPoints.Count, Is.EqualTo(1));
- Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
+ Assert.That(cpi.Groups.Count, Is.EqualTo(2));
+ Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2));
+ Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
+
+ cpi.Add(1000, new TimingControlPoint()); //is redundant
+
+ Assert.That(cpi.Groups.Count, Is.EqualTo(2));
+ Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2));
+ Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
}
[Test]
@@ -86,11 +92,12 @@ namespace osu.Game.Tests.NonVisual
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(0));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
- cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant
+ cpi.Add(1000, new EffectControlPoint { KiaiMode = true, OmitFirstBarLine = true }); // is not redundant
+ cpi.Add(1400, new EffectControlPoint { KiaiMode = true, OmitFirstBarLine = true }); // same settings, but is not redundant
- Assert.That(cpi.Groups.Count, Is.EqualTo(1));
- Assert.That(cpi.EffectPoints.Count, Is.EqualTo(1));
- Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
+ Assert.That(cpi.Groups.Count, Is.EqualTo(2));
+ Assert.That(cpi.EffectPoints.Count, Is.EqualTo(2));
+ Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
}
[Test]
diff --git a/osu.Game.Tests/Resources/hitobject-combo-offset.osu b/osu.Game.Tests/Resources/hitobject-combo-offset.osu
index c1f0dab8e9..d39a3e8548 100644
--- a/osu.Game.Tests/Resources/hitobject-combo-offset.osu
+++ b/osu.Game.Tests/Resources/hitobject-combo-offset.osu
@@ -5,27 +5,27 @@ osu file format v14
255,193,1000,49,0,0:0:0:0:
// Combo index = 4
-// Slider with new combo followed by circle with no new combo
+// Spinner with new combo followed by circle with no new combo
256,192,2000,12,0,2000,0:0:0:0:
255,193,3000,1,0,0:0:0:0:
// Combo index = 5
-// Slider without new combo followed by circle with no new combo
+// Spinner without new combo followed by circle with no new combo
256,192,4000,8,0,5000,0:0:0:0:
255,193,6000,1,0,0:0:0:0:
// Combo index = 5
-// Slider without new combo followed by circle with new combo
+// Spinner without new combo followed by circle with new combo
256,192,7000,8,0,8000,0:0:0:0:
255,193,9000,5,0,0:0:0:0:
// Combo index = 6
-// Slider with new combo and offset (1) followed by circle with new combo and offset (3)
+// Spinner with new combo and offset (1) followed by circle with new combo and offset (3)
256,192,10000,28,0,11000,0:0:0:0:
255,193,12000,53,0,0:0:0:0:
// Combo index = 11
-// Slider with new combo and offset (2) followed by slider with no new combo followed by circle with no new combo
+// Spinner with new combo and offset (2) followed by slider with no new combo followed by circle with no new combo
256,192,13000,44,0,14000,0:0:0:0:
256,192,15000,8,0,16000,0:0:0:0:
255,193,17000,1,0,0:0:0:0:
diff --git a/osu.Game.Tests/Resources/sample-beatmap-osu.osu b/osu.Game.Tests/Resources/sample-beatmap-osu.osu
new file mode 100644
index 0000000000..27c96077e6
--- /dev/null
+++ b/osu.Game.Tests/Resources/sample-beatmap-osu.osu
@@ -0,0 +1,32 @@
+osu file format v14
+
+[General]
+SampleSet: Normal
+StackLeniency: 0.7
+Mode: 0
+
+[Difficulty]
+HPDrainRate:3
+CircleSize:5
+OverallDifficulty:8
+ApproachRate:8
+SliderMultiplier:3.59999990463257
+SliderTickRate:2
+
+[TimingPoints]
+24,352.941176470588,4,1,1,100,1,0
+6376,-50,4,1,1,100,0,0
+
+[HitObjects]
+98,69,24,1,0,0:0:0:0:
+419,72,200,1,2,0:0:0:0:
+81,314,376,1,6,0:0:0:0:
+423,321,553,1,12,0:0:0:0:
+86,192,729,2,0,P|459:193|460:193,1,359.999990463257
+86,192,1259,2,0,P|246:82|453:203,1,449.999988079071
+86,192,1876,2,0,B|256:30|257:313|464:177,1,359.999990463257
+86,55,2406,2,12,B|447:51|447:51|452:348|452:348|78:344,1,989.999973773957,14|2,0:0|0:0,0:0:0:0:
+256,192,3553,12,0,4259,0:0:0:0:
+67,57,4435,5,0,0:0:0:0:
+440,52,4612,5,0,0:0:0:0:
+86,181,4788,6,0,L|492:183,1,359.999990463257
\ No newline at end of file
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs
index dd1b6cf6aa..efc2a6f552 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs
@@ -10,24 +10,22 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
-using osuTK.Input;
namespace osu.Game.Tests.Visual.Editor
{
public class TestSceneEditorChangeStates : ScreenTestScene
{
private EditorBeatmap editorBeatmap;
+ private TestEditor editor;
public override void SetUpSteps()
{
base.SetUpSteps();
- Screens.Edit.Editor editor = null;
-
AddStep("load editor", () =>
{
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
- LoadScreen(editor = new Screens.Edit.Editor());
+ LoadScreen(editor = new TestEditor());
});
AddUntilStep("wait for editor to load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true
@@ -160,36 +158,15 @@ namespace osu.Game.Tests.Visual.Editor
AddAssert("no hitobject added", () => addedObject == null);
}
- private void addUndoSteps()
+ private void addUndoSteps() => AddStep("undo", () => editor.Undo());
+
+ private void addRedoSteps() => AddStep("redo", () => editor.Redo());
+
+ private class TestEditor : Screens.Edit.Editor
{
- AddStep("press undo", () =>
- {
- InputManager.PressKey(Key.LControl);
- InputManager.PressKey(Key.Z);
- });
+ public new void Undo() => base.Undo();
- AddStep("release keys", () =>
- {
- InputManager.ReleaseKey(Key.LControl);
- InputManager.ReleaseKey(Key.Z);
- });
- }
-
- private void addRedoSteps()
- {
- AddStep("press redo", () =>
- {
- InputManager.PressKey(Key.LControl);
- InputManager.PressKey(Key.LShift);
- InputManager.PressKey(Key.Z);
- });
-
- AddStep("release keys", () =>
- {
- InputManager.ReleaseKey(Key.LControl);
- InputManager.ReleaseKey(Key.LShift);
- InputManager.ReleaseKey(Key.Z);
- });
+ public new void Redo() => base.Redo();
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs
new file mode 100644
index 0000000000..f87999ae61
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs
@@ -0,0 +1,133 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Track;
+using osu.Framework.Screens;
+using osu.Framework.Testing;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Storyboards;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneCompletionCancellation : TestPlayerTestScene
+ {
+ private Track track;
+
+ [Resolved]
+ private AudioManager audio { get; set; }
+
+ private int resultsDisplayWaitCount =>
+ (int)((Screens.Play.Player.RESULTS_DISPLAY_DELAY / TimePerAction) * 2);
+
+ protected override bool AllowFail => false;
+
+ [SetUpSteps]
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+
+ // Ensure track has actually running before attempting to seek
+ AddUntilStep("wait for track to start running", () => track.IsRunning);
+ }
+
+ [Test]
+ public void TestCancelCompletionOnRewind()
+ {
+ complete();
+ cancel();
+
+ checkNoRanking();
+ }
+
+ [Test]
+ public void TestReCompleteAfterCancellation()
+ {
+ complete();
+ cancel();
+ complete();
+
+ AddUntilStep("attempted to push ranking", () => ((FakeRankingPushPlayer)Player).GotoRankingInvoked);
+ }
+
+ ///
+ /// Tests whether can still pause after cancelling completion by reverting back to true.
+ ///
+ [Test]
+ public void TestCanPauseAfterCancellation()
+ {
+ complete();
+ cancel();
+
+ AddStep("pause", () => Player.Pause());
+ AddAssert("paused successfully", () => Player.GameplayClockContainer.IsPaused.Value);
+
+ checkNoRanking();
+ }
+
+ private void complete()
+ {
+ AddStep("seek to completion", () => track.Seek(5000));
+ AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
+ }
+
+ private void cancel()
+ {
+ AddStep("rewind to cancel", () => track.Seek(4000));
+ AddUntilStep("completion cleared by processor", () => !Player.ScoreProcessor.HasCompleted.Value);
+ }
+
+ private void checkNoRanking()
+ {
+ // wait to ensure there was no attempt of pushing the results screen.
+ AddWaitStep("wait", resultsDisplayWaitCount);
+ AddAssert("no attempt to push ranking", () => !((FakeRankingPushPlayer)Player).GotoRankingInvoked);
+ }
+
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
+ {
+ var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio);
+ track = working.Track;
+ return working;
+ }
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
+ {
+ var beatmap = new Beatmap();
+
+ for (int i = 1; i <= 19; i++)
+ {
+ beatmap.HitObjects.Add(new HitCircle
+ {
+ Position = new Vector2(256, 192),
+ StartTime = i * 250,
+ });
+ }
+
+ return beatmap;
+ }
+
+ protected override TestPlayer CreatePlayer(Ruleset ruleset) => new FakeRankingPushPlayer();
+
+ public class FakeRankingPushPlayer : TestPlayer
+ {
+ public bool GotoRankingInvoked;
+
+ public FakeRankingPushPlayer()
+ : base(true, true)
+ {
+ }
+
+ protected override void GotoRanking()
+ {
+ GotoRankingInvoked = true;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs
new file mode 100644
index 0000000000..db65e91d17
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs
@@ -0,0 +1,98 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Input.Bindings;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.UI;
+using osuTK.Input;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ [HeadlessTest]
+ public class TestSceneKeyBindings : OsuManualInputManagerTestScene
+ {
+ private readonly ActionReceiver receiver;
+
+ public TestSceneKeyBindings()
+ {
+ Add(new TestKeyBindingContainer
+ {
+ Child = receiver = new ActionReceiver()
+ });
+ }
+
+ [Test]
+ public void TestDefaultsWhenNotDatabased()
+ {
+ AddStep("fire key", () =>
+ {
+ InputManager.PressKey(Key.A);
+ InputManager.ReleaseKey(Key.A);
+ });
+
+ AddAssert("received key", () => receiver.ReceivedAction);
+ }
+
+ private class TestRuleset : Ruleset
+ {
+ public override IEnumerable GetModsFor(ModType type) =>
+ throw new System.NotImplementedException();
+
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) =>
+ throw new System.NotImplementedException();
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) =>
+ throw new System.NotImplementedException();
+
+ public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) =>
+ throw new System.NotImplementedException();
+
+ public override IEnumerable GetDefaultKeyBindings(int variant = 0)
+ {
+ return new[]
+ {
+ new KeyBinding(InputKey.A, TestAction.Down),
+ };
+ }
+
+ public override string Description => "test";
+ public override string ShortName => "test";
+ }
+
+ private enum TestAction
+ {
+ Down,
+ }
+
+ private class TestKeyBindingContainer : DatabasedKeyBindingContainer
+ {
+ public TestKeyBindingContainer()
+ : base(new TestRuleset().RulesetInfo, 0)
+ {
+ }
+ }
+
+ private class ActionReceiver : CompositeDrawable, IKeyBindingHandler
+ {
+ public bool ReceivedAction;
+
+ public bool OnPressed(TestAction action)
+ {
+ ReceivedAction = action == TestAction.Down;
+ return true;
+ }
+
+ public void OnReleased(TestAction action)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs
index 7c05d99c59..64d1a9ddcd 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using osu.Game.Overlays;
using NUnit.Framework;
+using osu.Game.Overlays.BeatmapListing;
namespace osu.Game.Tests.Visual.Online
{
@@ -13,6 +14,7 @@ namespace osu.Game.Tests.Visual.Online
public override IReadOnlyList RequiredTypes => new[]
{
typeof(BeatmapListingOverlay),
+ typeof(BeatmapListingFilterControl)
};
protected override bool UseOnlineAPI => true;
diff --git a/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs
new file mode 100644
index 0000000000..df95f24686
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs
@@ -0,0 +1,43 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Game.Overlays;
+using osu.Game.Overlays.Dashboard;
+using osu.Game.Overlays.Dashboard.Friends;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneDashboardOverlay : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DashboardOverlay),
+ typeof(DashboardOverlayHeader),
+ typeof(FriendDisplay)
+ };
+
+ protected override bool UseOnlineAPI => true;
+
+ private readonly DashboardOverlay overlay;
+
+ public TestSceneDashboardOverlay()
+ {
+ Add(overlay = new DashboardOverlay());
+ }
+
+ [Test]
+ public void TestShow()
+ {
+ AddStep("Show", overlay.Show);
+ }
+
+ [Test]
+ public void TestHide()
+ {
+ AddStep("Hide", overlay.Hide);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs
index f612992bf6..9fe873cb6a 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs
@@ -9,7 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Online;
-using osu.Game.Overlays.Direct;
+using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Rulesets.Osu;
using osu.Game.Tests.Resources;
using osuTK;
@@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Online
{
public override IReadOnlyList RequiredTypes => new[]
{
- typeof(PanelDownloadButton)
+ typeof(BeatmapPanelDownloadButton)
};
private TestDownloadButton downloadButton;
@@ -143,7 +143,7 @@ namespace osu.Game.Tests.Visual.Online
return beatmap;
}
- private class TestDownloadButton : PanelDownloadButton
+ private class TestDownloadButton : BeatmapPanelDownloadButton
{
public new bool DownloadEnabled => base.DownloadEnabled;
diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs
deleted file mode 100644
index d9873ea243..0000000000
--- a/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using NUnit.Framework;
-using osu.Game.Beatmaps;
-using osu.Game.Overlays;
-
-namespace osu.Game.Tests.Visual.Online
-{
- [TestFixture]
- public class TestSceneDirectOverlay : OsuTestScene
- {
- private DirectOverlay direct;
-
- protected override bool UseOnlineAPI => true;
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- Add(direct = new DirectOverlay());
- newBeatmaps();
-
- AddStep(@"toggle", direct.ToggleVisibility);
- AddStep(@"result counts", () => direct.ResultAmounts = new DirectOverlay.ResultCounts(1, 4, 13));
- AddStep(@"trigger disabled", () => Ruleset.Disabled = !Ruleset.Disabled);
- }
-
- private void newBeatmaps()
- {
- direct.BeatmapSets = new[]
- {
- new BeatmapSetInfo
- {
- OnlineBeatmapSetID = 578332,
- Metadata = new BeatmapMetadata
- {
- Title = @"OrVid",
- Artist = @"An",
- AuthorString = @"RLC",
- Source = @"",
- Tags = @"acuticnotes an-fillnote revid tear tearvid encrpted encryption axi axivid quad her hervid recoll",
- },
- OnlineInfo = new BeatmapSetOnlineInfo
- {
- Covers = new BeatmapSetOnlineCovers
- {
- Card = @"https://assets.ppy.sh/beatmaps/578332/covers/card.jpg?1494591390",
- Cover = @"https://assets.ppy.sh/beatmaps/578332/covers/cover.jpg?1494591390",
- },
- Preview = @"https://b.ppy.sh/preview/578332.mp3",
- PlayCount = 97,
- FavouriteCount = 72,
- },
- Beatmaps = new List
- {
- new BeatmapInfo
- {
- Ruleset = Ruleset.Value,
- StarDifficulty = 5.35f,
- Metadata = new BeatmapMetadata(),
- },
- },
- },
- new BeatmapSetInfo
- {
- OnlineBeatmapSetID = 599627,
- Metadata = new BeatmapMetadata
- {
- Title = @"tiny lamp",
- Artist = @"fhana",
- AuthorString = @"Sotarks",
- Source = @"ぎんぎつね",
- Tags = @"lantis junichi sato yuxuki waga kevin mitsunaga towana gingitsune opening op full ver version kalibe collab collaboration",
- },
- OnlineInfo = new BeatmapSetOnlineInfo
- {
- Covers = new BeatmapSetOnlineCovers
- {
- Card = @"https://assets.ppy.sh/beatmaps/599627/covers/card.jpg?1494539318",
- Cover = @"https://assets.ppy.sh/beatmaps/599627/covers/cover.jpg?1494539318",
- },
- Preview = @"https//b.ppy.sh/preview/599627.mp3",
- PlayCount = 3082,
- FavouriteCount = 14,
- },
- Beatmaps = new List
- {
- new BeatmapInfo
- {
- Ruleset = Ruleset.Value,
- StarDifficulty = 5.81f,
- Metadata = new BeatmapMetadata(),
- },
- },
- },
- new BeatmapSetInfo
- {
- OnlineBeatmapSetID = 513268,
- Metadata = new BeatmapMetadata
- {
- Title = @"At Gwanghwamun",
- Artist = @"KYUHYUN",
- AuthorString = @"Cerulean Veyron",
- Source = @"",
- Tags = @"soul ballad kh super junior sj suju 슈퍼주니어 kt뮤직 sm엔터테인먼트 s.m.entertainment kt music 1st mini album ep",
- },
- OnlineInfo = new BeatmapSetOnlineInfo
- {
- Covers = new BeatmapSetOnlineCovers
- {
- Card = @"https://assets.ppy.sh/beatmaps/513268/covers/card.jpg?1494502863",
- Cover = @"https://assets.ppy.sh/beatmaps/513268/covers/cover.jpg?1494502863",
- },
- Preview = @"https//b.ppy.sh/preview/513268.mp3",
- PlayCount = 2762,
- FavouriteCount = 15,
- },
- Beatmaps = new List
- {
- new BeatmapInfo
- {
- Ruleset = Ruleset.Value,
- StarDifficulty = 0.9f,
- Metadata = new BeatmapMetadata(),
- },
- new BeatmapInfo
- {
- Ruleset = Ruleset.Value,
- StarDifficulty = 1.1f,
- },
- new BeatmapInfo
- {
- Ruleset = Ruleset.Value,
- StarDifficulty = 2.02f,
- },
- new BeatmapInfo
- {
- Ruleset = Ruleset.Value,
- StarDifficulty = 3.49f,
- },
- },
- },
- new BeatmapSetInfo
- {
- OnlineBeatmapSetID = 586841,
- Metadata = new BeatmapMetadata
- {
- Title = @"RHAPSODY OF BLUE SKY",
- Artist = @"fhana",
- AuthorString = @"[Kamiya]",
- Source = @"小林さんちのメイドラゴン",
- Tags = @"kobayashi san chi no maidragon aozora no opening anime maid dragon oblivion karen dynamix imoutosan pata-mon gxytcgxytc",
- },
- OnlineInfo = new BeatmapSetOnlineInfo
- {
- Covers = new BeatmapSetOnlineCovers
- {
- Card = @"https://assets.ppy.sh/beatmaps/586841/covers/card.jpg?1494052741",
- Cover = @"https://assets.ppy.sh/beatmaps/586841/covers/cover.jpg?1494052741",
- },
- Preview = @"https//b.ppy.sh/preview/586841.mp3",
- PlayCount = 62317,
- FavouriteCount = 161,
- },
- Beatmaps = new List
- {
- new BeatmapInfo
- {
- Ruleset = Ruleset.Value,
- StarDifficulty = 1.26f,
- Metadata = new BeatmapMetadata(),
- },
- new BeatmapInfo
- {
- Ruleset = Ruleset.Value,
- StarDifficulty = 2.01f,
- },
- new BeatmapInfo
- {
- Ruleset = Ruleset.Value,
- StarDifficulty = 2.87f,
- },
- new BeatmapInfo
- {
- Ruleset = Ruleset.Value,
- StarDifficulty = 3.76f,
- },
- new BeatmapInfo
- {
- Ruleset = Ruleset.Value,
- StarDifficulty = 3.93f,
- },
- new BeatmapInfo
- {
- Ruleset = Ruleset.Value,
- StarDifficulty = 4.37f,
- },
- new BeatmapInfo
- {
- Ruleset = Ruleset.Value,
- StarDifficulty = 5.13f,
- },
- new BeatmapInfo
- {
- Ruleset = Ruleset.Value,
- StarDifficulty = 5.42f,
- },
- },
- },
- };
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs
index cb08cded37..d6ed654bac 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs
@@ -8,7 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Beatmaps;
-using osu.Game.Overlays.Direct;
+using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Rulesets;
using osu.Game.Users;
using osuTK;
@@ -20,8 +20,8 @@ namespace osu.Game.Tests.Visual.Online
{
public override IReadOnlyList RequiredTypes => new[]
{
- typeof(DirectGridPanel),
- typeof(DirectListPanel),
+ typeof(GridBeatmapPanel),
+ typeof(ListBeatmapPanel),
typeof(IconPill)
};
@@ -126,12 +126,12 @@ namespace osu.Game.Tests.Visual.Online
Spacing = new Vector2(5, 20),
Children = new Drawable[]
{
- new DirectGridPanel(normal),
- new DirectGridPanel(undownloadable),
- new DirectGridPanel(manyDifficulties),
- new DirectListPanel(normal),
- new DirectListPanel(undownloadable),
- new DirectListPanel(manyDifficulties),
+ new GridBeatmapPanel(normal),
+ new GridBeatmapPanel(undownloadable),
+ new GridBeatmapPanel(manyDifficulties),
+ new ListBeatmapPanel(normal),
+ new ListBeatmapPanel(undownloadable),
+ new ListBeatmapPanel(manyDifficulties),
},
},
};
diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
index cf365a7614..0b5ff1c960 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
@@ -10,6 +10,7 @@ using osu.Game.Users;
using osu.Game.Overlays;
using osu.Framework.Allocation;
using NUnit.Framework;
+using osu.Game.Online.API;
namespace osu.Game.Tests.Visual.Online
{
@@ -27,7 +28,7 @@ namespace osu.Game.Tests.Visual.Online
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
- private FriendDisplay display;
+ private TestFriendDisplay display;
[SetUp]
public void Setup() => Schedule(() =>
@@ -35,7 +36,7 @@ namespace osu.Game.Tests.Visual.Online
Child = new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
- Child = display = new FriendDisplay()
+ Child = display = new TestFriendDisplay()
};
});
@@ -83,5 +84,17 @@ namespace osu.Game.Tests.Visual.Online
LastVisit = DateTimeOffset.Now
}
};
+
+ private class TestFriendDisplay : FriendDisplay
+ {
+ public void Fetch()
+ {
+ base.APIStateChanged(API, APIState.Online);
+ }
+
+ public override void APIStateChanged(IAPIProvider api, APIState state)
+ {
+ }
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs
new file mode 100644
index 0000000000..103308d34d
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.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 NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Online.Chat;
+using osu.Game.Rulesets;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ [HeadlessTest]
+ public class TestSceneNowPlayingCommand : OsuTestScene
+ {
+ [Cached(typeof(IChannelPostTarget))]
+ private PostTarget postTarget { get; set; }
+
+ public TestSceneNowPlayingCommand()
+ {
+ Add(postTarget = new PostTarget());
+ }
+
+ [Test]
+ public void TestGenericActivity()
+ {
+ AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby());
+
+ AddStep("Run command", () => Add(new NowPlayingCommand()));
+
+ AddAssert("Check correct response", () => postTarget.LastMessage.Contains("is listening"));
+ }
+
+ [Test]
+ public void TestEditActivity()
+ {
+ AddStep("Set activity", () => API.Activity.Value = new UserActivity.Editing(new BeatmapInfo()));
+
+ AddStep("Run command", () => Add(new NowPlayingCommand()));
+
+ AddAssert("Check correct response", () => postTarget.LastMessage.Contains("is editing"));
+ }
+
+ [Test]
+ public void TestPlayActivity()
+ {
+ AddStep("Set activity", () => API.Activity.Value = new UserActivity.SoloGame(new BeatmapInfo(), new RulesetInfo()));
+
+ AddStep("Run command", () => Add(new NowPlayingCommand()));
+
+ AddAssert("Check correct response", () => postTarget.LastMessage.Contains("is playing"));
+ }
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestLinkPresence(bool hasOnlineId)
+ {
+ AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby());
+
+ AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(null, null)
+ {
+ BeatmapInfo = { OnlineBeatmapID = hasOnlineId ? 1234 : (int?)null }
+ });
+
+ AddStep("Run command", () => Add(new NowPlayingCommand()));
+
+ if (hasOnlineId)
+ AddAssert("Check link presence", () => postTarget.LastMessage.Contains("https://osu.ppy.sh/b/1234"));
+ else
+ AddAssert("Check link not present", () => !postTarget.LastMessage.Contains("https://"));
+ }
+
+ public class PostTarget : Component, IChannelPostTarget
+ {
+ public void PostMessage(string text, bool isAction = false, Channel target = null)
+ {
+ LastMessage = text;
+ }
+
+ public string LastMessage { get; private set; }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index 4405c75744..39e04ed39a 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -359,6 +359,68 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null);
}
+ [Test]
+ public void TestPresentNewRulesetNewBeatmap()
+ {
+ createSongSelect();
+ changeRuleset(2);
+
+ addRulesetImportStep(2);
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2);
+
+ addRulesetImportStep(0);
+ addRulesetImportStep(0);
+ addRulesetImportStep(0);
+
+ BeatmapInfo target = null;
+
+ AddStep("select beatmap/ruleset externally", () =>
+ {
+ target = manager.GetAllUsableBeatmapSets()
+ .Last(b => b.Beatmaps.Any(bi => bi.RulesetID == 0)).Beatmaps.Last();
+
+ Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == 0);
+ Beatmap.Value = manager.GetWorkingBeatmap(target);
+ });
+
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target));
+
+ // this is an important check, to make sure updateComponentFromBeatmap() was actually run
+ AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo == target);
+ }
+
+ [Test]
+ public void TestPresentNewBeatmapNewRuleset()
+ {
+ createSongSelect();
+ changeRuleset(2);
+
+ addRulesetImportStep(2);
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2);
+
+ addRulesetImportStep(0);
+ addRulesetImportStep(0);
+ addRulesetImportStep(0);
+
+ BeatmapInfo target = null;
+
+ AddStep("select beatmap/ruleset externally", () =>
+ {
+ target = manager.GetAllUsableBeatmapSets()
+ .Last(b => b.Beatmaps.Any(bi => bi.RulesetID == 0)).Beatmaps.Last();
+
+ Beatmap.Value = manager.GetWorkingBeatmap(target);
+ Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == 0);
+ });
+
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target));
+
+ AddUntilStep("has correct ruleset", () => Ruleset.Value.ID == 0);
+
+ // this is an important check, to make sure updateComponentFromBeatmap() was actually run
+ AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo == target);
+ }
+
[Test]
public void TestRulesetChangeResetsMods()
{
diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/TestSceneOsuGame.cs
index 492494ada3..2eaac2a45f 100644
--- a/osu.Game.Tests/Visual/TestSceneOsuGame.cs
+++ b/osu.Game.Tests/Visual/TestSceneOsuGame.cs
@@ -47,8 +47,8 @@ namespace osu.Game.Tests.Visual
typeof(IdleTracker),
typeof(OnScreenDisplay),
typeof(NotificationOverlay),
- typeof(DirectOverlay),
- typeof(SocialOverlay),
+ typeof(BeatmapListingOverlay),
+ typeof(DashboardOverlay),
typeof(ChannelManager),
typeof(ChatOverlay),
typeof(SettingsOverlay),
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs
similarity index 69%
rename from osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs
rename to osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs
index 1d8db71527..d6ede950df 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs
@@ -15,25 +15,27 @@ using osuTK;
namespace osu.Game.Tests.Visual.UserInterface
{
- public class TestSceneBeatmapListingSearchSection : OsuTestScene
+ public class TestSceneBeatmapListingSearchControl : OsuTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
- typeof(BeatmapListingSearchSection),
+ typeof(BeatmapListingSearchControl),
};
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
- private readonly BeatmapListingSearchSection section;
+ private readonly BeatmapListingSearchControl control;
- public TestSceneBeatmapListingSearchSection()
+ public TestSceneBeatmapListingSearchControl()
{
OsuSpriteText query;
OsuSpriteText ruleset;
OsuSpriteText category;
+ OsuSpriteText genre;
+ OsuSpriteText language;
- Add(section = new BeatmapListingSearchSection
+ Add(control = new BeatmapListingSearchControl
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -49,20 +51,24 @@ namespace osu.Game.Tests.Visual.UserInterface
query = new OsuSpriteText(),
ruleset = new OsuSpriteText(),
category = new OsuSpriteText(),
+ genre = new OsuSpriteText(),
+ language = new OsuSpriteText(),
}
});
- section.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true);
- section.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true);
- section.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true);
+ control.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true);
+ control.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true);
+ control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true);
+ control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true);
+ control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true);
}
[Test]
public void TestCovers()
{
- AddStep("Set beatmap", () => section.BeatmapSet = beatmap_set);
- AddStep("Set beatmap (no cover)", () => section.BeatmapSet = no_cover_beatmap_set);
- AddStep("Set null beatmap", () => section.BeatmapSet = null);
+ AddStep("Set beatmap", () => control.BeatmapSet = beatmap_set);
+ AddStep("Set beatmap (no cover)", () => control.BeatmapSet = no_cover_beatmap_set);
+ AddStep("Set null beatmap", () => control.BeatmapSet = null);
}
private static readonly BeatmapSetInfo beatmap_set = new BeatmapSetInfo
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSort.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSortTabControl.cs
similarity index 91%
rename from osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSort.cs
rename to osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSortTabControl.cs
index a5fa085abf..f643d4e3fe 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSort.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSortTabControl.cs
@@ -13,18 +13,17 @@ using osuTK;
namespace osu.Game.Tests.Visual.UserInterface
{
- public class TestSceneBeatmapListingSort : OsuTestScene
+ public class TestSceneBeatmapListingSortTabControl : OsuTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
- typeof(BeatmapListingSortTabControl),
typeof(OverlaySortTabControl<>),
};
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
- public TestSceneBeatmapListingSort()
+ public TestSceneBeatmapListingSortTabControl()
{
BeatmapListingSortTabControl control;
OsuSpriteText current;
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapSearchFilter.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapSearchFilter.cs
index 7b4424e568..283fe03af3 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapSearchFilter.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapSearchFilter.cs
@@ -8,7 +8,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Containers;
-using osu.Game.Online.API.Requests;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapListing;
using osuTK;
@@ -20,8 +19,7 @@ namespace osu.Game.Tests.Visual.UserInterface
public override IReadOnlyList RequiredTypes => new[]
{
typeof(BeatmapSearchFilterRow<>),
- typeof(BeatmapSearchRulesetFilterRow),
- typeof(BeatmapSearchSmallFilterRow<>),
+ typeof(BeatmapSearchRulesetFilterRow)
};
[Cached]
@@ -42,8 +40,8 @@ namespace osu.Game.Tests.Visual.UserInterface
Children = new Drawable[]
{
new BeatmapSearchRulesetFilterRow(),
- new BeatmapSearchFilterRow("Categories"),
- new BeatmapSearchSmallFilterRow("Header Name")
+ new BeatmapSearchFilterRow("Categories"),
+ new BeatmapSearchFilterRow("Header Name")
}
});
}
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index 68d113ce40..90c100db05 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -149,7 +149,12 @@ namespace osu.Game.Beatmaps
}
}
- public override string ToString() => $"{Metadata} [{Version}]".Trim();
+ public override string ToString()
+ {
+ string version = string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]";
+
+ return $"{Metadata} {version}".Trim();
+ }
public bool Equals(BeatmapInfo other)
{
diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs
index 001f319307..775d78f1fb 100644
--- a/osu.Game/Beatmaps/BeatmapMetadata.cs
+++ b/osu.Game/Beatmaps/BeatmapMetadata.cs
@@ -53,7 +53,11 @@ namespace osu.Game.Beatmaps
public string AudioFile { get; set; }
public string BackgroundFile { get; set; }
- public override string ToString() => $"{Artist} - {Title} ({Author})";
+ public override string ToString()
+ {
+ string author = Author == null ? string.Empty : $"({Author})";
+ return $"{Artist} - {Title} {author}".Trim();
+ }
[JsonIgnore]
public string[] SearchableTerms => new[]
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
index 39a0e6f6d4..a1822a1163 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
@@ -5,7 +5,7 @@ using System;
namespace osu.Game.Beatmaps.ControlPoints
{
- public abstract class ControlPoint : IComparable, IEquatable
+ public abstract class ControlPoint : IComparable
{
///
/// The time at which the control point takes effect.
@@ -19,12 +19,10 @@ namespace osu.Game.Beatmaps.ControlPoints
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
///
- /// Whether this control point is equivalent to another, ignoring time.
+ /// Determines whether this results in a meaningful change when placed alongside another.
///
- /// Another control point to compare with.
- /// Whether equivalent.
- public abstract bool EquivalentTo(ControlPoint other);
-
- public bool Equals(ControlPoint other) => Time == other?.Time && EquivalentTo(other);
+ /// An existing control point to compare with.
+ /// Whether this is redundant when placed alongside .
+ public abstract bool IsRedundant(ControlPoint existing);
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index df68d8acd2..af6ca24165 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -56,6 +56,7 @@ namespace osu.Game.Beatmaps.ControlPoints
///
/// All control points, of all types.
///
+ [JsonIgnore]
public IEnumerable AllControlPoints => Groups.SelectMany(g => g.ControlPoints).ToArray();
///
@@ -247,7 +248,7 @@ namespace osu.Game.Beatmaps.ControlPoints
break;
}
- return existing?.EquivalentTo(newPoint) == true;
+ return newPoint?.IsRedundant(existing) == true;
}
private void groupItemAdded(ControlPoint controlPoint)
diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
index 8b21098a51..2448b2b25c 100644
--- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
@@ -27,7 +27,8 @@ namespace osu.Game.Beatmaps.ControlPoints
set => SpeedMultiplierBindable.Value = value;
}
- public override bool EquivalentTo(ControlPoint other) =>
- other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(SpeedMultiplier);
+ public override bool IsRedundant(ControlPoint existing)
+ => existing is DifficultyControlPoint existingDifficulty
+ && SpeedMultiplier == existingDifficulty.SpeedMultiplier;
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
index 369b93ff3d..9b69147468 100644
--- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
@@ -35,8 +35,10 @@ namespace osu.Game.Beatmaps.ControlPoints
set => KiaiModeBindable.Value = value;
}
- public override bool EquivalentTo(ControlPoint other) =>
- other is EffectControlPoint otherTyped &&
- KiaiMode == otherTyped.KiaiMode && OmitFirstBarLine == otherTyped.OmitFirstBarLine;
+ public override bool IsRedundant(ControlPoint existing)
+ => !OmitFirstBarLine
+ && existing is EffectControlPoint existingEffect
+ && KiaiMode == existingEffect.KiaiMode
+ && OmitFirstBarLine == existingEffect.OmitFirstBarLine;
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
index 393bcfdb3c..61851a00d7 100644
--- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
@@ -68,8 +68,9 @@ namespace osu.Game.Beatmaps.ControlPoints
return newSampleInfo;
}
- public override bool EquivalentTo(ControlPoint other) =>
- other is SampleControlPoint otherTyped &&
- SampleBank == otherTyped.SampleBank && SampleVolume == otherTyped.SampleVolume;
+ public override bool IsRedundant(ControlPoint existing)
+ => existing is SampleControlPoint existingSample
+ && SampleBank == existingSample.SampleBank
+ && SampleVolume == existingSample.SampleVolume;
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
index 51b3377394..1927dd6575 100644
--- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
@@ -48,8 +48,7 @@ namespace osu.Game.Beatmaps.ControlPoints
///
public double BPM => 60000 / BeatLength;
- public override bool EquivalentTo(ControlPoint other) =>
- other is TimingControlPoint otherTyped
- && TimeSignature == otherTyped.TimeSignature && BeatLength.Equals(otherTyped.BeatLength);
+ // Timing points are never redundant as they can change the time signature.
+ public override bool IsRedundant(ControlPoint existing) => false;
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 33bb9774df..388abf4648 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -386,17 +386,10 @@ namespace osu.Game.Beatmaps.Formats
SampleVolume = sampleVolume,
CustomSampleBank = customSampleBank,
}, timingChange);
-
- // To handle the scenario where a non-timing line shares the same time value as a subsequent timing line but
- // appears earlier in the file, we buffer non-timing control points and rewrite them *after* control points from the timing line
- // with the same time value (allowing them to overwrite as necessary).
- //
- // The expected outcome is that we prefer the non-timing line's adjustments over the timing line's adjustments when time is equal.
- if (timingChange)
- flushPendingPoints();
}
private readonly List pendingControlPoints = new List();
+ private readonly HashSet pendingControlPointTypes = new HashSet();
private double pendingControlPointsTime;
private void addControlPoint(double time, ControlPoint point, bool timingChange)
@@ -405,21 +398,28 @@ namespace osu.Game.Beatmaps.Formats
flushPendingPoints();
if (timingChange)
- {
- beatmap.ControlPointInfo.Add(time, point);
- return;
- }
+ pendingControlPoints.Insert(0, point);
+ else
+ pendingControlPoints.Add(point);
- pendingControlPoints.Add(point);
pendingControlPointsTime = time;
}
private void flushPendingPoints()
{
- foreach (var p in pendingControlPoints)
- beatmap.ControlPointInfo.Add(pendingControlPointsTime, p);
+ // Changes from non-timing-points are added to the end of the list (see addControlPoint()) and should override any changes from timing-points (added to the start of the list).
+ for (int i = pendingControlPoints.Count - 1; i >= 0; i--)
+ {
+ var type = pendingControlPoints[i].GetType();
+ if (pendingControlPointTypes.Contains(type))
+ continue;
+
+ pendingControlPointTypes.Add(type);
+ beatmap.ControlPointInfo.Add(pendingControlPointsTime, pendingControlPoints[i]);
+ }
pendingControlPoints.Clear();
+ pendingControlPointTypes.Clear();
}
private void handleHitObject(string line)
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
index 12f2c58e35..44ccbb350d 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
@@ -10,6 +11,7 @@ using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Beatmaps.Formats
@@ -48,7 +50,7 @@ namespace osu.Game.Beatmaps.Formats
handleEvents(writer);
writer.WriteLine();
- handleTimingPoints(writer);
+ handleControlPoints(writer);
writer.WriteLine();
handleHitObjects(writer);
@@ -58,7 +60,7 @@ namespace osu.Game.Beatmaps.Formats
{
writer.WriteLine("[General]");
- writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}"));
+ if (beatmap.Metadata.AudioFile != null) writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}"));
writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}"));
writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}"));
// Todo: Not all countdown types are supported by lazer yet
@@ -103,15 +105,15 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine("[Metadata]");
writer.WriteLine(FormattableString.Invariant($"Title: {beatmap.Metadata.Title}"));
- writer.WriteLine(FormattableString.Invariant($"TitleUnicode: {beatmap.Metadata.TitleUnicode}"));
+ if (beatmap.Metadata.TitleUnicode != null) writer.WriteLine(FormattableString.Invariant($"TitleUnicode: {beatmap.Metadata.TitleUnicode}"));
writer.WriteLine(FormattableString.Invariant($"Artist: {beatmap.Metadata.Artist}"));
- writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}"));
+ if (beatmap.Metadata.ArtistUnicode != null) writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}"));
writer.WriteLine(FormattableString.Invariant($"Creator: {beatmap.Metadata.AuthorString}"));
writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.Version}"));
- writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}"));
- writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}"));
- writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID ?? 0}"));
- writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID ?? -1}"));
+ if (beatmap.Metadata.Source != null) writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}"));
+ if (beatmap.Metadata.Tags != null) writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}"));
+ if (beatmap.BeatmapInfo.OnlineBeatmapID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID}"));
+ if (beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID}"));
}
private void handleDifficulty(TextWriter writer)
@@ -137,7 +139,7 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}"));
}
- private void handleTimingPoints(TextWriter writer)
+ private void handleControlPoints(TextWriter writer)
{
if (beatmap.ControlPointInfo.Groups.Count == 0)
return;
@@ -146,20 +148,30 @@ namespace osu.Game.Beatmaps.Formats
foreach (var group in beatmap.ControlPointInfo.Groups)
{
- var timingPoint = group.ControlPoints.OfType().FirstOrDefault();
- var difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(group.Time);
- var samplePoint = beatmap.ControlPointInfo.SamplePointAt(group.Time);
- var effectPoint = beatmap.ControlPointInfo.EffectPointAt(group.Time);
+ var groupTimingPoint = group.ControlPoints.OfType().FirstOrDefault();
- // Convert beat length the legacy format
- double beatLength;
- if (timingPoint != null)
- beatLength = timingPoint.BeatLength;
- else
- beatLength = -100 / difficultyPoint.SpeedMultiplier;
+ // If the group contains a timing control point, it needs to be output separately.
+ if (groupTimingPoint != null)
+ {
+ writer.Write(FormattableString.Invariant($"{groupTimingPoint.Time},"));
+ writer.Write(FormattableString.Invariant($"{groupTimingPoint.BeatLength},"));
+ outputControlPointEffectsAt(groupTimingPoint.Time, true);
+ }
+
+ // Output any remaining effects as secondary non-timing control point.
+ var difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(group.Time);
+ writer.Write(FormattableString.Invariant($"{group.Time},"));
+ writer.Write(FormattableString.Invariant($"{-100 / difficultyPoint.SpeedMultiplier},"));
+ outputControlPointEffectsAt(group.Time, false);
+ }
+
+ void outputControlPointEffectsAt(double time, bool isTimingPoint)
+ {
+ var samplePoint = beatmap.ControlPointInfo.SamplePointAt(time);
+ var effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
// Apply the control point to a hit sample to uncover legacy properties (e.g. suffix)
- HitSampleInfo tempHitSample = samplePoint.ApplyTo(new HitSampleInfo());
+ HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo());
// Convert effect flags to the legacy format
LegacyEffectFlags effectFlags = LegacyEffectFlags.None;
@@ -168,13 +180,11 @@ namespace osu.Game.Beatmaps.Formats
if (effectPoint.OmitFirstBarLine)
effectFlags |= LegacyEffectFlags.OmitFirstBarLine;
- writer.Write(FormattableString.Invariant($"{group.Time},"));
- writer.Write(FormattableString.Invariant($"{beatLength},"));
- writer.Write(FormattableString.Invariant($"{(int)beatmap.ControlPointInfo.TimingPointAt(group.Time).TimeSignature},"));
+ writer.Write(FormattableString.Invariant($"{(int)beatmap.ControlPointInfo.TimingPointAt(time).TimeSignature},"));
writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},"));
- writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample.Suffix)},"));
+ writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},"));
writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},"));
- writer.Write(FormattableString.Invariant($"{(timingPoint != null ? '1' : '0')},"));
+ writer.Write(FormattableString.Invariant($"{(isTimingPoint ? '1' : '0')},"));
writer.Write(FormattableString.Invariant($"{(int)effectFlags}"));
writer.WriteLine();
}
@@ -187,27 +197,13 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine("[HitObjects]");
+ // TODO: implement other legacy rulesets
switch (beatmap.BeatmapInfo.RulesetID)
{
case 0:
foreach (var h in beatmap.HitObjects)
handleOsuHitObject(writer, h);
break;
-
- case 1:
- foreach (var h in beatmap.HitObjects)
- handleTaikoHitObject(writer, h);
- break;
-
- case 2:
- foreach (var h in beatmap.HitObjects)
- handleCatchHitObject(writer, h);
- break;
-
- case 3:
- foreach (var h in beatmap.HitObjects)
- handleManiaHitObject(writer, h);
- break;
}
}
@@ -254,7 +250,7 @@ namespace osu.Game.Beatmaps.Formats
break;
case IHasEndTime _:
- type |= LegacyHitObjectType.Spinner | LegacyHitObjectType.NewCombo;
+ type |= LegacyHitObjectType.Spinner;
break;
default:
@@ -328,12 +324,6 @@ namespace osu.Game.Beatmaps.Formats
}
}
- private void handleTaikoHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException();
-
- private void handleCatchHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException();
-
- private void handleManiaHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException();
-
private string getSampleBank(IList samples, bool banksOnly = false, bool zeroBanks = false)
{
LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank);
@@ -346,7 +336,7 @@ namespace osu.Game.Beatmaps.Formats
if (!banksOnly)
{
- string customSampleBank = toLegacyCustomSampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name))?.Suffix);
+ string customSampleBank = toLegacyCustomSampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name)));
string sampleFilename = samples.FirstOrDefault(s => string.IsNullOrEmpty(s.Name))?.LookupNames.First() ?? string.Empty;
int volume = samples.FirstOrDefault()?.Volume ?? 100;
@@ -402,6 +392,15 @@ namespace osu.Game.Beatmaps.Formats
}
}
- private string toLegacyCustomSampleBank(string sampleSuffix) => string.IsNullOrEmpty(sampleSuffix) ? "0" : sampleSuffix;
+ private string toLegacyCustomSampleBank(HitSampleInfo hitSampleInfo)
+ {
+ if (hitSampleInfo == null)
+ return "0";
+
+ if (hitSampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy)
+ return legacy.CustomSampleBank.ToString(CultureInfo.InvariantCulture);
+
+ return "0";
+ }
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index 5b2b213322..6406bd88a5 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -150,7 +150,8 @@ namespace osu.Game.Beatmaps.Formats
HitObjects,
Variables,
Fonts,
- Mania
+ CatchTheBeat,
+ Mania,
}
internal class LegacyDifficultyControlPoint : DifficultyControlPoint
@@ -178,9 +179,10 @@ namespace osu.Game.Beatmaps.Formats
return baseInfo;
}
- public override bool EquivalentTo(ControlPoint other) =>
- base.EquivalentTo(other) && other is LegacySampleControlPoint otherTyped &&
- CustomSampleBank == otherTyped.CustomSampleBank;
+ public override bool IsRedundant(ControlPoint existing)
+ => base.IsRedundant(existing)
+ && existing is LegacySampleControlPoint existingSample
+ && CustomSampleBank == existingSample.CustomSampleBank;
}
}
}
diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
index f36079682e..5a613d1a54 100644
--- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
+++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
@@ -103,7 +103,7 @@ namespace osu.Game.Graphics.Containers
TimeSinceLastBeat = beatLength - TimeUntilNextBeat;
- if (timingPoint.Equals(lastTimingPoint) && beatIndex == lastBeat)
+ if (timingPoint == lastTimingPoint && beatIndex == lastBeat)
return;
using (BeginDelayedSequence(-TimeSinceLastBeat, true))
diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs
index 6d244bff60..64f1ebeb1a 100644
--- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs
+++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs
@@ -43,13 +43,13 @@ namespace osu.Game.IO.Serialization.Converters
var list = new List();
var obj = JObject.Load(reader);
- var lookupTable = serializer.Deserialize>(obj["lookup_table"].CreateReader());
+ var lookupTable = serializer.Deserialize>(obj["$lookup_table"].CreateReader());
- foreach (var tok in obj["items"])
+ foreach (var tok in obj["$items"])
{
var itemReader = tok.CreateReader();
- var typeName = lookupTable[(int)tok["type"]];
+ var typeName = lookupTable[(int)tok["$type"]];
var instance = (T)Activator.CreateInstance(Type.GetType(typeName));
serializer.Populate(itemReader, instance);
@@ -61,7 +61,7 @@ namespace osu.Game.IO.Serialization.Converters
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
- var list = (List)value;
+ var list = (IEnumerable)value;
var lookupTable = new List();
var objects = new List();
@@ -84,16 +84,16 @@ namespace osu.Game.IO.Serialization.Converters
}
var itemObject = JObject.FromObject(item, serializer);
- itemObject.AddFirst(new JProperty("type", typeId));
+ itemObject.AddFirst(new JProperty("$type", typeId));
objects.Add(itemObject);
}
writer.WriteStartObject();
- writer.WritePropertyName("lookup_table");
+ writer.WritePropertyName("$lookup_table");
serializer.Serialize(writer, lookupTable);
- writer.WritePropertyName("items");
+ writer.WritePropertyName("$items");
serializer.Serialize(writer, objects);
writer.WriteEndObject();
diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs
index e83d899469..94edc33099 100644
--- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs
+++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs
@@ -62,6 +62,14 @@ namespace osu.Game.Input.Bindings
store.KeyBindingChanged -= ReloadMappings;
}
- protected override void ReloadMappings() => KeyBindings = store.Query(ruleset?.ID, variant).ToList();
+ protected override void ReloadMappings()
+ {
+ if (ruleset != null && !ruleset.ID.HasValue)
+ // if the provided ruleset is not stored to the database, we have no way to retrieve custom bindings.
+ // fallback to defaults instead.
+ KeyBindings = DefaultKeyBindings;
+ else
+ KeyBindings = store.Query(ruleset?.ID, variant).ToList();
+ }
}
}
diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs
index 47600e4f68..0bba04cac3 100644
--- a/osu.Game/Online/API/APIRequest.cs
+++ b/osu.Game/Online/API/APIRequest.cs
@@ -18,24 +18,32 @@ namespace osu.Game.Online.API
public T Result { get; private set; }
- protected APIRequest()
- {
- base.Success += () => TriggerSuccess(((OsuJsonWebRequest)WebRequest)?.ResponseObject);
- }
-
///
/// Invoked on successful completion of an API request.
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
///
public new event APISuccessHandler Success;
+ protected override void PostProcess()
+ {
+ base.PostProcess();
+ Result = ((OsuJsonWebRequest)WebRequest)?.ResponseObject;
+ }
+
internal void TriggerSuccess(T result)
{
if (Result != null)
throw new InvalidOperationException("Attempted to trigger success more than once");
Result = result;
- Success?.Invoke(result);
+
+ TriggerSuccess();
+ }
+
+ internal override void TriggerSuccess()
+ {
+ base.TriggerSuccess();
+ Success?.Invoke(Result);
}
}
@@ -99,6 +107,8 @@ namespace osu.Game.Online.API
if (checkAndScheduleFailure())
return;
+ PostProcess();
+
API.Schedule(delegate
{
if (cancelled) return;
@@ -107,7 +117,14 @@ namespace osu.Game.Online.API
});
}
- internal void TriggerSuccess()
+ ///
+ /// Perform any post-processing actions after a successful request.
+ ///
+ protected virtual void PostProcess()
+ {
+ }
+
+ internal virtual void TriggerSuccess()
{
Success?.Invoke();
}
diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
index 930ca8fdf1..047496b473 100644
--- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
+++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
@@ -1,30 +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 System.ComponentModel;
using osu.Framework.IO.Network;
using osu.Game.Overlays;
-using osu.Game.Overlays.Direct;
+using osu.Game.Overlays.BeatmapListing;
using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests
{
public class SearchBeatmapSetsRequest : APIRequest
{
+ public SearchCategory SearchCategory { get; set; }
+
+ public SortCriteria SortCriteria { get; set; }
+
+ public SortDirection SortDirection { get; set; }
+
+ public SearchGenre Genre { get; set; }
+
+ public SearchLanguage Language { get; set; }
+
private readonly string query;
private readonly RulesetInfo ruleset;
- private readonly BeatmapSearchCategory searchCategory;
- private readonly DirectSortCriteria sortCriteria;
- private readonly SortDirection direction;
- private string directionString => direction == SortDirection.Descending ? @"desc" : @"asc";
- public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, BeatmapSearchCategory searchCategory = BeatmapSearchCategory.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending)
+ private string directionString => SortDirection == SortDirection.Descending ? @"desc" : @"asc";
+
+ public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset)
{
this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query);
this.ruleset = ruleset;
- this.searchCategory = searchCategory;
- this.sortCriteria = sortCriteria;
- this.direction = direction;
+
+ SearchCategory = SearchCategory.Any;
+ SortCriteria = SortCriteria.Ranked;
+ SortDirection = SortDirection.Descending;
+ Genre = SearchGenre.Any;
+ Language = SearchLanguage.Any;
}
protected override WebRequest CreateWebRequest()
@@ -35,31 +45,19 @@ namespace osu.Game.Online.API.Requests
if (ruleset.ID.HasValue)
req.AddParameter("m", ruleset.ID.Value.ToString());
- req.AddParameter("s", searchCategory.ToString().ToLowerInvariant());
- req.AddParameter("sort", $"{sortCriteria.ToString().ToLowerInvariant()}_{directionString}");
+ req.AddParameter("s", SearchCategory.ToString().ToLowerInvariant());
+
+ if (Genre != SearchGenre.Any)
+ req.AddParameter("g", ((int)Genre).ToString());
+
+ if (Language != SearchLanguage.Any)
+ req.AddParameter("l", ((int)Language).ToString());
+
+ req.AddParameter("sort", $"{SortCriteria.ToString().ToLowerInvariant()}_{directionString}");
return req;
}
protected override string Target => @"beatmapsets/search";
}
-
- public enum BeatmapSearchCategory
- {
- Any,
-
- [Description("Has Leaderboard")]
- Leaderboard,
- Ranked,
- Qualified,
- Loved,
- Favourites,
-
- [Description("Pending & WIP")]
- Pending,
- Graveyard,
-
- [Description("My Maps")]
- Mine,
- }
}
diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs
index 2c37216fd6..822f628dd2 100644
--- a/osu.Game/Online/Chat/ChannelManager.cs
+++ b/osu.Game/Online/Chat/ChannelManager.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Online.Chat
///
/// Manages everything channel related
///
- public class ChannelManager : PollingComponent
+ public class ChannelManager : PollingComponent, IChannelPostTarget
{
///
/// The channels the player joins on startup
@@ -204,6 +204,10 @@ namespace osu.Game.Online.Chat
switch (command)
{
+ case "np":
+ AddInternal(new NowPlayingCommand());
+ break;
+
case "me":
if (string.IsNullOrWhiteSpace(content))
{
@@ -234,7 +238,7 @@ namespace osu.Game.Online.Chat
break;
case "help":
- target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel]"));
+ target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /np"));
break;
default:
diff --git a/osu.Game/Online/Chat/IChannelPostTarget.cs b/osu.Game/Online/Chat/IChannelPostTarget.cs
new file mode 100644
index 0000000000..5697e918f0
--- /dev/null
+++ b/osu.Game/Online/Chat/IChannelPostTarget.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+
+namespace osu.Game.Online.Chat
+{
+ [Cached(typeof(IChannelPostTarget))]
+ public interface IChannelPostTarget
+ {
+ ///
+ /// Posts a message to the currently opened channel.
+ ///
+ /// The message text that is going to be posted
+ /// Is true if the message is an action, e.g.: user is currently eating
+ /// An optional target channel. If null, will be used.
+ void PostMessage(string text, bool isAction = false, Channel target = null);
+ }
+}
diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs
new file mode 100644
index 0000000000..c0b54812b6
--- /dev/null
+++ b/osu.Game/Online/Chat/NowPlayingCommand.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.Beatmaps;
+using osu.Game.Online.API;
+using osu.Game.Users;
+
+namespace osu.Game.Online.Chat
+{
+ public class NowPlayingCommand : Component
+ {
+ [Resolved]
+ private IChannelPostTarget channelManager { get; set; }
+
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
+ [Resolved]
+ private Bindable currentBeatmap { get; set; }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ string verb;
+ BeatmapInfo beatmap;
+
+ switch (api.Activity.Value)
+ {
+ case UserActivity.SoloGame solo:
+ verb = "playing";
+ beatmap = solo.Beatmap;
+ break;
+
+ case UserActivity.Editing edit:
+ verb = "editing";
+ beatmap = edit.Beatmap;
+ break;
+
+ default:
+ verb = "listening to";
+ beatmap = currentBeatmap.Value.BeatmapInfo;
+ break;
+ }
+
+ var beatmapString = beatmap.OnlineBeatmapID.HasValue ? $"[https://osu.ppy.sh/b/{beatmap.OnlineBeatmapID} {beatmap}]" : beatmap.ToString();
+
+ channelManager.PostMessage($"is {verb} {beatmapString}", true);
+ Expire();
+ }
+ }
+}
diff --git a/osu.Game/Online/PollingComponent.cs b/osu.Game/Online/PollingComponent.cs
index acbb2c39f4..228f147835 100644
--- a/osu.Game/Online/PollingComponent.cs
+++ b/osu.Game/Online/PollingComponent.cs
@@ -3,7 +3,7 @@
using System;
using System.Threading.Tasks;
-using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Threading;
namespace osu.Game.Online
@@ -11,7 +11,7 @@ namespace osu.Game.Online
///
/// A component which requires a constant polling process.
///
- public abstract class PollingComponent : Component
+ public abstract class PollingComponent : CompositeDrawable // switch away from Component because InternalChildren are used in usages.
{
private double? lastTimePolled;
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 5e93d760e3..f5f7d0cef4 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -65,9 +65,9 @@ namespace osu.Game
private NowPlayingOverlay nowPlaying;
- private DirectOverlay direct;
+ private BeatmapListingOverlay beatmapListing;
- private SocialOverlay social;
+ private DashboardOverlay dashboard;
private UserProfileOverlay userProfile;
@@ -610,8 +610,8 @@ namespace osu.Game
loadComponentSingleFile(screenshotManager, Add);
//overlay elements
- loadComponentSingleFile(direct = new DirectOverlay(), overlayContent.Add, true);
- loadComponentSingleFile(social = new SocialOverlay(), overlayContent.Add, true);
+ loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true);
+ loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true);
var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true);
loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true);
loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true);
@@ -670,7 +670,7 @@ namespace osu.Game
}
// ensure only one of these overlays are open at once.
- var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, social, direct, changelogOverlay, rankingsOverlay };
+ var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, dashboard, beatmapListing, changelogOverlay, rankingsOverlay };
foreach (var overlay in singleDisplayOverlays)
{
@@ -842,7 +842,7 @@ namespace osu.Game
return true;
case GlobalAction.ToggleSocial:
- social.ToggleVisibility();
+ dashboard.ToggleVisibility();
return true;
case GlobalAction.ResetInputSettings:
@@ -865,7 +865,7 @@ namespace osu.Game
return true;
case GlobalAction.ToggleDirect:
- direct.ToggleVisibility();
+ beatmapListing.ToggleVisibility();
return true;
case GlobalAction.ToggleGameplayMouseButtons:
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 5487bd9320..609b6ce98e 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -168,7 +168,7 @@ namespace osu.Game
var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures);
- dependencies.Cache(RulesetStore = new RulesetStore(contextFactory));
+ dependencies.Cache(RulesetStore = new RulesetStore(contextFactory, Storage));
dependencies.Cache(FileStore = new FileStore(contextFactory, Storage));
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
diff --git a/osu.Game/Overlays/Direct/BeatmapDownloadTrackingComposite.cs b/osu.Game/Overlays/BeatmapDownloadTrackingComposite.cs
similarity index 94%
rename from osu.Game/Overlays/Direct/BeatmapDownloadTrackingComposite.cs
rename to osu.Game/Overlays/BeatmapDownloadTrackingComposite.cs
index fd04a1541e..f6b5b181c3 100644
--- a/osu.Game/Overlays/Direct/BeatmapDownloadTrackingComposite.cs
+++ b/osu.Game/Overlays/BeatmapDownloadTrackingComposite.cs
@@ -5,7 +5,7 @@ using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Online;
-namespace osu.Game.Overlays.Direct
+namespace osu.Game.Overlays
{
public abstract class BeatmapDownloadTrackingComposite : DownloadTrackingComposite
{
diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs
new file mode 100644
index 0000000000..4dd60c7113
--- /dev/null
+++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs
@@ -0,0 +1,164 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Threading;
+using osu.Game.Beatmaps;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Rulesets;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Overlays.BeatmapListing
+{
+ public class BeatmapListingFilterControl : CompositeDrawable
+ {
+ public Action> SearchFinished;
+ public Action SearchStarted;
+
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
+ [Resolved]
+ private RulesetStore rulesets { get; set; }
+
+ private readonly BeatmapListingSearchControl searchControl;
+ private readonly BeatmapListingSortTabControl sortControl;
+ private readonly Box sortControlBackground;
+
+ private SearchBeatmapSetsRequest getSetsRequest;
+
+ public BeatmapListingFilterControl()
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ InternalChild = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 10),
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Masking = true,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Colour = Color4.Black.Opacity(0.25f),
+ Type = EdgeEffectType.Shadow,
+ Radius = 3,
+ Offset = new Vector2(0f, 1f),
+ },
+ Child = searchControl = new BeatmapListingSearchControl(),
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 40,
+ Children = new Drawable[]
+ {
+ sortControlBackground = new Box
+ {
+ RelativeSizeAxes = Axes.Both
+ },
+ sortControl = new BeatmapListingSortTabControl
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Margin = new MarginPadding { Left = 20 }
+ }
+ }
+ }
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OverlayColourProvider colourProvider)
+ {
+ sortControlBackground.Colour = colourProvider.Background5;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ var sortCriteria = sortControl.Current;
+ var sortDirection = sortControl.SortDirection;
+
+ searchControl.Query.BindValueChanged(query =>
+ {
+ sortCriteria.Value = string.IsNullOrEmpty(query.NewValue) ? SortCriteria.Ranked : SortCriteria.Relevance;
+ sortDirection.Value = SortDirection.Descending;
+ queueUpdateSearch(true);
+ });
+
+ searchControl.Ruleset.BindValueChanged(_ => queueUpdateSearch());
+ searchControl.Category.BindValueChanged(_ => queueUpdateSearch());
+ searchControl.Genre.BindValueChanged(_ => queueUpdateSearch());
+ searchControl.Language.BindValueChanged(_ => queueUpdateSearch());
+
+ sortCriteria.BindValueChanged(_ => queueUpdateSearch());
+ sortDirection.BindValueChanged(_ => queueUpdateSearch());
+ }
+
+ private ScheduledDelegate queryChangedDebounce;
+
+ private void queueUpdateSearch(bool queryTextChanged = false)
+ {
+ SearchStarted?.Invoke();
+
+ getSetsRequest?.Cancel();
+
+ queryChangedDebounce?.Cancel();
+ queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100);
+ }
+
+ private void updateSearch()
+ {
+ getSetsRequest = new SearchBeatmapSetsRequest(searchControl.Query.Value, searchControl.Ruleset.Value)
+ {
+ SearchCategory = searchControl.Category.Value,
+ SortCriteria = sortControl.Current.Value,
+ SortDirection = sortControl.SortDirection.Value,
+ Genre = searchControl.Genre.Value,
+ Language = searchControl.Language.Value
+ };
+
+ getSetsRequest.Success += response => Schedule(() => onSearchFinished(response));
+
+ api.Queue(getSetsRequest);
+ }
+
+ private void onSearchFinished(SearchBeatmapSetsResponse response)
+ {
+ var beatmaps = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList();
+
+ searchControl.BeatmapSet = response.Total == 0 ? null : beatmaps.First();
+
+ SearchFinished?.Invoke(beatmaps);
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ getSetsRequest?.Cancel();
+ queryChangedDebounce?.Cancel();
+
+ base.Dispose(isDisposing);
+ }
+
+ public void TakeFocus() => searchControl.TakeFocus();
+ }
+}
diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs
similarity index 81%
rename from osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs
rename to osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs
index f9799d8a6b..29c4fe0d2e 100644
--- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs
+++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs
@@ -5,8 +5,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Online.API.Requests;
-using osu.Game.Rulesets;
using osuTK;
using osu.Framework.Bindables;
using osu.Game.Beatmaps.Drawables;
@@ -14,16 +12,21 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osuTK.Graphics;
+using osu.Game.Rulesets;
namespace osu.Game.Overlays.BeatmapListing
{
- public class BeatmapListingSearchSection : CompositeDrawable
+ public class BeatmapListingSearchControl : CompositeDrawable
{
public Bindable Query => textBox.Current;
public Bindable Ruleset => modeFilter.Current;
- public Bindable Category => categoryFilter.Current;
+ public Bindable Category => categoryFilter.Current;
+
+ public Bindable Genre => genreFilter.Current;
+
+ public Bindable Language => languageFilter.Current;
public BeatmapSetInfo BeatmapSet
{
@@ -42,12 +45,14 @@ namespace osu.Game.Overlays.BeatmapListing
private readonly BeatmapSearchTextBox textBox;
private readonly BeatmapSearchRulesetFilterRow modeFilter;
- private readonly BeatmapSearchFilterRow categoryFilter;
+ private readonly BeatmapSearchFilterRow categoryFilter;
+ private readonly BeatmapSearchFilterRow genreFilter;
+ private readonly BeatmapSearchFilterRow languageFilter;
private readonly Box background;
private readonly UpdateableBeatmapSetCover beatmapCover;
- public BeatmapListingSearchSection()
+ public BeatmapListingSearchControl()
{
AutoSizeAxes = Axes.Y;
RelativeSizeAxes = Axes.X;
@@ -97,7 +102,9 @@ namespace osu.Game.Overlays.BeatmapListing
Children = new Drawable[]
{
modeFilter = new BeatmapSearchRulesetFilterRow(),
- categoryFilter = new BeatmapSearchFilterRow(@"Categories"),
+ categoryFilter = new BeatmapSearchFilterRow(@"Categories"),
+ genreFilter = new BeatmapSearchFilterRow(@"Genre"),
+ languageFilter = new BeatmapSearchFilterRow(@"Language"),
}
}
}
@@ -105,7 +112,7 @@ namespace osu.Game.Overlays.BeatmapListing
}
});
- Category.Value = BeatmapSearchCategory.Leaderboard;
+ categoryFilter.Current.Value = SearchCategory.Leaderboard;
}
[BackgroundDependencyLoader]
@@ -114,6 +121,8 @@ namespace osu.Game.Overlays.BeatmapListing
background.Colour = colourProvider.Dark6;
}
+ public void TakeFocus() => textBox.TakeFocus();
+
private class BeatmapSearchTextBox : SearchTextBox
{
protected override Color4 SelectionColour => Color4.Gray;
diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs
index 27c43b092a..4c77a736ac 100644
--- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs
+++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs
@@ -8,17 +8,16 @@ using osu.Framework.Graphics;
using osuTK.Graphics;
using osuTK;
using osu.Framework.Input.Events;
-using osu.Game.Overlays.Direct;
namespace osu.Game.Overlays.BeatmapListing
{
- public class BeatmapListingSortTabControl : OverlaySortTabControl
+ public class BeatmapListingSortTabControl : OverlaySortTabControl
{
public readonly Bindable SortDirection = new Bindable(Overlays.SortDirection.Descending);
public BeatmapListingSortTabControl()
{
- Current.Value = DirectSortCriteria.Ranked;
+ Current.Value = SortCriteria.Ranked;
}
protected override SortTabControl CreateControl() => new BeatmapSortTabControl
@@ -30,7 +29,7 @@ namespace osu.Game.Overlays.BeatmapListing
{
public readonly Bindable SortDirection = new Bindable();
- protected override TabItem CreateTabItem(DirectSortCriteria value) => new BeatmapSortTabItem(value)
+ protected override TabItem CreateTabItem(SortCriteria value) => new BeatmapSortTabItem(value)
{
SortDirection = { BindTarget = SortDirection }
};
@@ -40,12 +39,12 @@ namespace osu.Game.Overlays.BeatmapListing
{
public readonly Bindable SortDirection = new Bindable();
- public BeatmapSortTabItem(DirectSortCriteria value)
+ public BeatmapSortTabItem(SortCriteria value)
: base(value)
{
}
- protected override TabButton CreateTabButton(DirectSortCriteria value) => new BeatmapTabButton(value)
+ protected override TabButton CreateTabButton(SortCriteria value) => new BeatmapTabButton(value)
{
Active = { BindTarget = Active },
SortDirection = { BindTarget = SortDirection }
@@ -67,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapListing
private readonly SpriteIcon icon;
- public BeatmapTabButton(DirectSortCriteria value)
+ public BeatmapTabButton(SortCriteria value)
: base(value)
{
Add(icon = new SpriteIcon
diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs
index 2c046a2bbf..64b3afcae1 100644
--- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs
+++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs
@@ -15,6 +15,8 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
+using Humanizer;
+using osu.Game.Utils;
namespace osu.Game.Overlays.BeatmapListing
{
@@ -53,8 +55,8 @@ namespace osu.Game.Overlays.BeatmapListing
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
- Font = OsuFont.GetFont(size: 10),
- Text = headerName.ToUpper()
+ Font = OsuFont.GetFont(size: 13),
+ Text = headerName.Titleize()
},
CreateFilter().With(f =>
{
@@ -81,7 +83,7 @@ namespace osu.Game.Overlays.BeatmapListing
if (typeof(T).IsEnum)
{
- foreach (var val in (T[])Enum.GetValues(typeof(T)))
+ foreach (var val in OrderAttributeUtils.GetValuesInOrder())
AddItem(val);
}
}
diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchSmallFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchSmallFilterRow.cs
deleted file mode 100644
index 6daa7cb0e0..0000000000
--- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchSmallFilterRow.cs
+++ /dev/null
@@ -1,32 +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.Graphics.UserInterface;
-
-namespace osu.Game.Overlays.BeatmapListing
-{
- public class BeatmapSearchSmallFilterRow : BeatmapSearchFilterRow
- {
- public BeatmapSearchSmallFilterRow(string headerName)
- : base(headerName)
- {
- }
-
- protected override BeatmapSearchFilter CreateFilter() => new SmallBeatmapSearchFilter();
-
- private class SmallBeatmapSearchFilter : BeatmapSearchFilter
- {
- protected override TabItem CreateTabItem(T value) => new SmallTabItem(value);
-
- private class SmallTabItem : FilterTabItem
- {
- public SmallTabItem(T value)
- : base(value)
- {
- }
-
- protected override float TextSize => 10;
- }
- }
- }
-}
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs
similarity index 96%
rename from osu.Game/Overlays/Direct/DirectPanel.cs
rename to osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs
index 4ad8e95512..88c15776cd 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs
@@ -26,9 +26,9 @@ using osu.Game.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
-namespace osu.Game.Overlays.Direct
+namespace osu.Game.Overlays.BeatmapListing.Panels
{
- public abstract class DirectPanel : OsuClickableContainer, IHasContextMenu
+ public abstract class BeatmapPanel : OsuClickableContainer, IHasContextMenu
{
public readonly BeatmapSetInfo SetInfo;
@@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Direct
protected Action ViewBeatmap;
- protected DirectPanel(BeatmapSetInfo setInfo)
+ protected BeatmapPanel(BeatmapSetInfo setInfo)
{
Debug.Assert(setInfo.OnlineBeatmapSetID != null);
@@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Direct
if (SetInfo.Beatmaps.Count > maximum_difficulty_icons)
{
foreach (var ruleset in SetInfo.Beatmaps.Select(b => b.Ruleset).Distinct())
- icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is DirectListPanel ? Color4.White : colours.Gray5));
+ icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is ListBeatmapPanel ? Color4.White : colours.Gray5));
}
else
{
diff --git a/osu.Game/Overlays/Direct/PanelDownloadButton.cs b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs
similarity index 93%
rename from osu.Game/Overlays/Direct/PanelDownloadButton.cs
rename to osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs
index 387ced6acb..589f2d5072 100644
--- a/osu.Game/Overlays/Direct/PanelDownloadButton.cs
+++ b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs
@@ -11,9 +11,9 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
-namespace osu.Game.Overlays.Direct
+namespace osu.Game.Overlays.BeatmapListing.Panels
{
- public class PanelDownloadButton : BeatmapDownloadTrackingComposite
+ public class BeatmapPanelDownloadButton : BeatmapDownloadTrackingComposite
{
protected bool DownloadEnabled => button.Enabled.Value;
@@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Direct
private readonly DownloadButton button;
private Bindable noVideoSetting;
- public PanelDownloadButton(BeatmapSetInfo beatmapSet)
+ public BeatmapPanelDownloadButton(BeatmapSetInfo beatmapSet)
: base(beatmapSet)
{
InternalChild = shakeContainer = new ShakeContainer
diff --git a/osu.Game/Overlays/Direct/DownloadProgressBar.cs b/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs
similarity index 97%
rename from osu.Game/Overlays/Direct/DownloadProgressBar.cs
rename to osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs
index 9a8644efd2..93cf8799b5 100644
--- a/osu.Game/Overlays/Direct/DownloadProgressBar.cs
+++ b/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs
@@ -10,7 +10,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osuTK.Graphics;
-namespace osu.Game.Overlays.Direct
+namespace osu.Game.Overlays.BeatmapListing.Panels
{
public class DownloadProgressBar : BeatmapDownloadTrackingComposite
{
diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs
similarity index 97%
rename from osu.Game/Overlays/Direct/DirectGridPanel.cs
rename to osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs
index 2528ccec41..84d35da096 100644
--- a/osu.Game/Overlays/Direct/DirectGridPanel.cs
+++ b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs
@@ -1,25 +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 osuTK;
-using osuTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
+using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+using osuTK;
+using osuTK.Graphics;
-namespace osu.Game.Overlays.Direct
+namespace osu.Game.Overlays.BeatmapListing.Panels
{
- public class DirectGridPanel : DirectPanel
+ public class GridBeatmapPanel : BeatmapPanel
{
private const float horizontal_padding = 10;
private const float vertical_padding = 5;
@@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Direct
protected override PlayButton PlayButton => playButton;
protected override Box PreviewBar => progressBar;
- public DirectGridPanel(BeatmapSetInfo beatmap)
+ public GridBeatmapPanel(BeatmapSetInfo beatmap)
: base(beatmap)
{
Width = 380;
@@ -156,7 +156,7 @@ namespace osu.Game.Overlays.Direct
},
},
},
- new PanelDownloadButton(SetInfo)
+ new BeatmapPanelDownloadButton(SetInfo)
{
Size = new Vector2(50, 30),
Margin = new MarginPadding(horizontal_padding),
diff --git a/osu.Game/Overlays/Direct/IconPill.cs b/osu.Game/Overlays/BeatmapListing/Panels/IconPill.cs
similarity index 96%
rename from osu.Game/Overlays/Direct/IconPill.cs
rename to osu.Game/Overlays/BeatmapListing/Panels/IconPill.cs
index d63bb2a292..1cb6c84f13 100644
--- a/osu.Game/Overlays/Direct/IconPill.cs
+++ b/osu.Game/Overlays/BeatmapListing/Panels/IconPill.cs
@@ -8,7 +8,7 @@ using osu.Framework.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
-namespace osu.Game.Overlays.Direct
+namespace osu.Game.Overlays.BeatmapListing.Panels
{
public class IconPill : CircularContainer
{
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs
similarity index 97%
rename from osu.Game/Overlays/Direct/DirectListPanel.cs
rename to osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs
index b64142dfe7..433ea37f06 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs
@@ -1,25 +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 osuTK;
-using osuTK.Graphics;
+using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Colour;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
-using osu.Framework.Allocation;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
+using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+using osuTK;
+using osuTK.Graphics;
-namespace osu.Game.Overlays.Direct
+namespace osu.Game.Overlays.BeatmapListing.Panels
{
- public class DirectListPanel : DirectPanel
+ public class ListBeatmapPanel : BeatmapPanel
{
private const float transition_duration = 120;
private const float horizontal_padding = 10;
@@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Direct
private const float height = 70;
private FillFlowContainer statusContainer;
- protected PanelDownloadButton DownloadButton;
+ protected BeatmapPanelDownloadButton DownloadButton;
private PlayButton playButton;
private Box progressBar;
@@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Direct
protected override PlayButton PlayButton => playButton;
protected override Box PreviewBar => progressBar;
- public DirectListPanel(BeatmapSetInfo beatmap)
+ public ListBeatmapPanel(BeatmapSetInfo beatmap)
: base(beatmap)
{
RelativeSizeAxes = Axes.X;
@@ -151,7 +151,7 @@ namespace osu.Game.Overlays.Direct
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
AutoSizeAxes = Axes.Both,
- Child = DownloadButton = new PanelDownloadButton(SetInfo)
+ Child = DownloadButton = new BeatmapPanelDownloadButton(SetInfo)
{
Size = new Vector2(height - vertical_padding * 3),
Margin = new MarginPadding { Left = vertical_padding * 2, Right = vertical_padding },
diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs
similarity index 98%
rename from osu.Game/Overlays/Direct/PlayButton.cs
rename to osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs
index d9f335b6a7..e95fdeecf4 100644
--- a/osu.Game/Overlays/Direct/PlayButton.cs
+++ b/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs
@@ -14,7 +14,7 @@ using osu.Game.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
-namespace osu.Game.Overlays.Direct
+namespace osu.Game.Overlays.BeatmapListing.Panels
{
public class PlayButton : Container
{
diff --git a/osu.Game/Overlays/BeatmapListing/SearchCategory.cs b/osu.Game/Overlays/BeatmapListing/SearchCategory.cs
new file mode 100644
index 0000000000..84859bf5b5
--- /dev/null
+++ b/osu.Game/Overlays/BeatmapListing/SearchCategory.cs
@@ -0,0 +1,26 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.ComponentModel;
+
+namespace osu.Game.Overlays.BeatmapListing
+{
+ public enum SearchCategory
+ {
+ Any,
+
+ [Description("Has Leaderboard")]
+ Leaderboard,
+ Ranked,
+ Qualified,
+ Loved,
+ Favourites,
+
+ [Description("Pending & WIP")]
+ Pending,
+ Graveyard,
+
+ [Description("My Maps")]
+ Mine,
+ }
+}
diff --git a/osu.Game/Overlays/BeatmapListing/SearchGenre.cs b/osu.Game/Overlays/BeatmapListing/SearchGenre.cs
new file mode 100644
index 0000000000..b12bba6249
--- /dev/null
+++ b/osu.Game/Overlays/BeatmapListing/SearchGenre.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 System.ComponentModel;
+
+namespace osu.Game.Overlays.BeatmapListing
+{
+ public enum SearchGenre
+ {
+ Any = 0,
+ Unspecified = 1,
+
+ [Description("Video Game")]
+ VideoGame = 2,
+ Anime = 3,
+ Rock = 4,
+ Pop = 5,
+ Other = 6,
+ Novelty = 7,
+
+ [Description("Hip Hop")]
+ HipHop = 9,
+ Electronic = 10
+ }
+}
diff --git a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs
new file mode 100644
index 0000000000..dac7e4f1a2
--- /dev/null
+++ b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs
@@ -0,0 +1,47 @@
+// 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.Utils;
+
+namespace osu.Game.Overlays.BeatmapListing
+{
+ [HasOrderedElements]
+ public enum SearchLanguage
+ {
+ [Order(0)]
+ Any,
+
+ [Order(11)]
+ Other,
+
+ [Order(1)]
+ English,
+
+ [Order(6)]
+ Japanese,
+
+ [Order(2)]
+ Chinese,
+
+ [Order(10)]
+ Instrumental,
+
+ [Order(7)]
+ Korean,
+
+ [Order(3)]
+ French,
+
+ [Order(4)]
+ German,
+
+ [Order(9)]
+ Swedish,
+
+ [Order(8)]
+ Spanish,
+
+ [Order(5)]
+ Italian
+ }
+}
diff --git a/osu.Game/Overlays/BeatmapListing/SortCriteria.cs b/osu.Game/Overlays/BeatmapListing/SortCriteria.cs
new file mode 100644
index 0000000000..e409cbdda7
--- /dev/null
+++ b/osu.Game/Overlays/BeatmapListing/SortCriteria.cs
@@ -0,0 +1,17 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Overlays.BeatmapListing
+{
+ public enum SortCriteria
+ {
+ Title,
+ Artist,
+ Difficulty,
+ Ranked,
+ Rating,
+ Plays,
+ Favourites,
+ Relevance
+ }
+}
diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs
index b450f33ee1..f680f7c67b 100644
--- a/osu.Game/Overlays/BeatmapListingOverlay.cs
+++ b/osu.Game/Overlays/BeatmapListingOverlay.cs
@@ -1,27 +1,24 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using System.Linq;
+using System.Threading;
using osu.Framework.Allocation;
-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.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
-using osu.Framework.Threading;
+using osu.Framework.Input.Events;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
-using osu.Game.Online.API.Requests;
using osu.Game.Overlays.BeatmapListing;
-using osu.Game.Overlays.Direct;
-using osu.Game.Rulesets;
+using osu.Game.Overlays.BeatmapListing.Panels;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Overlays
{
@@ -30,20 +27,17 @@ namespace osu.Game.Overlays
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; }
- [Resolved]
- private RulesetStore rulesets { get; set; }
-
- private SearchBeatmapSetsRequest getSetsRequest;
-
private Drawable currentContent;
- private BeatmapListingSearchSection searchSection;
- private BeatmapListingSortTabControl sortControl;
+ private LoadingLayer loadingLayer;
+ private Container panelTarget;
public BeatmapListingOverlay()
: base(OverlayColourScheme.Blue)
{
}
+ private BeatmapListingFilterControl filterControl;
+
[BackgroundDependencyLoader]
private void load()
{
@@ -63,27 +57,13 @@ namespace osu.Game.Overlays
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
- Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
- new FillFlowContainer
+ new BeatmapListingHeader(),
+ filterControl = new BeatmapListingFilterControl
{
- AutoSizeAxes = Axes.Y,
- RelativeSizeAxes = Axes.X,
- Direction = FillDirection.Vertical,
- Masking = true,
- EdgeEffect = new EdgeEffectParameters
- {
- Colour = Color4.Black.Opacity(0.25f),
- Type = EdgeEffectType.Shadow,
- Radius = 3,
- Offset = new Vector2(0f, 1f),
- },
- Children = new Drawable[]
- {
- new BeatmapListingHeader(),
- searchSection = new BeatmapListingSearchSection(),
- }
+ SearchStarted = onSearchStarted,
+ SearchFinished = onSearchFinished,
},
new Container
{
@@ -96,154 +76,70 @@ namespace osu.Game.Overlays
RelativeSizeAxes = Axes.Both,
Colour = ColourProvider.Background4,
},
- new FillFlowContainer
+ panelTarget = new Container
{
- RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- Children = new Drawable[]
- {
- new Container
- {
- RelativeSizeAxes = Axes.X,
- Height = 40,
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = ColourProvider.Background5
- },
- sortControl = new BeatmapListingSortTabControl
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Margin = new MarginPadding { Left = 20 }
- }
- }
- },
- new Container
- {
- AutoSizeAxes = Axes.Y,
- RelativeSizeAxes = Axes.X,
- Padding = new MarginPadding { Horizontal = 20 },
- Children = new Drawable[]
- {
- panelTarget = new Container
- {
- AutoSizeAxes = Axes.Y,
- RelativeSizeAxes = Axes.X,
- },
- loadingLayer = new LoadingLayer(panelTarget),
- }
- },
- }
- }
+ RelativeSizeAxes = Axes.X,
+ Padding = new MarginPadding { Horizontal = 20 }
+ },
+ loadingLayer = new LoadingLayer(panelTarget)
}
- }
+ },
}
}
}
};
}
- protected override void LoadComplete()
+ protected override void OnFocus(FocusEvent e)
{
- base.LoadComplete();
+ base.OnFocus(e);
- var sortCriteria = sortControl.Current;
- var sortDirection = sortControl.SortDirection;
-
- searchSection.Query.BindValueChanged(query =>
- {
- sortCriteria.Value = string.IsNullOrEmpty(query.NewValue) ? DirectSortCriteria.Ranked : DirectSortCriteria.Relevance;
- sortDirection.Value = SortDirection.Descending;
-
- queueUpdateSearch(true);
- });
-
- searchSection.Ruleset.BindValueChanged(_ => queueUpdateSearch());
- searchSection.Category.BindValueChanged(_ => queueUpdateSearch());
- sortCriteria.BindValueChanged(_ => queueUpdateSearch());
- sortDirection.BindValueChanged(_ => queueUpdateSearch());
+ filterControl.TakeFocus();
}
- private ScheduledDelegate queryChangedDebounce;
+ private CancellationTokenSource cancellationToken;
- private LoadingLayer loadingLayer;
- private Container panelTarget;
-
- private void queueUpdateSearch(bool queryTextChanged = false)
+ private void onSearchStarted()
{
- getSetsRequest?.Cancel();
-
- queryChangedDebounce?.Cancel();
- queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100);
- }
-
- private void updateSearch()
- {
- if (!IsLoaded)
- return;
-
- if (State.Value == Visibility.Hidden)
- return;
-
- if (API == null)
- return;
+ cancellationToken?.Cancel();
previewTrackManager.StopAnyPlaying(this);
- loadingLayer.Show();
-
- getSetsRequest = new SearchBeatmapSetsRequest(
- searchSection.Query.Value,
- searchSection.Ruleset.Value,
- searchSection.Category.Value,
- sortControl.Current.Value,
- sortControl.SortDirection.Value);
-
- getSetsRequest.Success += response => Schedule(() => recreatePanels(response));
-
- API.Queue(getSetsRequest);
+ if (panelTarget.Any())
+ loadingLayer.Show();
}
- private void recreatePanels(SearchBeatmapSetsResponse response)
+ private void onSearchFinished(List beatmaps)
{
- if (response.Total == 0)
+ if (!beatmaps.Any())
{
- searchSection.BeatmapSet = null;
- LoadComponentAsync(new NotFoundDrawable(), addContentToPlaceholder);
+ LoadComponentAsync(new NotFoundDrawable(), addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
return;
}
- var beatmaps = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList();
-
- var newPanels = new FillFlowContainer
+ var newPanels = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(10),
Alpha = 0,
Margin = new MarginPadding { Vertical = 15 },
- ChildrenEnumerable = beatmaps.Select(b => new DirectGridPanel(b)
+ ChildrenEnumerable = beatmaps.Select(b => new GridBeatmapPanel(b)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
})
};
- LoadComponentAsync(newPanels, loaded =>
- {
- addContentToPlaceholder(loaded);
- searchSection.BeatmapSet = beatmaps.First();
- });
+ LoadComponentAsync(newPanels, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
}
private void addContentToPlaceholder(Drawable content)
{
loadingLayer.Hide();
- Drawable lastContent = currentContent;
+ var lastContent = currentContent;
if (lastContent != null)
{
@@ -262,9 +158,7 @@ namespace osu.Game.Overlays
protected override void Dispose(bool isDisposing)
{
- getSetsRequest?.Cancel();
- queryChangedDebounce?.Cancel();
-
+ cancellationToken?.Cancel();
base.Dispose(isDisposing);
}
diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs
index e64256b850..56c0052bfe 100644
--- a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs
+++ b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs
@@ -13,7 +13,7 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online;
using osu.Game.Online.API;
-using osu.Game.Overlays.Direct;
+using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs
index 7eae05e4a9..6accce7d77 100644
--- a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs
+++ b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs
@@ -11,7 +11,7 @@ using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
-using osu.Game.Overlays.Direct;
+using osu.Game.Overlays.BeatmapListing.Panels;
using osuTK;
namespace osu.Game.Overlays.BeatmapSet.Buttons
diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs
index 11dc424183..17fa689cd2 100644
--- a/osu.Game/Overlays/BeatmapSet/Header.cs
+++ b/osu.Game/Overlays/BeatmapSet/Header.cs
@@ -15,8 +15,8 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
+using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Overlays.BeatmapSet.Buttons;
-using osu.Game.Overlays.Direct;
using osu.Game.Rulesets;
using osuTK;
using osuTK.Graphics;
@@ -274,7 +274,7 @@ namespace osu.Game.Overlays.BeatmapSet
{
case DownloadState.LocallyAvailable:
// temporary for UX until new design is implemented.
- downloadButtonsContainer.Child = new PanelDownloadButton(BeatmapSet.Value)
+ downloadButtonsContainer.Child = new BeatmapPanelDownloadButton(BeatmapSet.Value)
{
Width = 50,
RelativeSizeAxes = Axes.Y,
diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs
new file mode 100644
index 0000000000..9ee679a866
--- /dev/null
+++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs
@@ -0,0 +1,24 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Overlays.Dashboard
+{
+ public class DashboardOverlayHeader : TabControlOverlayHeader
+ {
+ protected override OverlayTitle CreateTitle() => new DashboardTitle();
+
+ private class DashboardTitle : OverlayTitle
+ {
+ public DashboardTitle()
+ {
+ Title = "dashboard";
+ IconTexture = "Icons/changelog";
+ }
+ }
+ }
+
+ public enum DashboardOverlayTabs
+ {
+ Friends
+ }
+}
diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
index 3c9b31daae..79fda99c73 100644
--- a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
+++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
@@ -16,7 +16,7 @@ using osuTK;
namespace osu.Game.Overlays.Dashboard.Friends
{
- public class FriendDisplay : CompositeDrawable
+ public class FriendDisplay : OverlayView>
{
private List users = new List();
@@ -26,34 +26,29 @@ namespace osu.Game.Overlays.Dashboard.Friends
set
{
users = value;
-
onlineStreamControl.Populate(value);
}
}
- [Resolved]
- private IAPIProvider api { get; set; }
-
- private GetFriendsRequest request;
private CancellationTokenSource cancellationToken;
private Drawable currentContent;
- private readonly FriendOnlineStreamControl onlineStreamControl;
- private readonly Box background;
- private readonly Box controlBackground;
- private readonly UserListToolbar userListToolbar;
- private readonly Container itemsPlaceholder;
- private readonly LoadingLayer loading;
+ private FriendOnlineStreamControl onlineStreamControl;
+ private Box background;
+ private Box controlBackground;
+ private UserListToolbar userListToolbar;
+ private Container itemsPlaceholder;
+ private LoadingLayer loading;
- public FriendDisplay()
+ [BackgroundDependencyLoader]
+ private void load(OverlayColourProvider colourProvider)
{
- RelativeSizeAxes = Axes.X;
- AutoSizeAxes = Axes.Y;
InternalChild = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new Container
@@ -134,11 +129,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
}
}
};
- }
- [BackgroundDependencyLoader]
- private void load(OverlayColourProvider colourProvider)
- {
background.Colour = colourProvider.Background4;
controlBackground.Colour = colourProvider.Background5;
}
@@ -152,14 +143,11 @@ namespace osu.Game.Overlays.Dashboard.Friends
userListToolbar.SortCriteria.BindValueChanged(_ => recreatePanels());
}
- public void Fetch()
- {
- if (!api.IsLoggedIn)
- return;
+ protected override APIRequest> CreateRequest() => new GetFriendsRequest();
- request = new GetFriendsRequest();
- request.Success += response => Schedule(() => Users = response);
- api.Queue(request);
+ protected override void OnSuccess(List response)
+ {
+ Users = response;
}
private void recreatePanels()
@@ -258,9 +246,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
protected override void Dispose(bool isDisposing)
{
- request?.Cancel();
cancellationToken?.Cancel();
-
base.Dispose(isDisposing);
}
}
diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs
new file mode 100644
index 0000000000..a72c3f4fa5
--- /dev/null
+++ b/osu.Game/Overlays/DashboardOverlay.cs
@@ -0,0 +1,150 @@
+// 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.Threading;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API;
+using osu.Game.Overlays.Dashboard;
+using osu.Game.Overlays.Dashboard.Friends;
+
+namespace osu.Game.Overlays
+{
+ public class DashboardOverlay : FullscreenOverlay
+ {
+ private CancellationTokenSource cancellationToken;
+
+ private Box background;
+ private Container content;
+ private DashboardOverlayHeader header;
+ private LoadingLayer loading;
+ private OverlayScrollContainer scrollFlow;
+
+ public DashboardOverlay()
+ : base(OverlayColourScheme.Purple)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Children = new Drawable[]
+ {
+ background = new Box
+ {
+ RelativeSizeAxes = Axes.Both
+ },
+ scrollFlow = new OverlayScrollContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ ScrollbarVisible = false,
+ Child = new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Y,
+ RelativeSizeAxes = Axes.X,
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ header = new DashboardOverlayHeader
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Depth = -float.MaxValue
+ },
+ content = new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y
+ }
+ }
+ }
+ },
+ loading = new LoadingLayer(content),
+ };
+
+ background.Colour = ColourProvider.Background5;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ header.Current.BindValueChanged(onTabChanged);
+ }
+
+ private bool displayUpdateRequired = true;
+
+ protected override void PopIn()
+ {
+ base.PopIn();
+
+ // We don't want to create a new display on every call, only when exiting from fully closed state.
+ if (displayUpdateRequired)
+ {
+ header.Current.TriggerChange();
+ displayUpdateRequired = false;
+ }
+ }
+
+ protected override void PopOutComplete()
+ {
+ base.PopOutComplete();
+ loadDisplay(Empty());
+ displayUpdateRequired = true;
+ }
+
+ private void loadDisplay(Drawable display)
+ {
+ scrollFlow.ScrollToStart();
+
+ LoadComponentAsync(display, loaded =>
+ {
+ if (API.IsLoggedIn)
+ loading.Hide();
+
+ content.Child = loaded;
+ }, (cancellationToken = new CancellationTokenSource()).Token);
+ }
+
+ private void onTabChanged(ValueChangedEvent tab)
+ {
+ cancellationToken?.Cancel();
+ loading.Show();
+
+ if (!API.IsLoggedIn)
+ {
+ loadDisplay(Empty());
+ return;
+ }
+
+ switch (tab.NewValue)
+ {
+ case DashboardOverlayTabs.Friends:
+ loadDisplay(new FriendDisplay());
+ break;
+
+ default:
+ throw new NotImplementedException($"Display for {tab.NewValue} tab is not implemented");
+ }
+ }
+
+ public override void APIStateChanged(IAPIProvider api, APIState state)
+ {
+ if (State.Value == Visibility.Hidden)
+ return;
+
+ header.Current.TriggerChange();
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ cancellationToken?.Cancel();
+ base.Dispose(isDisposing);
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Direct/DirectRulesetSelector.cs b/osu.Game/Overlays/Direct/DirectRulesetSelector.cs
deleted file mode 100644
index 106aaa616b..0000000000
--- a/osu.Game/Overlays/Direct/DirectRulesetSelector.cs
+++ /dev/null
@@ -1,93 +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.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.UserInterface;
-using osu.Framework.Input.Events;
-using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.UserInterface;
-using osu.Game.Rulesets;
-using osuTK;
-using osuTK.Graphics;
-
-namespace osu.Game.Overlays.Direct
-{
- public class DirectRulesetSelector : RulesetSelector
- {
- public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput;
-
- public override bool HandlePositionalInput => !Current.Disabled && base.HandlePositionalInput;
-
- public override bool PropagatePositionalInputSubTree => !Current.Disabled && base.PropagatePositionalInputSubTree;
-
- public DirectRulesetSelector()
- {
- TabContainer.Masking = false;
- TabContainer.Spacing = new Vector2(10, 0);
- AutoSizeAxes = Axes.Both;
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- Current.BindDisabledChanged(value => SelectedTab.FadeColour(value ? Color4.DarkGray : Color4.White, 200, Easing.OutQuint), true);
- }
-
- protected override TabItem CreateTabItem(RulesetInfo value) => new DirectRulesetTabItem(value);
-
- protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
- {
- Direction = FillDirection.Horizontal,
- AutoSizeAxes = Axes.Both,
- };
-
- private class DirectRulesetTabItem : TabItem
- {
- private readonly ConstrainedIconContainer iconContainer;
-
- public DirectRulesetTabItem(RulesetInfo value)
- : base(value)
- {
- AutoSizeAxes = Axes.Both;
-
- Children = new Drawable[]
- {
- iconContainer = new ConstrainedIconContainer
- {
- Icon = value.CreateInstance().CreateIcon(),
- Size = new Vector2(32),
- },
- new HoverClickSounds()
- };
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- updateState();
- }
-
- protected override bool OnHover(HoverEvent e)
- {
- base.OnHover(e);
- updateState();
- return true;
- }
-
- protected override void OnHoverLost(HoverLostEvent e)
- {
- base.OnHoverLost(e);
- updateState();
- }
-
- protected override void OnActivated() => updateState();
-
- protected override void OnDeactivated() => updateState();
-
- private void updateState() => iconContainer.FadeColour(IsHovered || Active.Value ? Color4.White : Color4.Gray, 120, Easing.InQuad);
- }
- }
-}
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
deleted file mode 100644
index e5b2b5cc34..0000000000
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ /dev/null
@@ -1,47 +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.Game.Graphics;
-using osu.Game.Online.API.Requests;
-using osu.Game.Overlays.SearchableList;
-using osu.Game.Rulesets;
-using osuTK.Graphics;
-
-namespace osu.Game.Overlays.Direct
-{
- public class FilterControl : SearchableListFilterControl
- {
- private DirectRulesetSelector rulesetSelector;
-
- protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"384552");
- protected override DirectSortCriteria DefaultTab => DirectSortCriteria.Ranked;
- protected override BeatmapSearchCategory DefaultCategory => BeatmapSearchCategory.Leaderboard;
-
- protected override Drawable CreateSupplementaryControls() => rulesetSelector = new DirectRulesetSelector();
-
- public Bindable Ruleset => rulesetSelector.Current;
-
- [BackgroundDependencyLoader(true)]
- private void load(OsuColour colours, Bindable ruleset)
- {
- DisplayStyleControl.Dropdown.AccentColour = colours.BlueDark;
- rulesetSelector.Current.BindTo(ruleset);
- }
- }
-
- public enum DirectSortCriteria
- {
- Title,
- Artist,
- Difficulty,
- Ranked,
- Rating,
- Plays,
- Favourites,
- Relevance,
- }
-}
diff --git a/osu.Game/Overlays/Direct/Header.cs b/osu.Game/Overlays/Direct/Header.cs
deleted file mode 100644
index 5b3e394a18..0000000000
--- a/osu.Game/Overlays/Direct/Header.cs
+++ /dev/null
@@ -1,43 +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.ComponentModel;
-using osu.Framework.Extensions.Color4Extensions;
-using osuTK.Graphics;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Sprites;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Overlays.SearchableList;
-
-namespace osu.Game.Overlays.Direct
-{
- public class Header : SearchableListHeader
- {
- protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"252f3a");
-
- protected override DirectTab DefaultTab => DirectTab.Search;
- protected override Drawable CreateHeaderText() => new OsuSpriteText { Text = @"osu!direct", Font = OsuFont.GetFont(size: 25) };
- protected override IconUsage Icon => OsuIcon.ChevronDownCircle;
-
- public Header()
- {
- Tabs.Current.Value = DirectTab.NewestMaps;
- Tabs.Current.TriggerChange();
- }
- }
-
- public enum DirectTab
- {
- Search,
-
- [Description("Newest Maps")]
- NewestMaps = DirectSortCriteria.Ranked,
-
- [Description("Top Rated")]
- TopRated = DirectSortCriteria.Rating,
-
- [Description("Most Played")]
- MostPlayed = DirectSortCriteria.Plays,
- }
-}
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
deleted file mode 100644
index 61986d1cf0..0000000000
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ /dev/null
@@ -1,298 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using Humanizer;
-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.Threading;
-using osu.Game.Audio;
-using osu.Game.Beatmaps;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Online.API.Requests;
-using osu.Game.Overlays.Direct;
-using osu.Game.Overlays.SearchableList;
-using osu.Game.Rulesets;
-using osuTK;
-using osuTK.Graphics;
-
-namespace osu.Game.Overlays
-{
- public class DirectOverlay : SearchableListOverlay
- {
- private const float panel_padding = 10f;
-
- [Resolved]
- private RulesetStore rulesets { get; set; }
-
- private readonly FillFlowContainer resultCountsContainer;
- private readonly OsuSpriteText resultCountsText;
- private FillFlowContainer panels;
-
- protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"485e74");
- protected override Color4 TrianglesColourLight => Color4Extensions.FromHex(@"465b71");
- protected override Color4 TrianglesColourDark => Color4Extensions.FromHex(@"3f5265");
-
- protected override SearchableListHeader CreateHeader() => new Header();
- protected override SearchableListFilterControl CreateFilterControl() => new FilterControl();
-
- private IEnumerable beatmapSets;
-
- public IEnumerable BeatmapSets
- {
- get => beatmapSets;
- set
- {
- if (ReferenceEquals(beatmapSets, value)) return;
-
- beatmapSets = value?.ToList();
-
- if (beatmapSets == null) return;
-
- var artists = new List();
- var songs = new List();
- var tags = new List();
-
- foreach (var s in beatmapSets)
- {
- artists.Add(s.Metadata.Artist);
- songs.Add(s.Metadata.Title);
- tags.AddRange(s.Metadata.Tags.Split(' '));
- }
-
- ResultAmounts = new ResultCounts(distinctCount(artists), distinctCount(songs), distinctCount(tags));
- }
- }
-
- private ResultCounts resultAmounts;
-
- public ResultCounts ResultAmounts
- {
- get => resultAmounts;
- set
- {
- if (value == ResultAmounts) return;
-
- resultAmounts = value;
-
- updateResultCounts();
- }
- }
-
- public DirectOverlay()
- : base(OverlayColourScheme.Blue)
- {
- ScrollFlow.Children = new Drawable[]
- {
- resultCountsContainer = new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Margin = new MarginPadding { Top = 5 },
- Children = new Drawable[]
- {
- new OsuSpriteText
- {
- Text = "Found ",
- Font = OsuFont.GetFont(size: 15)
- },
- resultCountsText = new OsuSpriteText
- {
- Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold)
- },
- }
- },
- };
-
- Filter.Search.Current.ValueChanged += text =>
- {
- if (!string.IsNullOrEmpty(text.NewValue))
- {
- Header.Tabs.Current.Value = DirectTab.Search;
-
- if (Filter.Tabs.Current.Value == DirectSortCriteria.Ranked)
- Filter.Tabs.Current.Value = DirectSortCriteria.Relevance;
- }
- else
- {
- Header.Tabs.Current.Value = DirectTab.NewestMaps;
-
- if (Filter.Tabs.Current.Value == DirectSortCriteria.Relevance)
- Filter.Tabs.Current.Value = DirectSortCriteria.Ranked;
- }
- };
- ((FilterControl)Filter).Ruleset.ValueChanged += _ => queueUpdateSearch();
- Filter.DisplayStyleControl.DisplayStyle.ValueChanged += style => recreatePanels(style.NewValue);
- Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => queueUpdateSearch();
-
- Header.Tabs.Current.ValueChanged += tab =>
- {
- if (tab.NewValue != DirectTab.Search)
- {
- currentQuery.Value = string.Empty;
- Filter.Tabs.Current.Value = (DirectSortCriteria)Header.Tabs.Current.Value;
- queueUpdateSearch();
- }
- };
-
- currentQuery.ValueChanged += text => queueUpdateSearch(!string.IsNullOrEmpty(text.NewValue));
-
- currentQuery.BindTo(Filter.Search.Current);
-
- Filter.Tabs.Current.ValueChanged += tab =>
- {
- if (Header.Tabs.Current.Value != DirectTab.Search && tab.NewValue != (DirectSortCriteria)Header.Tabs.Current.Value)
- Header.Tabs.Current.Value = DirectTab.Search;
-
- queueUpdateSearch();
- };
-
- updateResultCounts();
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- resultCountsContainer.Colour = colours.Yellow;
- }
-
- private void updateResultCounts()
- {
- resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, Easing.OutQuint);
- if (ResultAmounts == null) return;
-
- resultCountsText.Text = "Artist".ToQuantity(ResultAmounts.Artists) + ", " +
- "Song".ToQuantity(ResultAmounts.Songs) + ", " +
- "Tag".ToQuantity(ResultAmounts.Tags);
- }
-
- private void recreatePanels(PanelDisplayStyle displayStyle)
- {
- if (panels != null)
- {
- panels.FadeOut(200);
- panels.Expire();
- panels = null;
- }
-
- if (BeatmapSets == null) return;
-
- var newPanels = new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Spacing = new Vector2(panel_padding),
- Margin = new MarginPadding { Top = 10 },
- ChildrenEnumerable = BeatmapSets.Select(b =>
- {
- switch (displayStyle)
- {
- case PanelDisplayStyle.Grid:
- return new DirectGridPanel(b)
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- };
-
- default:
- return new DirectListPanel(b);
- }
- })
- };
-
- LoadComponentAsync(newPanels, p =>
- {
- if (panels != null) ScrollFlow.Remove(panels);
- ScrollFlow.Add(panels = newPanels);
- });
- }
-
- protected override void PopIn()
- {
- base.PopIn();
-
- // Queries are allowed to be run only on the first pop-in
- if (getSetsRequest == null)
- queueUpdateSearch();
- }
-
- private SearchBeatmapSetsRequest getSetsRequest;
-
- private readonly Bindable currentQuery = new Bindable(string.Empty);
-
- private ScheduledDelegate queryChangedDebounce;
-
- [Resolved]
- private PreviewTrackManager previewTrackManager { get; set; }
-
- private void queueUpdateSearch(bool queryTextChanged = false)
- {
- BeatmapSets = null;
- ResultAmounts = null;
-
- getSetsRequest?.Cancel();
-
- queryChangedDebounce?.Cancel();
- queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100);
- }
-
- private void updateSearch()
- {
- if (!IsLoaded)
- return;
-
- if (State.Value == Visibility.Hidden)
- return;
-
- if (API == null)
- return;
-
- previewTrackManager.StopAnyPlaying(this);
-
- getSetsRequest = new SearchBeatmapSetsRequest(
- currentQuery.Value,
- ((FilterControl)Filter).Ruleset.Value,
- Filter.DisplayStyleControl.Dropdown.Current.Value,
- Filter.Tabs.Current.Value); //todo: sort direction (?)
-
- getSetsRequest.Success += response =>
- {
- Task.Run(() =>
- {
- var sets = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList();
-
- // may not need scheduling; loads async internally.
- Schedule(() =>
- {
- BeatmapSets = sets;
- recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value);
- });
- });
- };
-
- API.Queue(getSetsRequest);
- }
-
- private int distinctCount(List list) => list.Distinct().ToArray().Length;
-
- public class ResultCounts
- {
- public readonly int Artists;
- public readonly int Songs;
- public readonly int Tags;
-
- public ResultCounts(int artists, int songs, int tags)
- {
- Artists = artists;
- Songs = songs;
- Tags = tags;
- }
- }
- }
-}
diff --git a/osu.Game/Overlays/OverlayHeader.cs b/osu.Game/Overlays/OverlayHeader.cs
index 4ac0f697c3..dbc934bde9 100644
--- a/osu.Game/Overlays/OverlayHeader.cs
+++ b/osu.Game/Overlays/OverlayHeader.cs
@@ -12,6 +12,8 @@ namespace osu.Game.Overlays
{
public abstract class OverlayHeader : Container
{
+ public const int CONTENT_X_MARGIN = 50;
+
private readonly Box titleBackground;
protected readonly FillFlowContainer HeaderInfo;
@@ -54,7 +56,7 @@ namespace osu.Game.Overlays
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding
{
- Horizontal = UserProfileOverlay.CONTENT_X_MARGIN,
+ Horizontal = CONTENT_X_MARGIN,
},
Children = new[]
{
diff --git a/osu.Game/Overlays/OverlayView.cs b/osu.Game/Overlays/OverlayView.cs
new file mode 100644
index 0000000000..3e2c54c726
--- /dev/null
+++ b/osu.Game/Overlays/OverlayView.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 osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Online.API;
+
+namespace osu.Game.Overlays
+{
+ ///
+ /// A subview containing online content, to be displayed inside a .
+ ///
+ ///
+ /// Automatically performs a data fetch on load.
+ ///
+ /// The type of the API response.
+ public abstract class OverlayView : CompositeDrawable, IOnlineComponent
+ where T : class
+ {
+ [Resolved]
+ protected IAPIProvider API { get; private set; }
+
+ private APIRequest request;
+
+ protected OverlayView()
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ API.Register(this);
+ }
+
+ ///
+ /// Create the API request for fetching data.
+ ///
+ protected abstract APIRequest CreateRequest();
+
+ ///
+ /// Fired when results arrive from the main API request.
+ ///
+ ///
+ protected abstract void OnSuccess(T response);
+
+ ///
+ /// Force a re-request for data from the API.
+ ///
+ protected void PerformFetch()
+ {
+ request?.Cancel();
+
+ request = CreateRequest();
+ request.Success += response => Schedule(() => OnSuccess(response));
+
+ API.Queue(request);
+ }
+
+ public virtual void APIStateChanged(IAPIProvider api, APIState state)
+ {
+ switch (state)
+ {
+ case APIState.Online:
+ PerformFetch();
+ break;
+ }
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ request?.Cancel();
+ API?.Unregister(this);
+ base.Dispose(isDisposing);
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs
index fcd12e2b54..191f3c908a 100644
--- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs
+++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs
@@ -7,7 +7,7 @@ using osu.Framework.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
-using osu.Game.Overlays.Direct;
+using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Users;
using osuTK;
@@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps
protected override Drawable CreateDrawableItem(APIBeatmapSet model) => !model.OnlineBeatmapSetID.HasValue
? null
- : new DirectGridPanel(model.ToBeatmapSet(Rulesets))
+ : new GridBeatmapPanel(model.ToBeatmapSet(Rulesets))
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs
index 6f06eecd6e..917509e842 100644
--- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs
+++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs
@@ -12,10 +12,10 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Rankings.Tables;
using System.Linq;
-using osu.Game.Overlays.Direct;
using System.Threading;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Overlays.BeatmapListing.Panels;
namespace osu.Game.Overlays.Rankings
{
@@ -140,7 +140,7 @@ namespace osu.Game.Overlays.Rankings
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Spacing = new Vector2(10),
- Children = response.BeatmapSets.Select(b => new DirectGridPanel(b.ToBeatmapSet(rulesets))
+ Children = response.BeatmapSets.Select(b => new GridBeatmapPanel(b.ToBeatmapSet(rulesets))
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs
index 02f7c9b0d3..9548573b4f 100644
--- a/osu.Game/Overlays/SocialOverlay.cs
+++ b/osu.Game/Overlays/SocialOverlay.cs
@@ -239,10 +239,4 @@ namespace osu.Game.Overlays
}
}
}
-
- public enum SortDirection
- {
- Ascending,
- Descending
- }
}
diff --git a/osu.Game/Overlays/SortDirection.cs b/osu.Game/Overlays/SortDirection.cs
new file mode 100644
index 0000000000..3af9614972
--- /dev/null
+++ b/osu.Game/Overlays/SortDirection.cs
@@ -0,0 +1,11 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Overlays
+{
+ public enum SortDirection
+ {
+ Ascending,
+ Descending
+ }
+}
diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs
index ab1a6aff78..e8e000f441 100644
--- a/osu.Game/Overlays/TabControlOverlayHeader.cs
+++ b/osu.Game/Overlays/TabControlOverlayHeader.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Overlays
},
TabControl = CreateTabControl().With(control =>
{
- control.Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN };
+ control.Margin = new MarginPadding { Left = CONTENT_X_MARGIN };
control.Current = Current;
})
}
diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs
index 897587d198..227347112c 100644
--- a/osu.Game/Overlays/Toolbar/Toolbar.cs
+++ b/osu.Game/Overlays/Toolbar/Toolbar.cs
@@ -71,7 +71,7 @@ namespace osu.Game.Overlays.Toolbar
{
new ToolbarChangelogButton(),
new ToolbarRankingsButton(),
- new ToolbarDirectButton(),
+ new ToolbarBeatmapListingButton(),
new ToolbarChatButton(),
new ToolbarSocialButton(),
new ToolbarMusicButton(),
diff --git a/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs
similarity index 63%
rename from osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs
rename to osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs
index 1d07a3ae70..eecb368ee9 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs
@@ -6,17 +6,17 @@ using osu.Game.Graphics;
namespace osu.Game.Overlays.Toolbar
{
- public class ToolbarDirectButton : ToolbarOverlayToggleButton
+ public class ToolbarBeatmapListingButton : ToolbarOverlayToggleButton
{
- public ToolbarDirectButton()
+ public ToolbarBeatmapListingButton()
{
SetIcon(OsuIcon.ChevronDownCircle);
}
[BackgroundDependencyLoader(true)]
- private void load(DirectOverlay direct)
+ private void load(BeatmapListingOverlay beatmapListing)
{
- StateContainer = direct;
+ StateContainer = beatmapListing;
}
}
}
diff --git a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs
index 5e353d3319..f6646eb81d 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs
@@ -14,9 +14,9 @@ namespace osu.Game.Overlays.Toolbar
}
[BackgroundDependencyLoader(true)]
- private void load(SocialOverlay chat)
+ private void load(DashboardOverlay dashboard)
{
- StateContainer = chat;
+ StateContainer = dashboard;
}
}
}
diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs
index 4e4a75db82..a1915b974c 100644
--- a/osu.Game/Rulesets/Mods/ModHidden.cs
+++ b/osu.Game/Rulesets/Mods/ModHidden.cs
@@ -23,6 +23,13 @@ namespace osu.Game.Rulesets.Mods
protected Bindable IncreaseFirstObjectVisibility = new Bindable();
+ ///
+ /// Check whether the provided hitobject should be considered the "first" hideable object.
+ /// Can be used to skip spinners, for instance.
+ ///
+ /// The hitobject to check.
+ protected virtual bool IsFirstHideableObject(DrawableHitObject hitObject) => true;
+
public void ReadFromConfig(OsuConfigManager config)
{
IncreaseFirstObjectVisibility = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility);
@@ -30,8 +37,11 @@ namespace osu.Game.Rulesets.Mods
public virtual void ApplyToDrawableHitObjects(IEnumerable drawables)
{
- foreach (var d in drawables.Skip(IncreaseFirstObjectVisibility.Value ? 1 : 0))
- d.ApplyCustomUpdateState += ApplyHiddenState;
+ if (IncreaseFirstObjectVisibility.Value)
+ drawables = drawables.SkipWhile(h => !IsFirstHideableObject(h)).Skip(1);
+
+ foreach (var dho in drawables)
+ dho.ApplyCustomUpdateState += ApplyHiddenState;
}
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index b14927bcd5..0047142cbd 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Reflection;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -180,11 +179,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
private void apply(HitObject hitObject)
{
-#pragma warning disable 618 // can be removed 20200417
- if (GetType().GetMethod(nameof(AddNested), BindingFlags.NonPublic | BindingFlags.Instance)?.DeclaringType != typeof(DrawableHitObject))
- return;
-#pragma warning restore 618
-
if (nestedHitObjects.IsValueCreated)
{
nestedHitObjects.Value.Clear();
@@ -195,7 +189,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
{
var drawableNested = CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}.");
- addNested(drawableNested);
+ drawableNested.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r);
+ drawableNested.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r);
+ drawableNested.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j);
+
+ nestedHitObjects.Value.Add(drawableNested);
AddNestedHitObject(drawableNested);
}
}
@@ -208,13 +206,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
{
}
- ///
- /// Adds a nested . This should not be used except for legacy nested usages.
- ///
- ///
- [Obsolete("Use AddNestedHitObject() / ClearNestedHitObjects() / CreateNestedHitObject() instead.")] // can be removed 20200417
- protected virtual void AddNested(DrawableHitObject h) => addNested(h);
-
///
/// Invoked by the base to remove all previously-added nested s.
///
@@ -229,17 +220,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// The drawable representation for .
protected virtual DrawableHitObject CreateNestedHitObject(HitObject hitObject) => null;
- private void addNested(DrawableHitObject hitObject)
- {
- // Todo: Exists for legacy purposes, can be removed 20200417
-
- hitObject.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r);
- hitObject.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r);
- hitObject.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j);
-
- nestedHitObjects.Value.Add(hitObject);
- }
-
#region State / Transform Management
///
@@ -398,7 +378,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
}
}
- public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => AllJudged && base.UpdateSubTreeMasking(source, maskingBounds);
+ public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false;
protected override void UpdateAfterChildren()
{
diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs
index a389d4ff75..543134cfb4 100644
--- a/osu.Game/Rulesets/RulesetStore.cs
+++ b/osu.Game/Rulesets/RulesetStore.cs
@@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using osu.Framework.Logging;
+using osu.Framework.Platform;
using osu.Game.Database;
namespace osu.Game.Rulesets
@@ -17,16 +18,24 @@ namespace osu.Game.Rulesets
private readonly Dictionary loadedAssemblies = new Dictionary();
- public RulesetStore(IDatabaseContextFactory factory)
+ private readonly Storage rulesetStorage;
+
+ public RulesetStore(IDatabaseContextFactory factory, Storage storage = null)
: base(factory)
{
+ rulesetStorage = storage?.GetStorageForDirectory("rulesets");
+
// On android in release configuration assemblies are loaded from the apk directly into memory.
// We cannot read assemblies from cwd, so should check loaded assemblies instead.
loadFromAppDomain();
loadFromDisk();
- addMissingRulesets();
- AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetAssembly;
+ // the event handler contains code for resolving dependency on the game assembly for rulesets located outside the base game directory.
+ // It needs to be attached to the assembly lookup event before the actual call to loadUserRulesets() else rulesets located out of the base game directory will fail
+ // to load as unable to locate the game core assembly.
+ AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetDependencyAssembly;
+ loadUserRulesets();
+ addMissingRulesets();
}
///
@@ -48,7 +57,21 @@ namespace osu.Game.Rulesets
///
public IEnumerable AvailableRulesets { get; private set; }
- private Assembly resolveRulesetAssembly(object sender, ResolveEventArgs args) => loadedAssemblies.Keys.FirstOrDefault(a => a.FullName == args.Name);
+ private Assembly resolveRulesetDependencyAssembly(object sender, ResolveEventArgs args)
+ {
+ var asm = new AssemblyName(args.Name);
+
+ // the requesting assembly may be located out of the executable's base directory, thus requiring manual resolving of its dependencies.
+ // this attempts resolving the ruleset dependencies on game core and framework assemblies by returning assemblies with the same assembly name
+ // already loaded in the AppDomain.
+ foreach (var curAsm in AppDomain.CurrentDomain.GetAssemblies())
+ {
+ if (asm.Name.Equals(curAsm.GetName().Name, StringComparison.Ordinal))
+ return curAsm;
+ }
+
+ return loadedAssemblies.Keys.FirstOrDefault(a => a.FullName == asm.FullName);
+ }
private void addMissingRulesets()
{
@@ -120,6 +143,16 @@ namespace osu.Game.Rulesets
}
}
+ private void loadUserRulesets()
+ {
+ if (rulesetStorage == null) return;
+
+ var rulesets = rulesetStorage.GetFiles(".", $"{ruleset_library_prefix}.*.dll");
+
+ foreach (var ruleset in rulesets.Where(f => !f.Contains("Tests")))
+ loadRulesetFromFile(rulesetStorage.GetFullPath(ruleset));
+ }
+
private void loadFromDisk()
{
try
@@ -175,7 +208,7 @@ namespace osu.Game.Rulesets
protected virtual void Dispose(bool disposing)
{
- AppDomain.CurrentDomain.AssemblyResolve -= resolveRulesetAssembly;
+ AppDomain.CurrentDomain.AssemblyResolve -= resolveRulesetDependencyAssembly;
}
}
}
diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs
index 334b95f808..8aef615b5f 100644
--- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using osu.Framework.Bindables;
using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
@@ -12,11 +13,6 @@ namespace osu.Game.Rulesets.Scoring
{
public abstract class JudgementProcessor : Component
{
- ///
- /// Invoked when all s have been judged by this .
- ///
- public event Action AllJudged;
-
///
/// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by this .
///
@@ -32,10 +28,12 @@ namespace osu.Game.Rulesets.Scoring
///
public int JudgedHits { get; private set; }
+ private readonly BindableBool hasCompleted = new BindableBool();
+
///
/// Whether all s have been processed.
///
- public bool HasCompleted => JudgedHits == MaxHits;
+ public IBindable HasCompleted => hasCompleted;
///
/// Applies a to this .
@@ -60,8 +58,7 @@ namespace osu.Game.Rulesets.Scoring
NewJudgement?.Invoke(result);
- if (HasCompleted)
- AllJudged?.Invoke();
+ updateHasCompleted();
}
///
@@ -72,6 +69,8 @@ namespace osu.Game.Rulesets.Scoring
{
JudgedHits--;
+ updateHasCompleted();
+
RevertResultInternal(result);
}
@@ -134,5 +133,7 @@ namespace osu.Game.Rulesets.Scoring
ApplyResult(result);
}
}
+
+ private void updateHasCompleted() => hasCompleted.Value = JudgedHits == MaxHits;
}
}
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 9a1f450dc6..54e4af94a4 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -22,6 +22,7 @@ using osu.Game.Screens.Edit.Design;
using osuTK.Input;
using System.Collections.Generic;
using osu.Framework;
+using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
@@ -37,7 +38,7 @@ using osu.Game.Users;
namespace osu.Game.Screens.Edit
{
[Cached(typeof(IBeatSnapProvider))]
- public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IBeatSnapProvider
+ public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IKeyBindingHandler, IBeatSnapProvider
{
public override float BackgroundParallaxAmount => 0.1f;
@@ -157,8 +158,8 @@ namespace osu.Game.Screens.Edit
{
Items = new[]
{
- undoMenuItem = new EditorMenuItem("Undo", MenuItemType.Standard, undo),
- redoMenuItem = new EditorMenuItem("Redo", MenuItemType.Standard, redo)
+ undoMenuItem = new EditorMenuItem("Undo", MenuItemType.Standard, Undo),
+ redoMenuItem = new EditorMenuItem("Redo", MenuItemType.Standard, Redo)
}
}
}
@@ -230,6 +231,30 @@ namespace osu.Game.Screens.Edit
clock.ProcessFrame();
}
+ public bool OnPressed(PlatformAction action)
+ {
+ switch (action.ActionType)
+ {
+ case PlatformActionType.Undo:
+ Undo();
+ return true;
+
+ case PlatformActionType.Redo:
+ Redo();
+ return true;
+
+ case PlatformActionType.Save:
+ saveBeatmap();
+ return true;
+ }
+
+ return false;
+ }
+
+ public void OnReleased(PlatformAction action)
+ {
+ }
+
protected override bool OnKeyDown(KeyDownEvent e)
{
switch (e.Key)
@@ -241,28 +266,6 @@ namespace osu.Game.Screens.Edit
case Key.Right:
seek(e, 1);
return true;
-
- case Key.S:
- if (e.ControlPressed)
- {
- saveBeatmap();
- return true;
- }
-
- break;
-
- case Key.Z:
- if (e.ControlPressed)
- {
- if (e.ShiftPressed)
- redo();
- else
- undo();
-
- return true;
- }
-
- break;
}
return base.OnKeyDown(e);
@@ -326,9 +329,9 @@ namespace osu.Game.Screens.Edit
return base.OnExiting(next);
}
- private void undo() => changeHandler.RestoreState(-1);
+ protected void Undo() => changeHandler.RestoreState(-1);
- private void redo() => changeHandler.RestoreState(1);
+ protected void Redo() => changeHandler.RestoreState(1);
private void resetTrack(bool seekToStart = false)
{
diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs
index fe538728e3..30e5e9702e 100644
--- a/osu.Game/Screens/Menu/ButtonSystem.cs
+++ b/osu.Game/Screens/Menu/ButtonSystem.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Screens.Menu
public Action OnEdit;
public Action OnExit;
- public Action OnDirect;
+ public Action OnBeatmapListing;
public Action OnSolo;
public Action OnSettings;
public Action OnMulti;
@@ -130,7 +130,7 @@ namespace osu.Game.Screens.Menu
buttonsTopLevel.Add(new Button(@"play", @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P));
buttonsTopLevel.Add(new Button(@"osu!editor", @"button-generic-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
- buttonsTopLevel.Add(new Button(@"osu!direct", @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnDirect?.Invoke(), 0, Key.D));
+ buttonsTopLevel.Add(new Button(@"osu!direct", @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.D));
if (host.CanExit)
buttonsTopLevel.Add(new Button(@"exit", string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q));
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index 174eadfe26..0589e4d12b 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -72,7 +72,7 @@ namespace osu.Game.Screens.Menu
private SongTicker songTicker;
[BackgroundDependencyLoader(true)]
- private void load(DirectOverlay direct, SettingsOverlay settings, RankingsOverlay rankings, OsuConfigManager config, SessionStatics statics)
+ private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, RankingsOverlay rankings, OsuConfigManager config, SessionStatics statics)
{
holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay);
loginDisplayed = statics.GetBindable(Static.LoginOverlayDisplayed);
@@ -133,7 +133,7 @@ namespace osu.Game.Screens.Menu
};
buttons.OnSettings = () => settings?.ToggleVisibility();
- buttons.OnDirect = () => direct?.ToggleVisibility();
+ buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility();
buttons.OnChart = () => rankings?.ShowSpotlights();
LoadComponentAsync(background = new BackgroundScreenDefault());
diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs
index d7dcca9809..c024304856 100644
--- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs
+++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs
@@ -21,7 +21,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osu.Game.Online.Chat;
using osu.Game.Online.Multiplayer;
-using osu.Game.Overlays.Direct;
+using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play.HUD;
@@ -210,7 +210,7 @@ namespace osu.Game.Screens.Multi
return true;
}
- private class PlaylistDownloadButton : PanelDownloadButton
+ private class PlaylistDownloadButton : BeatmapPanelDownloadButton
{
public PlaylistDownloadButton(BeatmapSetInfo beatmapSet)
: base(beatmapSet)
diff --git a/osu.Game/Screens/Play/BreakTracker.cs b/osu.Game/Screens/Play/BreakTracker.cs
index 64262d52b5..fcd7ed6b73 100644
--- a/osu.Game/Screens/Play/BreakTracker.cs
+++ b/osu.Game/Screens/Play/BreakTracker.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Screens.Play
isBreakTime.Value = getCurrentBreak()?.HasEffect == true
|| Clock.CurrentTime < gameplayStartTime
- || scoreProcessor?.HasCompleted == true;
+ || scoreProcessor?.HasCompleted.Value == true;
}
private BreakPeriod getCurrentBreak()
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 4597ae760c..ece4c6307e 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -37,6 +37,11 @@ namespace osu.Game.Screens.Play
[Cached]
public class Player : ScreenWithBeatmapBackground
{
+ ///
+ /// The delay upon completion of the beatmap before displaying the results screen.
+ ///
+ public const double RESULTS_DISPLAY_DELAY = 1000.0;
+
public override bool AllowBackButton => false; // handled by HoldForMenuButton
protected override UserActivity InitialActivity => new UserActivity.SoloGame(Beatmap.Value.BeatmapInfo, Ruleset.Value);
@@ -197,7 +202,7 @@ namespace osu.Game.Screens.Play
};
// Bind the judgement processors to ourselves
- ScoreProcessor.AllJudged += onCompletion;
+ ScoreProcessor.HasCompleted.ValueChanged += updateCompletionState;
HealthProcessor.Failed += onFail;
foreach (var mod in Mods.Value.OfType())
@@ -412,22 +417,33 @@ namespace osu.Game.Screens.Play
private ScheduledDelegate completionProgressDelegate;
- private void onCompletion()
+ private void updateCompletionState(ValueChangedEvent completionState)
{
// screen may be in the exiting transition phase.
if (!this.IsCurrentScreen())
return;
+ if (!completionState.NewValue)
+ {
+ completionProgressDelegate?.Cancel();
+ completionProgressDelegate = null;
+ ValidForResume = true;
+ return;
+ }
+
+ if (completionProgressDelegate != null)
+ throw new InvalidOperationException($"{nameof(updateCompletionState)} was fired more than once");
+
// Only show the completion screen if the player hasn't failed
- if (HealthProcessor.HasFailed || completionProgressDelegate != null)
+ if (HealthProcessor.HasFailed)
return;
ValidForResume = false;
if (!showResults) return;
- using (BeginDelayedSequence(1000))
- scheduleGotoRanking();
+ using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY))
+ completionProgressDelegate = Schedule(GotoRanking);
}
protected virtual ScoreInfo CreateScore()
@@ -679,12 +695,6 @@ namespace osu.Game.Screens.Play
storyboardReplacesBackground.Value = false;
}
- private void scheduleGotoRanking()
- {
- completionProgressDelegate?.Cancel();
- completionProgressDelegate = Schedule(GotoRanking);
- }
-
#endregion
}
}
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index a8225ba1ec..f989ab2787 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -28,8 +28,15 @@ namespace osu.Game.Screens.Select
{
public class BeatmapCarousel : CompositeDrawable, IKeyBindingHandler
{
- private const float bleed_top = FilterControl.HEIGHT;
- private const float bleed_bottom = Footer.HEIGHT;
+ ///
+ /// Height of the area above the carousel that should be treated as visible due to transparency of elements in front of it.
+ ///
+ public float BleedTop { get; set; }
+
+ ///
+ /// Height of the area below the carousel that should be treated as visible due to transparency of elements in front of it.
+ ///
+ public float BleedBottom { get; set; }
///
/// Triggered when the loaded change and are completely loaded.
@@ -217,6 +224,9 @@ namespace osu.Game.Screens.Select
/// True if a selection was made, False if it wasn't.
public bool SelectBeatmap(BeatmapInfo beatmap, bool bypassFilters = true)
{
+ // ensure that any pending events from BeatmapManager have been run before attempting a selection.
+ Scheduler.Update();
+
if (beatmap?.Hidden != false)
return false;
@@ -373,17 +383,17 @@ namespace osu.Game.Screens.Select
/// the beatmap carousel bleeds into the and the
///
///
- private float visibleHalfHeight => (DrawHeight + bleed_bottom + bleed_top) / 2;
+ private float visibleHalfHeight => (DrawHeight + BleedBottom + BleedTop) / 2;
///
/// The position of the lower visible bound with respect to the current scroll position.
///
- private float visibleBottomBound => scroll.Current + DrawHeight + bleed_bottom;
+ private float visibleBottomBound => scroll.Current + DrawHeight + BleedBottom;
///
/// The position of the upper visible bound with respect to the current scroll position.
///
- private float visibleUpperBound => scroll.Current - bleed_top;
+ private float visibleUpperBound => scroll.Current - BleedTop;
public void FlushPendingFilterOperations()
{
@@ -641,7 +651,11 @@ namespace osu.Game.Screens.Select
case DrawableCarouselBeatmap beatmap:
{
if (beatmap.Item.State.Value == CarouselItemState.Selected)
- scrollTarget = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2;
+ // scroll position at currentY makes the set panel appear at the very top of the carousel's screen space
+ // move down by half of visible height (height of the carousel's visible extent, including semi-transparent areas)
+ // then reapply the top semi-transparent area (because carousel's screen space starts below it)
+ // and finally add half of the panel's own height to achieve vertical centering of the panel itself
+ scrollTarget = currentY - visibleHalfHeight + BleedTop + beatmap.DrawHeight / 2;
void performMove(float y, float? startY = null)
{
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index 5bc2e1aa56..0d07a335cf 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -153,6 +153,8 @@ namespace osu.Game.Screens.Select
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
RelativeSizeAxes = Axes.Both,
+ BleedTop = FilterControl.HEIGHT,
+ BleedBottom = Footer.HEIGHT,
SelectionChanged = updateSelectedBeatmap,
BeatmapSetsChanged = carouselBeatmapsLoaded,
GetRecommendedBeatmap = recommender.GetRecommendedBeatmap,
@@ -426,7 +428,7 @@ namespace osu.Game.Screens.Select
}
///
- /// selection has been changed as the result of a user interaction.
+ /// Selection has been changed as the result of a user interaction.
///
private void performUpdateSelected()
{
@@ -435,7 +437,7 @@ namespace osu.Game.Screens.Select
selectionChangedDebounce?.Cancel();
- if (beatmap == null)
+ if (beatmapNoDebounce == null)
run();
else
selectionChangedDebounce = Scheduler.AddDelayed(run, 200);
@@ -448,9 +450,11 @@ namespace osu.Game.Screens.Select
{
Mods.Value = Array.Empty();
- // required to return once in order to have the carousel in a good state.
- // if the ruleset changed, the rest of the selection update will happen via updateSelectedRuleset.
- return;
+ // transferRulesetValue() may trigger a refilter. If the current selection does not match the new ruleset, we want to switch away from it.
+ // The default logic on WorkingBeatmap change is to switch to a matching ruleset (see workingBeatmapChanged()), but we don't want that here.
+ // We perform an early selection attempt and clear out the beatmap selection to avoid a second ruleset change (revert).
+ if (beatmap != null && !Carousel.SelectBeatmap(beatmap, false))
+ beatmap = null;
}
// We may be arriving here due to another component changing the bindable Beatmap.
@@ -714,7 +718,7 @@ namespace osu.Game.Screens.Select
if (decoupledRuleset.Value?.Equals(Ruleset.Value) == true)
return false;
- Logger.Log($"decoupled ruleset transferred (\"{decoupledRuleset.Value}\" -> \"{Ruleset.Value}\"");
+ Logger.Log($"decoupled ruleset transferred (\"{decoupledRuleset.Value}\" -> \"{Ruleset.Value}\")");
rulesetNoDebounce = decoupledRuleset.Value = Ruleset.Value;
// if we have a pending filter operation, we want to run it now.
diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
index 2db902c182..a988bd589f 100644
--- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
@@ -74,7 +74,7 @@ namespace osu.Game.Skinning
switch (pair.Key)
{
case "ColumnLineWidth":
- parseArrayValue(pair.Value, currentConfig.ColumnLineWidth);
+ parseArrayValue(pair.Value, currentConfig.ColumnLineWidth, false);
break;
case "ColumnSpacing":
@@ -124,7 +124,7 @@ namespace osu.Game.Skinning
pendingLines.Clear();
}
- private void parseArrayValue(string value, float[] output)
+ private void parseArrayValue(string value, float[] output, bool applyScaleFactor = true)
{
string[] values = value.Split(',');
@@ -133,7 +133,7 @@ namespace osu.Game.Skinning
if (i >= output.Length)
break;
- output[i] = float.Parse(values[i], CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
+ output[i] = float.Parse(values[i], CultureInfo.InvariantCulture) * (applyScaleFactor ? LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR : 1);
}
}
}
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 91f970d19f..003fa24d5b 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -249,6 +249,14 @@ namespace osu.Game.Skinning
case LegacyManiaSkinConfigurationLookups.RightStageImage:
return SkinUtils.As(getManiaImage(existing, "StageRight"));
+
+ case LegacyManiaSkinConfigurationLookups.LeftLineWidth:
+ Debug.Assert(maniaLookup.TargetColumn != null);
+ return SkinUtils.As(new Bindable(existing.ColumnLineWidth[maniaLookup.TargetColumn.Value]));
+
+ case LegacyManiaSkinConfigurationLookups.RightLineWidth:
+ Debug.Assert(maniaLookup.TargetColumn != null);
+ return SkinUtils.As(new Bindable(existing.ColumnLineWidth[maniaLookup.TargetColumn.Value + 1]));
}
return null;
diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs
index 5d4b8de7ac..75b7ba28b9 100644
--- a/osu.Game/Skinning/LegacySkinDecoder.cs
+++ b/osu.Game/Skinning/LegacySkinDecoder.cs
@@ -44,6 +44,12 @@ namespace osu.Game.Skinning
}
break;
+
+ // osu!catch section only has colour settings
+ // so no harm in handling the entire section
+ case Section.CatchTheBeat:
+ HandleColours(skin, line);
+ return;
}
if (!string.IsNullOrEmpty(pair.Key))
diff --git a/osu.Game/Tests/Visual/ModPerfectTestScene.cs b/osu.Game/Tests/Visual/ModPerfectTestScene.cs
index 3565fe751b..640ecb832f 100644
--- a/osu.Game/Tests/Visual/ModPerfectTestScene.cs
+++ b/osu.Game/Tests/Visual/ModPerfectTestScene.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual
public bool CheckFailed(bool failed)
{
if (!failed)
- return ScoreProcessor.HasCompleted && !HealthProcessor.HasFailed;
+ return ScoreProcessor.HasCompleted.Value && !HealthProcessor.HasFailed;
return HealthProcessor.HasFailed;
}
diff --git a/osu.Game/Tests/Visual/ScrollingTestContainer.cs b/osu.Game/Tests/Visual/ScrollingTestContainer.cs
index 18326a78ad..3b741fcf1d 100644
--- a/osu.Game/Tests/Visual/ScrollingTestContainer.cs
+++ b/osu.Game/Tests/Visual/ScrollingTestContainer.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual
public void Flip() => scrollingInfo.Direction.Value = scrollingInfo.Direction.Value == ScrollingDirection.Up ? ScrollingDirection.Down : ScrollingDirection.Up;
- private class TestScrollingInfo : IScrollingInfo
+ public class TestScrollingInfo : IScrollingInfo
{
public readonly Bindable Direction = new Bindable();
IBindable IScrollingInfo.Direction => Direction;
@@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual
IScrollAlgorithm IScrollingInfo.Algorithm => Algorithm;
}
- private class TestScrollAlgorithm : IScrollAlgorithm
+ public class TestScrollAlgorithm : IScrollAlgorithm
{
public readonly SortedList ControlPoints = new SortedList();
diff --git a/osu.Game/Utils/OrderAttribute.cs b/osu.Game/Utils/OrderAttribute.cs
new file mode 100644
index 0000000000..aded7f9814
--- /dev/null
+++ b/osu.Game/Utils/OrderAttribute.cs
@@ -0,0 +1,52 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace osu.Game.Utils
+{
+ public static class OrderAttributeUtils
+ {
+ ///
+ /// Get values of an enum in order. Supports custom ordering via .
+ ///
+ public static IEnumerable GetValuesInOrder()
+ {
+ var type = typeof(T);
+
+ if (!type.IsEnum)
+ throw new InvalidOperationException("T must be an enum");
+
+ IEnumerable items = (T[])Enum.GetValues(type);
+
+ if (Attribute.GetCustomAttribute(type, typeof(HasOrderedElementsAttribute)) == null)
+ return items;
+
+ return items.OrderBy(i =>
+ {
+ if (type.GetField(i.ToString()).GetCustomAttributes(typeof(OrderAttribute), false).FirstOrDefault() is OrderAttribute attr)
+ return attr.Order;
+
+ throw new ArgumentException($"Not all values of {nameof(T)} have {nameof(OrderAttribute)} specified.");
+ });
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Field)]
+ public class OrderAttribute : Attribute
+ {
+ public readonly int Order;
+
+ public OrderAttribute(int order)
+ {
+ Order = order;
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Enum)]
+ public class HasOrderedElementsAttribute : Attribute
+ {
+ }
+}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 76f7a030f9..9c17c453a6 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -19,11 +19,11 @@
-
+
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 7a487a6430..07ea4b9c2a 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,17 +70,17 @@
-
+
-
+
-
+