diff --git a/osu.Android.props b/osu.Android.props
index 4167d07698..c57fc342ba 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,7 +51,6 @@
osu.licenseheader
-
@@ -63,6 +62,6 @@
-
+
diff --git a/osu.Android.sln.DotSettings b/osu.Android.sln.DotSettings
index 3f5bd9d34d..5a97fc7518 100644
--- a/osu.Android.sln.DotSettings
+++ b/osu.Android.sln.DotSettings
@@ -1,4 +1,4 @@
-
+
True
True
True
@@ -167,6 +167,14 @@
WARNING
<?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile>
Code Cleanup (peppy)
+ Required
+ Required
+ Required
+ Explicit
+ ExpressionBody
+ ExpressionBody
+ True
+ NEXT_LINE
True
True
True
@@ -176,12 +184,22 @@
True
True
NEXT_LINE
+ 1
+ 1
+ NEXT_LINE
+ MULTILINE
NEXT_LINE
+ 1
+ 1
True
+ NEXT_LINE
NEVER
NEVER
+ True
False
+ True
NEVER
+ False
False
True
False
@@ -189,6 +207,7 @@
True
True
False
+ False
CHOP_IF_LONG
True
200
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs
new file mode 100644
index 0000000000..04e6dea376
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs
@@ -0,0 +1,29 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Catch.Mods;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ [TestFixture]
+ public class CatchLegacyModConversionTest : LegacyModConversionTest
+ {
+ [TestCase(LegacyMods.Easy, new[] { typeof(CatchModEasy) })]
+ [TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) })]
+ [TestCase(LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime) })]
+ [TestCase(LegacyMods.Nightcore, new[] { typeof(CatchModNightcore) })]
+ [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModNightcore) })]
+ [TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModFlashlight), typeof(CatchModNightcore) })]
+ [TestCase(LegacyMods.Perfect, new[] { typeof(CatchModPerfect) })]
+ [TestCase(LegacyMods.SuddenDeath, new[] { typeof(CatchModSuddenDeath) })]
+ [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(CatchModPerfect) })]
+ [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime), typeof(CatchModPerfect) })]
+ public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods);
+
+ protected override Ruleset CreateRuleset() => new CatchRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index c527a81f51..36342024b0 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 5428b4eeb8..71d68ace94 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -46,6 +46,11 @@ namespace osu.Game.Rulesets.Catch
else if (mods.HasFlag(LegacyMods.DoubleTime))
yield return new CatchModDoubleTime();
+ if (mods.HasFlag(LegacyMods.Perfect))
+ yield return new CatchModPerfect();
+ else if (mods.HasFlag(LegacyMods.SuddenDeath))
+ yield return new CatchModSuddenDeath();
+
if (mods.HasFlag(LegacyMods.Autoplay))
yield return new CatchModAutoplay();
@@ -67,14 +72,8 @@ namespace osu.Game.Rulesets.Catch
if (mods.HasFlag(LegacyMods.NoFail))
yield return new CatchModNoFail();
- if (mods.HasFlag(LegacyMods.Perfect))
- yield return new CatchModPerfect();
-
if (mods.HasFlag(LegacyMods.Relax))
yield return new CatchModRelax();
-
- if (mods.HasFlag(LegacyMods.SuddenDeath))
- yield return new CatchModSuddenDeath();
}
public override IEnumerable GetModsFor(ModType type)
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
index 4ea1f22006..6c8515eb90 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
@@ -37,9 +37,6 @@ namespace osu.Game.Rulesets.Catch.Replays
float lastPosition = 0.5f;
double lastTime = 0;
- // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled
- addFrame(-100000, lastPosition);
-
void moveToNext(CatchHitObject h)
{
float positionChange = Math.Abs(lastPosition - h.X);
@@ -127,6 +124,10 @@ namespace osu.Game.Rulesets.Catch.Replays
private void addFrame(double time, float? position = null, bool dashing = false)
{
+ // todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame.
+ if (Replay.Frames.Count == 0)
+ Replay.Frames.Add(new CatchReplayFrame(time - 1, position, false, null));
+
var last = currentFrame;
currentFrame = new CatchReplayFrame(time, position, dashing, last);
Replay.Frames.Add(currentFrame);
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs
new file mode 100644
index 0000000000..957743c5f1
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs
@@ -0,0 +1,30 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [TestFixture]
+ public class ManiaLegacyModConversionTest : LegacyModConversionTest
+ {
+ [TestCase(LegacyMods.Easy, new[] { typeof(ManiaModEasy) })]
+ [TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(ManiaModHardRock), typeof(ManiaModDoubleTime) })]
+ [TestCase(LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime) })]
+ [TestCase(LegacyMods.Nightcore, new[] { typeof(ManiaModNightcore) })]
+ [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(ManiaModNightcore) })]
+ [TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(ManiaModFlashlight), typeof(ManiaModNightcore) })]
+ [TestCase(LegacyMods.Perfect, new[] { typeof(ManiaModPerfect) })]
+ [TestCase(LegacyMods.SuddenDeath, new[] { typeof(ManiaModSuddenDeath) })]
+ [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(ManiaModPerfect) })]
+ [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime), typeof(ManiaModPerfect) })]
+ [TestCase(LegacyMods.Random | LegacyMods.SuddenDeath, new[] { typeof(ManiaModRandom), typeof(ManiaModSuddenDeath) })]
+ public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods);
+
+ protected override Ruleset CreateRuleset() => new ManiaRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
index f260357df5..a5248c7712 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
@@ -16,6 +16,11 @@ namespace osu.Game.Rulesets.Mania.Tests
[HeadlessTest]
public class TestSceneAutoGeneration : OsuTestScene
{
+ ///
+ /// The number of frames which are generated at the start of a replay regardless of hitobject content.
+ ///
+ private const int frame_offset = 1;
+
[Test]
public void TestSingleNote()
{
@@ -28,11 +33,11 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate();
- Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
- Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
- Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
- Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released");
+ Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
+ Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
+ Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
}
[Test]
@@ -49,11 +54,11 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate();
- Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
- Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
- Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
- Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released");
+ Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
+ Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
+ Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
}
[Test]
@@ -69,11 +74,11 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate();
- Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
- Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
- Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
- Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
+ Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
+ Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
+ Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
}
[Test]
@@ -91,11 +96,13 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate();
- Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
- Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
- Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
- Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
+ Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
+
+ Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
+ Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
+
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
}
[Test]
@@ -112,15 +119,15 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate();
- Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
- Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
- Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect first note release time");
- Assert.AreEqual(2000, generated.Frames[3].Time, "Incorrect second note hit time");
- Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
- Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released");
- Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released");
+ Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames");
+ Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
+ Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect first note release time");
+ Assert.AreEqual(2000, generated.Frames[frame_offset + 2].Time, "Incorrect second note hit time");
+ Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 3], ManiaAction.Key2), "Key2 has not been released");
}
[Test]
@@ -139,16 +146,16 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate();
- Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
- Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
- Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect first note release time");
- Assert.AreEqual(2000, generated.Frames[2].Time, "Incorrect second note hit time");
- Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
- Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
- Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key1), "Key1 has not been released");
- Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has been released");
- Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released");
+ Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames");
+ Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
+ Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time");
+ Assert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time");
+ Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key1), "Key1 has not been released");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has been released");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 3], ManiaAction.Key2), "Key2 has not been released");
}
[Test]
@@ -166,14 +173,14 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate();
- Assert.IsTrue(generated.Frames.Count == 4, "Replay must have 4 frames");
- Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
- Assert.AreEqual(3000, generated.Frames[2].Time, "Incorrect second note press time + first note release time");
- Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect second note release time");
- Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released");
- Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key2), "Key2 has not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been released");
+ Assert.IsTrue(generated.Frames.Count == frame_offset + 3, "Replay must have 3 generated frames");
+ Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
+ Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect second note press time + first note release time");
+ Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect second note release time");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key2), "Key2 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has not been released");
}
private bool checkContains(ReplayFrame frame, params ManiaAction[] actions) => actions.All(action => ((ManiaReplayFrame)frame).Actions.Contains(action));
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index af10d5e06e..09bf9241f2 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 0c4e7d4858..c74a292331 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -46,6 +46,11 @@ namespace osu.Game.Rulesets.Mania
else if (mods.HasFlag(LegacyMods.DoubleTime))
yield return new ManiaModDoubleTime();
+ if (mods.HasFlag(LegacyMods.Perfect))
+ yield return new ManiaModPerfect();
+ else if (mods.HasFlag(LegacyMods.SuddenDeath))
+ yield return new ManiaModSuddenDeath();
+
if (mods.HasFlag(LegacyMods.Autoplay))
yield return new ManiaModAutoplay();
@@ -97,14 +102,8 @@ namespace osu.Game.Rulesets.Mania
if (mods.HasFlag(LegacyMods.NoFail))
yield return new ManiaModNoFail();
- if (mods.HasFlag(LegacyMods.Perfect))
- yield return new ManiaModPerfect();
-
if (mods.HasFlag(LegacyMods.Random))
yield return new ManiaModRandom();
-
- if (mods.HasFlag(LegacyMods.SuddenDeath))
- yield return new ManiaModSuddenDeath();
}
public override IEnumerable GetModsFor(ModType type)
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
index 31221c05ee..8f353ae138 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -18,8 +18,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
public class DrawableNote : DrawableManiaHitObject, IKeyBindingHandler
{
- public const float CORNER_RADIUS = NotePiece.NOTE_HEIGHT / 2;
-
private readonly NotePiece headPiece;
public DrawableNote(Note hitObject)
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
index 7b8bbc2095..2b336ca16d 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
@@ -47,9 +47,6 @@ namespace osu.Game.Rulesets.Mania.Replays
public override Replay Generate()
{
- // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled
- Replay.Frames.Add(new ManiaReplayFrame(-100000, 0));
-
var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
var actions = new List();
@@ -70,6 +67,10 @@ namespace osu.Game.Rulesets.Mania.Replays
}
}
+ // todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame.
+ if (Replay.Frames.Count == 0)
+ Replay.Frames.Add(new ManiaReplayFrame(group.First().Time - 1));
+
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray()));
}
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs
new file mode 100644
index 0000000000..495f2738b5
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs
@@ -0,0 +1,30 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [TestFixture]
+ public class OsuLegacyModConversionTest : LegacyModConversionTest
+ {
+ [TestCase(LegacyMods.Easy, new[] { typeof(OsuModEasy) })]
+ [TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(OsuModHardRock), typeof(OsuModDoubleTime) })]
+ [TestCase(LegacyMods.DoubleTime, new[] { typeof(OsuModDoubleTime) })]
+ [TestCase(LegacyMods.Nightcore, new[] { typeof(OsuModNightcore) })]
+ [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(OsuModNightcore) })]
+ [TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(OsuModFlashlight), typeof(OsuModFlashlight) })]
+ [TestCase(LegacyMods.Perfect, new[] { typeof(OsuModPerfect) })]
+ [TestCase(LegacyMods.SuddenDeath, new[] { typeof(OsuModSuddenDeath) })]
+ [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(OsuModPerfect) })]
+ [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(OsuModDoubleTime), typeof(OsuModPerfect) })]
+ [TestCase(LegacyMods.SpunOut | LegacyMods.Easy, new[] { typeof(OsuModSpunOut), typeof(OsuModEasy) })]
+ public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods);
+
+ protected override Ruleset CreateRuleset() => new OsuRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
index 399cf22599..95c2810e94 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
@@ -29,7 +29,8 @@ namespace osu.Game.Rulesets.Osu.Tests
};
for (int i = 0; i < 512; i++)
- beatmap.HitObjects.Add(new HitCircle { Position = new Vector2(256, 192), StartTime = i * 100 });
+ if (i % 32 < 20)
+ beatmap.HitObjects.Add(new HitCircle { Position = new Vector2(256, 192), StartTime = i * 100 });
return beatmap;
}
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index c331c811d2..791043bcc6 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
index ca72f18e9c..65d7acc911 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
@@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) };
public bool AllowFail => false;
+ public bool RestartOnFail => false;
private OsuInputManager inputManager;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index 2d940479f3..32c9e913c6 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
@@ -17,7 +17,9 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Description => @"Play with no approach circles and fading circles/sliders.";
public override double ScoreMultiplier => 1.06;
- public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn) };
+
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModTraceable), typeof(OsuModSpinIn) };
+
private const double fade_in_duration_multiplier = 0.4;
private const double fade_out_duration_multiplier = 0.3;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
index 62b5ecfd58..e786ec86f9 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1;
// todo: this mod should be able to be compatible with hidden with a bit of further implementation.
- public override Type[] IncompatibleMods => new[] { typeof(OsuModeObjectScaleTween), typeof(OsuModHidden) };
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModeObjectScaleTween), typeof(OsuModHidden), typeof(OsuModTraceable) };
private const int rotate_offset = 360;
private const float rotate_starting_width = 2;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
new file mode 100644
index 0000000000..7e20feba02
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
@@ -0,0 +1,73 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Framework.Bindables;
+using System.Collections.Generic;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ internal class OsuModTraceable : Mod, IReadFromConfig, IApplicableToDrawableHitObjects
+ {
+ public override string Name => "Traceable";
+ public override string Acronym => "TC";
+ public override IconUsage Icon => FontAwesome.Brands.SnapchatGhost;
+ public override ModType Type => ModType.Fun;
+ public override string Description => "Put your faith in the approach circles...";
+ public override double ScoreMultiplier => 1;
+
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModSpinIn), typeof(OsuModeObjectScaleTween) };
+ private Bindable increaseFirstObjectVisibility = new Bindable();
+
+ public void ReadFromConfig(OsuConfigManager config)
+ {
+ increaseFirstObjectVisibility = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility);
+ }
+
+ public void ApplyToDrawableHitObjects(IEnumerable drawables)
+ {
+ foreach (var drawable in drawables.Skip(increaseFirstObjectVisibility.Value ? 1 : 0))
+ drawable.ApplyCustomUpdateState += ApplyTraceableState;
+ }
+
+ protected void ApplyTraceableState(DrawableHitObject drawable, ArmedState state)
+ {
+ if (!(drawable is DrawableOsuHitObject drawableOsu))
+ return;
+
+ var h = drawableOsu.HitObject;
+
+ switch (drawable)
+ {
+ case DrawableHitCircle circle:
+ // we only want to see the approach circle
+ using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
+ circle.CirclePiece.Hide();
+
+ break;
+
+ case DrawableSlider slider:
+ slider.AccentColour.BindValueChanged(_ =>
+ {
+ //will trigger on skin change.
+ slider.Body.AccentColour = slider.AccentColour.Value.Opacity(0);
+ slider.Body.BorderColour = slider.AccentColour.Value;
+ }, true);
+
+ break;
+
+ case DrawableSpinner spinner:
+ spinner.Disc.Hide();
+ spinner.Background.Hide();
+ break;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs
index e926ade41b..923278f484 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Mods
private Bindable increaseFirstObjectVisibility = new Bindable();
- public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn) };
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn), typeof(OsuModTraceable) };
public void ReadFromConfig(OsuConfigManager config)
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index 83646c561d..c90f230f93 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
{
- public ApproachCircle ApproachCircle;
+ public ApproachCircle ApproachCircle { get; }
private readonly IBindable positionBindable = new Bindable();
private readonly IBindable stackHeightBindable = new Bindable();
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly HitArea hitArea;
- private readonly SkinnableDrawable mainContent;
+ public SkinnableDrawable CirclePiece { get; }
public DrawableHitCircle(HitCircle h)
: base(h)
@@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return true;
},
},
- mainContent = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)),
+ CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)),
ApproachCircle = new ApproachCircle
{
Alpha = 0,
@@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.UpdateInitialTransforms();
- mainContent.FadeInFromZero(HitObject.TimeFadeIn);
+ CirclePiece.FadeInFromZero(HitObject.TimeFadeIn);
ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt));
ApproachCircle.ScaleTo(1f, HitObject.TimePreempt);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
index 938a2293ba..022e9ea12b 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
@@ -1,22 +1,66 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Game.Configuration;
using osuTK;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
+using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableOsuJudgement : DrawableJudgement
{
+ private SkinnableSprite lighting;
+ private Bindable lightingColour;
+
public DrawableOsuJudgement(JudgementResult result, DrawableHitObject judgedObject)
: base(result, judgedObject)
{
}
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ if (config.Get(OsuSetting.HitLighting) && Result.Type != HitResult.Miss)
+ {
+ AddInternal(lighting = new SkinnableSprite("lighting")
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Blending = BlendingParameters.Additive,
+ Depth = float.MaxValue
+ });
+
+ if (JudgedObject != null)
+ {
+ lightingColour = JudgedObject.AccentColour.GetBoundCopy();
+ lightingColour.BindValueChanged(colour => lighting.Colour = colour.NewValue, true);
+ }
+ else
+ {
+ lighting.Colour = Color4.White;
+ }
+ }
+ }
+
+ protected override double FadeOutDelay => lighting == null ? base.FadeOutDelay : 1400;
+
protected override void ApplyHitAnimations()
{
+ if (lighting != null)
+ {
+ JudgementBody.Delay(FadeInDuration).FadeOut(400);
+
+ lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out);
+ lighting.FadeIn(200).Then().Delay(200).FadeOut(1000);
+ }
+
JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint);
base.ApplyHitAnimations();
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 08b43b0345..9e8ad9851c 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -163,9 +163,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private float sliderPathRadius;
- protected override void SkinChanged(ISkinSource skin, bool allowFallback)
+ protected override void ApplySkin(ISkinSource skin, bool allowFallback)
{
- base.SkinChanged(skin, allowFallback);
+ base.ApplySkin(skin, allowFallback);
Body.BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE;
sliderPathRadius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
@@ -173,6 +173,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Body.AccentColour = skin.GetConfig(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value;
Body.BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
+
+ bool allowBallTint = skin.GetConfig(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
+ Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White;
}
private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius;
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index ceb9ed9343..fa69cec78d 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -52,6 +52,11 @@ namespace osu.Game.Rulesets.Osu
else if (mods.HasFlag(LegacyMods.DoubleTime))
yield return new OsuModDoubleTime();
+ if (mods.HasFlag(LegacyMods.Perfect))
+ yield return new OsuModPerfect();
+ else if (mods.HasFlag(LegacyMods.SuddenDeath))
+ yield return new OsuModSuddenDeath();
+
if (mods.HasFlag(LegacyMods.Autopilot))
yield return new OsuModAutopilot();
@@ -76,18 +81,12 @@ namespace osu.Game.Rulesets.Osu
if (mods.HasFlag(LegacyMods.NoFail))
yield return new OsuModNoFail();
- if (mods.HasFlag(LegacyMods.Perfect))
- yield return new OsuModPerfect();
-
if (mods.HasFlag(LegacyMods.Relax))
yield return new OsuModRelax();
if (mods.HasFlag(LegacyMods.SpunOut))
yield return new OsuModSpunOut();
- if (mods.HasFlag(LegacyMods.SuddenDeath))
- yield return new OsuModSuddenDeath();
-
if (mods.HasFlag(LegacyMods.Target))
yield return new OsuModTarget();
@@ -140,6 +139,7 @@ namespace osu.Game.Rulesets.Osu
new OsuModSpinIn(),
new MultiMod(new OsuModGrow(), new OsuModDeflate()),
new MultiMod(new ModWindUp(), new ModWindDown()),
+ new OsuModTraceable(),
};
case ModType.System:
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
index e7b686d27d..98219cafe8 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
@@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
HitCircleOverlap,
SliderBorderSize,
SliderPathRadius,
+ AllowSliderBallTint,
CursorExpand,
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index df12ebc514..d1757de445 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
{
Origin = Anchor.Centre,
Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition,
- Scale = new Vector2(((OsuHitObject)judgedObject.HitObject).Scale * 1.65f)
+ Scale = new Vector2(((OsuHitObject)judgedObject.HitObject).Scale)
};
judgementLayer.Add(explosion);
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs
new file mode 100644
index 0000000000..a59544386b
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs
@@ -0,0 +1,29 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Taiko.Mods;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ [TestFixture]
+ public class TaikoLegacyModConversionTest : LegacyModConversionTest
+ {
+ [TestCase(LegacyMods.Easy, new[] { typeof(TaikoModEasy) })]
+ [TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(TaikoModHardRock), typeof(TaikoModDoubleTime) })]
+ [TestCase(LegacyMods.DoubleTime, new[] { typeof(TaikoModDoubleTime) })]
+ [TestCase(LegacyMods.Nightcore, new[] { typeof(TaikoModNightcore) })]
+ [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(TaikoModNightcore) })]
+ [TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(TaikoModFlashlight), typeof(TaikoModNightcore) })]
+ [TestCase(LegacyMods.Perfect, new[] { typeof(TaikoModPerfect) })]
+ [TestCase(LegacyMods.SuddenDeath, new[] { typeof(TaikoModSuddenDeath) })]
+ [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(TaikoModPerfect) })]
+ [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(TaikoModDoubleTime), typeof(TaikoModPerfect) })]
+ public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods);
+
+ protected override Ruleset CreateRuleset() => new TaikoRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index d2a0a8fa6f..b0e0efdc68 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 7fdb823388..b2655f592c 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -45,6 +45,11 @@ namespace osu.Game.Rulesets.Taiko
else if (mods.HasFlag(LegacyMods.DoubleTime))
yield return new TaikoModDoubleTime();
+ if (mods.HasFlag(LegacyMods.Perfect))
+ yield return new TaikoModPerfect();
+ else if (mods.HasFlag(LegacyMods.SuddenDeath))
+ yield return new TaikoModSuddenDeath();
+
if (mods.HasFlag(LegacyMods.Autoplay))
yield return new TaikoModAutoplay();
@@ -66,14 +71,8 @@ namespace osu.Game.Rulesets.Taiko
if (mods.HasFlag(LegacyMods.NoFail))
yield return new TaikoModNoFail();
- if (mods.HasFlag(LegacyMods.Perfect))
- yield return new TaikoModPerfect();
-
if (mods.HasFlag(LegacyMods.Relax))
yield return new TaikoModRelax();
-
- if (mods.HasFlag(LegacyMods.SuddenDeath))
- yield return new TaikoModSuddenDeath();
}
public override IEnumerable GetModsFor(ModType type)
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index ad0ed00989..385ab4064d 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -15,7 +15,10 @@ using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.IO;
using osu.Game.Tests.Resources;
+using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
+using SharpCompress.Common;
+using SharpCompress.Writers.Zip;
namespace osu.Game.Tests.Beatmaps.IO
{
@@ -87,6 +90,48 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
+ [Test]
+ public async Task TestImportCorruptThenImport()
+ {
+ //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport"))
+ {
+ try
+ {
+ var osu = loadOsu(host);
+
+ var imported = await LoadOszIntoOsu(osu);
+
+ var firstFile = imported.Files.First();
+
+ var files = osu.Dependencies.Get();
+
+ long originalLength;
+ using (var stream = files.Storage.GetStream(firstFile.FileInfo.StoragePath))
+ originalLength = stream.Length;
+
+ using (var stream = files.Storage.GetStream(firstFile.FileInfo.StoragePath, FileAccess.Write, FileMode.Create))
+ stream.WriteByte(0);
+
+ var importedSecondTime = await LoadOszIntoOsu(osu);
+
+ using (var stream = files.Storage.GetStream(firstFile.FileInfo.StoragePath))
+ Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import");
+
+ // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
+ Assert.IsTrue(imported.ID == importedSecondTime.ID);
+ Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
+
+ checkBeatmapSetCount(osu, 1);
+ checkSingleReferencedFileCount(osu, 18);
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
[Test]
public async Task TestRollbackOnFailure()
{
@@ -135,7 +180,7 @@ namespace osu.Game.Tests.Beatmaps.IO
using (var zip = ZipArchive.Open(brokenOsz))
{
zip.AddEntry("broken.osu", brokenOsu, false);
- zip.SaveTo(outStream, SharpCompress.Common.CompressionType.Deflate);
+ zip.SaveTo(outStream, CompressionType.Deflate);
}
// this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
@@ -366,6 +411,51 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
+ [Test]
+ public async Task TestImportNestedStructure()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportNestedStructure"))
+ {
+ try
+ {
+ var osu = loadOsu(host);
+
+ var temp = TestResources.GetTestBeatmapForImport();
+
+ string extractedFolder = $"{temp}_extracted";
+ string subfolder = Path.Combine(extractedFolder, "subfolder");
+
+ Directory.CreateDirectory(subfolder);
+
+ try
+ {
+ using (var zip = ZipArchive.Open(temp))
+ zip.WriteToDirectory(subfolder);
+
+ using (var zip = ZipArchive.Create())
+ {
+ zip.AddAllFromDirectory(extractedFolder);
+ zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
+ }
+
+ var imported = await osu.Dependencies.Get().Import(temp);
+
+ ensureLoaded(osu);
+
+ Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder");
+ }
+ finally
+ {
+ Directory.Delete(extractedFolder, true);
+ }
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null)
{
var temp = path ?? TestResources.GetTestBeatmapForImport();
diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
index 4babb07213..89b5db9e1b 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -117,17 +117,57 @@ namespace osu.Game.Tests.Scores.IO
}
}
+ [Test]
+ public async Task TestImportWithDeletedBeatmapSet()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDeletedBeatmapSet"))
+ {
+ try
+ {
+ var osu = await loadOsu(host);
+
+ var toImport = new ScoreInfo
+ {
+ Hash = Guid.NewGuid().ToString(),
+ Statistics = new Dictionary
+ {
+ { HitResult.Perfect, 100 },
+ { HitResult.Miss, 50 }
+ }
+ };
+
+ var imported = await loadIntoOsu(osu, toImport);
+
+ var beatmapManager = osu.Dependencies.Get();
+ var scoreManager = osu.Dependencies.Get();
+
+ beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.Beatmap.ID)));
+ Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true));
+
+ var secondImport = await loadIntoOsu(osu, imported);
+ Assert.That(secondImport, Is.Null);
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
private async Task loadIntoOsu(OsuGameBase osu, ScoreInfo score)
{
var beatmapManager = osu.Dependencies.Get();
- score.Beatmap = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
- score.Ruleset = new OsuRuleset().RulesetInfo;
+ if (score.Beatmap == null)
+ score.Beatmap = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
+
+ if (score.Ruleset == null)
+ score.Ruleset = new OsuRuleset().RulesetInfo;
var scoreManager = osu.Dependencies.Get();
await scoreManager.Import(score);
- return scoreManager.GetAllUsableScores().First();
+ return scoreManager.GetAllUsableScores().FirstOrDefault();
}
private async Task loadOsu(GameHost host)
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
index 452ac859de..f94071a7a9 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
@@ -3,6 +3,7 @@
using System.ComponentModel;
using System.Linq;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
@@ -12,6 +13,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Description("Player instantiated with an autoplay mod.")]
public class TestSceneAutoplay : AllPlayersTestScene
{
+ private ClockBackedTestWorkingBeatmap.TrackVirtualManual track;
+
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
@@ -21,7 +24,18 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void AddCheckSteps()
{
AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0);
- AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0));
+ AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2));
+ AddStep("rewind", () => track.Seek(-10000));
+ AddUntilStep("key counter reset", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
+ }
+
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
+ {
+ var working = base.CreateWorkingBeatmap(beatmap);
+
+ track = (ClockBackedTestWorkingBeatmap.TrackVirtualManual)working.Track;
+
+ return working;
}
private class ScoreAccessiblePlayer : TestPlayer
@@ -29,6 +43,8 @@ namespace osu.Game.Tests.Visual.Gameplay
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public new HUDOverlay HUDOverlay => base.HUDOverlay;
+ public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
+
public ScoreAccessiblePlayer()
: base(false, false)
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
index d57ec44f39..cca6301b02 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
@@ -6,8 +6,6 @@ using System.Linq;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.UI;
-using osu.Game.Scoring;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay
@@ -17,9 +15,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Array.Empty();
-
- var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty());
- return new FailPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap));
+ return new FailPlayer();
}
protected override void AddCheckSteps()
@@ -29,16 +25,12 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("total judgements == 1", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits == 1);
}
- private class FailPlayer : ReplayPlayer
+ private class FailPlayer : TestPlayer
{
- public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
-
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
- protected override bool PauseOnFocusLost => false;
-
- public FailPlayer(Score score)
- : base(score, false, false)
+ public FailPlayer()
+ : base(false, false)
{
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
index 237fee1594..ffc025a942 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
@@ -14,6 +14,7 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osuTK;
@@ -47,9 +48,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for track to start running", () => track.IsRunning);
addSeekStep(3000);
AddAssert("all judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
+ AddUntilStep("key counter counted keys", () => player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7));
AddStep("clear results", () => player.AppliedResults.Clear());
addSeekStep(0);
AddAssert("none judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
+ AddUntilStep("key counters reset", () => player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
AddAssert("no results triggered", () => player.AppliedResults.Count == 0);
}
@@ -90,6 +93,10 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public readonly List AppliedResults = new List();
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+
+ public new HUDOverlay HUDOverlay => base.HUDOverlay;
+
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs
index 18088a9a5b..ad747e88e1 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs
@@ -7,7 +7,6 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
-using osu.Framework.Timing;
using osu.Game.Screens.Play;
using osuTK.Input;
@@ -25,14 +24,15 @@ namespace osu.Game.Tests.Visual.Gameplay
public TestSceneKeyCounter()
{
- KeyCounterKeyboard rewindTestKeyCounterKeyboard;
+ KeyCounterKeyboard testCounter;
+
KeyCounterDisplay kc = new KeyCounterDisplay
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Children = new KeyCounter[]
{
- rewindTestKeyCounterKeyboard = new KeyCounterKeyboard(Key.X),
+ testCounter = new KeyCounterKeyboard(Key.X),
new KeyCounterKeyboard(Key.X),
new KeyCounterMouse(MouseButton.Left),
new KeyCounterMouse(MouseButton.Right),
@@ -44,10 +44,8 @@ namespace osu.Game.Tests.Visual.Gameplay
Key key = (Key)((int)Key.A + RNG.Next(26));
kc.Add(new KeyCounterKeyboard(key));
});
- AddSliderStep("Fade time", 0, 200, 50, v => kc.FadeTime = v);
Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key;
- double time1 = 0;
AddStep($"Press {testKey} key", () =>
{
@@ -55,48 +53,17 @@ namespace osu.Game.Tests.Visual.Gameplay
InputManager.ReleaseKey(testKey);
});
- AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 1);
+ AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 1);
AddStep($"Press {testKey} key", () =>
{
InputManager.PressKey(testKey);
InputManager.ReleaseKey(testKey);
- time1 = Clock.CurrentTime;
});
- AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 2);
-
- IFrameBasedClock oldClock = null;
-
- AddStep($"Rewind {testKey} counter once", () =>
- {
- oldClock = rewindTestKeyCounterKeyboard.Clock;
- rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(time1 - 10));
- });
-
- AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 1);
-
- AddStep($"Rewind {testKey} counter to zero", () => rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(0)));
-
- AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 0);
-
- AddStep("Restore clock", () => rewindTestKeyCounterKeyboard.Clock = oldClock);
+ AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 2);
Add(kc);
}
-
- private class FixedClock : IClock
- {
- private readonly double time;
-
- public FixedClock(double time)
- {
- this.time = time;
- }
-
- public double CurrentTime => time;
- public double Rate => 1;
- public bool IsRunning => false;
- }
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs
index 3fbce9d43c..36335bc54a 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs
@@ -26,12 +26,14 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0));
+ AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail);
}
private class ScoreAccessibleReplayPlayer : ReplayPlayer
{
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public new HUDOverlay HUDOverlay => base.HUDOverlay;
+ public new bool AllowFail => base.AllowFail;
protected override bool PauseOnFocusLost => false;
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
index f555c276f4..658f678b10 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
@@ -68,6 +68,34 @@ namespace osu.Game.Tests.Visual.Online
changelog.ShowListing();
changelog.Show();
});
+
+ AddStep(@"Ensure HTML string unescaping", () =>
+ {
+ changelog.ShowBuild(new APIChangelogBuild
+ {
+ Version = "2019.920.0",
+ DisplayVersion = "2019.920.0",
+ UpdateStream = new APIUpdateStream
+ {
+ Name = "Test",
+ DisplayName = "Test"
+ },
+ ChangelogEntries = new List
+ {
+ new APIChangelogEntry
+ {
+ Category = "Testing HTML strings unescaping",
+ Title = "Ensuring HTML strings are being unescaped",
+ MessageHtml = """"This text should appear triple-quoted""" >_<",
+ GithubUser = new APIChangelogUser
+ {
+ DisplayName = "Dummy",
+ OsuUsername = "Dummy",
+ }
+ },
+ }
+ });
+ });
}
}
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
similarity index 87%
rename from osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs
rename to osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
index 186f27a8b2..fb27ec7654 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
@@ -3,10 +3,12 @@
using System;
using System.Collections.Generic;
-using System.ComponentModel;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
+using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Leaderboards;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Select.Leaderboards;
using osu.Game.Users;
@@ -14,19 +16,20 @@ using osuTK;
namespace osu.Game.Tests.Visual.SongSelect
{
- [Description("PlaySongSelect leaderboard")]
- public class TestSceneLeaderboard : OsuTestScene
+ public class TestSceneBeatmapLeaderboard : OsuTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
typeof(Placeholder),
typeof(MessagePlaceholder),
typeof(RetrievalFailurePlaceholder),
+ typeof(UserTopScoreContainer),
+ typeof(Leaderboard),
};
private readonly FailableLeaderboard leaderboard;
- public TestSceneLeaderboard()
+ public TestSceneBeatmapLeaderboard()
{
Add(leaderboard = new FailableLeaderboard
{
@@ -37,6 +40,7 @@ namespace osu.Game.Tests.Visual.SongSelect
});
AddStep(@"New Scores", newScores);
+ AddStep(@"Show personal best", showPersonalBest);
AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores));
AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure));
AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter));
@@ -47,6 +51,32 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep($"{status} beatmap", () => showBeatmapWithStatus(status));
}
+ private void showPersonalBest()
+ {
+ leaderboard.TopScore = new APILegacyUserTopScoreInfo
+ {
+ Position = 999,
+ Score = new APILegacyScoreInfo
+ {
+ Rank = ScoreRank.XH,
+ Accuracy = 1,
+ MaxCombo = 244,
+ TotalScore = 1707827,
+ Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
+ User = new User
+ {
+ Id = 6602580,
+ Username = @"waaiiru",
+ Country = new Country
+ {
+ FullName = @"Spain",
+ FlagName = @"ES",
+ },
+ },
+ }
+ };
+ }
+
private void newScores()
{
var scores = new[]
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs
new file mode 100644
index 0000000000..7fac45e0f1
--- /dev/null
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs
@@ -0,0 +1,119 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osuTK.Graphics;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Scoring;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Screens.Select.Leaderboards;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.SongSelect
+{
+ public class TestSceneUserTopScoreContainer : OsuTestScene
+ {
+ public TestSceneUserTopScoreContainer()
+ {
+ UserTopScoreContainer topScoreContainer;
+
+ Add(new Container
+ {
+ Origin = Anchor.BottomCentre,
+ Anchor = Anchor.Centre,
+ AutoSizeAxes = Axes.Y,
+ Width = 500,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.DarkGreen,
+ },
+ topScoreContainer = new UserTopScoreContainer
+ {
+ Origin = Anchor.BottomCentre,
+ Anchor = Anchor.BottomCentre,
+ }
+ }
+ });
+
+ var scores = new[]
+ {
+ new APILegacyUserTopScoreInfo
+ {
+ Position = 999,
+ Score = new APILegacyScoreInfo
+ {
+ Rank = ScoreRank.XH,
+ Accuracy = 1,
+ MaxCombo = 244,
+ TotalScore = 1707827,
+ Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
+ User = new User
+ {
+ Id = 6602580,
+ Username = @"waaiiru",
+ Country = new Country
+ {
+ FullName = @"Spain",
+ FlagName = @"ES",
+ },
+ },
+ }
+ },
+ new APILegacyUserTopScoreInfo
+ {
+ Position = 110000,
+ Score = new APILegacyScoreInfo
+ {
+ Rank = ScoreRank.X,
+ Accuracy = 1,
+ MaxCombo = 244,
+ TotalScore = 1707827,
+ User = new User
+ {
+ Id = 4608074,
+ Username = @"Skycries",
+ Country = new Country
+ {
+ FullName = @"Brazil",
+ FlagName = @"BR",
+ },
+ },
+ }
+ },
+ new APILegacyUserTopScoreInfo
+ {
+ Position = 22333,
+ Score = new APILegacyScoreInfo
+ {
+ Rank = ScoreRank.S,
+ Accuracy = 1,
+ MaxCombo = 244,
+ TotalScore = 1707827,
+ User = new User
+ {
+ Id = 1541390,
+ Username = @"Toukai",
+ Country = new Country
+ {
+ FullName = @"Canada",
+ FlagName = @"CA",
+ },
+ },
+ }
+ }
+ };
+
+ AddStep(@"Trigger visibility", topScoreContainer.ToggleVisibility);
+ AddStep(@"Add score(rank 999)", () => topScoreContainer.Score.Value = scores[0]);
+ AddStep(@"Add score(rank 110000)", () => topScoreContainer.Score.Value = scores[1]);
+ AddStep(@"Add score(rank 22333)", () => topScoreContainer.Score.Value = scores[2]);
+ AddStep(@"Add null score", () => topScoreContainer.Score.Value = null);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs
index f787754aa4..feef1dae6b 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs
@@ -52,19 +52,23 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("start confirming", () => overlay.Begin());
AddStep("abort confirming", () => overlay.Abort());
+ AddAssert("ensure not fired internally", () => !overlay.Fired);
AddAssert("ensure aborted", () => !fired);
AddStep("start confirming", () => overlay.Begin());
AddUntilStep("wait until confirmed", () => fired);
+ AddAssert("ensure fired internally", () => overlay.Fired);
+
+ AddStep("abort after fire", () => overlay.Abort());
+ AddAssert("ensure not fired internally", () => !overlay.Fired);
+ AddStep("start confirming", () => overlay.Begin());
+ AddUntilStep("wait until fired again", () => overlay.Fired);
}
private class TestHoldToConfirmOverlay : ExitConfirmOverlay
{
- protected override bool AllowMultipleFires => true;
-
public void Begin() => BeginConfirm();
- public void Abort() => AbortConfirm();
}
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs
new file mode 100644
index 0000000000..73e0191adb
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs
@@ -0,0 +1,89 @@
+// 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.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Screens.Edit.Setup.Components.LabelledComponents;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ public class TestSceneLabelledComponent : OsuTestScene
+ {
+ [TestCase(false)]
+ [TestCase(true)]
+ public void TestPadded(bool hasDescription) => createPaddedComponent(hasDescription);
+
+ [TestCase(false)]
+ [TestCase(true)]
+ public void TestNonPadded(bool hasDescription) => createPaddedComponent(hasDescription, false);
+
+ private void createPaddedComponent(bool hasDescription = false, bool padded = true)
+ {
+ AddStep("create component", () =>
+ {
+ LabelledComponent component;
+
+ Child = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Width = 500,
+ AutoSizeAxes = Axes.Y,
+ Child = component = padded ? (LabelledComponent)new PaddedLabelledComponent() : new NonPaddedLabelledComponent(),
+ };
+
+ component.Label = "a sample component";
+ component.Description = hasDescription ? "this text describes the component" : string.Empty;
+ });
+ }
+
+ private class PaddedLabelledComponent : LabelledComponent
+ {
+ public PaddedLabelledComponent()
+ : base(true)
+ {
+ }
+
+ protected override Drawable CreateComponent() => new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Colour = Color4.Red,
+ Text = @"(( Component ))"
+ };
+ }
+
+ private class NonPaddedLabelledComponent : LabelledComponent
+ {
+ public NonPaddedLabelledComponent()
+ : base(false)
+ {
+ }
+
+ protected override Drawable CreateComponent() => new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 40,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.SlateGray
+ },
+ new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Colour = Color4.Red,
+ Text = @"(( Component ))"
+ }
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index 84f67c9319..75e6354612 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -3,7 +3,7 @@
-
+
diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
index bba3c92245..491cf54686 100644
--- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
+++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index b9ed3664ef..55b8b80e44 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -89,7 +89,7 @@ namespace osu.Game.Beatmaps
protected override Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
{
if (archive != null)
- beatmapSet.Beatmaps = createBeatmapDifficulties(archive);
+ beatmapSet.Beatmaps = createBeatmapDifficulties(beatmapSet.Files);
foreach (BeatmapInfo b in beatmapSet.Beatmaps)
{
@@ -279,13 +279,13 @@ namespace osu.Game.Beatmaps
///
/// Create all required s for the provided archive.
///
- private List createBeatmapDifficulties(ArchiveReader reader)
+ private List createBeatmapDifficulties(List files)
{
var beatmapInfos = new List();
- foreach (var name in reader.Filenames.Where(f => f.EndsWith(".osu")))
+ foreach (var file in files.Where(f => f.Filename.EndsWith(".osu")))
{
- using (var raw = reader.GetStream(name))
+ using (var raw = Files.Store.GetStream(file.FileInfo.StoragePath))
using (var ms = new MemoryStream()) //we need a memory stream so we can seek
using (var sr = new StreamReader(ms))
{
@@ -295,12 +295,13 @@ namespace osu.Game.Beatmaps
var decoder = Decoder.GetDecoder(sr);
IBeatmap beatmap = decoder.Decode(sr);
- beatmap.BeatmapInfo.Path = name;
+ beatmap.BeatmapInfo.Path = file.Filename;
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
var ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
beatmap.BeatmapInfo.Ruleset = ruleset;
+
// TODO: this should be done in a better place once we actually need to dynamically update it.
beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0;
beatmap.BeatmapInfo.Length = calculateLength(beatmap);
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index e26021d930..64b1f2d7bc 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -81,6 +81,8 @@ namespace osu.Game.Configuration
Set(OsuSetting.DimLevel, 0.3, 0, 1, 0.01);
Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01);
+ Set(OsuSetting.HitLighting, true);
+
Set(OsuSetting.ShowInterface, true);
Set(OsuSetting.ShowHealthDisplayWhenCantFail, true);
Set(OsuSetting.KeyOverlay, false);
@@ -112,6 +114,8 @@ namespace osu.Game.Configuration
Set(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f);
+ Set(OsuSetting.UIHoldActivationDelay, 200, 0, 500);
+
Set(OsuSetting.IntroSequence, IntroSequence.Triangles);
}
@@ -180,6 +184,8 @@ namespace osu.Game.Configuration
ScalingSizeX,
ScalingSizeY,
UIScale,
- IntroSequence
+ IntroSequence,
+ UIHoldActivationDelay,
+ HitLighting
}
}
diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index 52d3f013ce..17d1bd822e 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -7,10 +7,12 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Humanizer;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using osu.Framework;
using osu.Framework.Extensions;
+using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.IO.File;
using osu.Framework.Logging;
using osu.Framework.Platform;
@@ -109,7 +111,7 @@ namespace osu.Game.Database
protected async Task Import(ProgressNotification notification, params string[] paths)
{
notification.Progress = 0;
- notification.Text = "Import is initialising...";
+ notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising...";
int current = 0;
@@ -145,7 +147,7 @@ namespace osu.Game.Database
if (imported.Count == 0)
{
- notification.Text = "Import failed!";
+ notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import failed!";
notification.State = ProgressNotificationState.Cancelled;
}
else
@@ -481,12 +483,16 @@ namespace osu.Game.Database
{
var fileInfos = new List();
+ string prefix = reader.Filenames.GetCommonPrefix();
+ if (!(prefix.EndsWith("/") || prefix.EndsWith("\\")))
+ prefix = string.Empty;
+
// import files to manager
foreach (string file in reader.Filenames)
using (Stream s = reader.GetStream(file))
fileInfos.Add(new TFileModel
{
- Filename = FileSafety.PathStandardise(file),
+ Filename = FileSafety.PathStandardise(file.Substring(prefix.Length)),
FileInfo = files.Add(s)
});
@@ -585,7 +591,7 @@ namespace osu.Game.Database
///
/// The existing model.
/// The newly imported model.
- /// Whether the existing model should be restored and used. Returning false will delete the existing a force a re-import.
+ /// Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import.
protected virtual bool CanUndelete(TModel existing, TModel import) => true;
private DbSet queryModel() => ContextFactory.Get().Set();
diff --git a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs
index 773265d19b..5d549ba217 100644
--- a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs
+++ b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs
@@ -2,9 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Configuration;
namespace osu.Game.Graphics.Containers
{
@@ -12,12 +14,13 @@ namespace osu.Game.Graphics.Containers
{
public Action Action;
- private const int default_activation_delay = 200;
private const int fadeout_delay = 200;
- private readonly double activationDelay;
+ ///
+ /// Whether currently in a fired state (and the confirm has been sent).
+ ///
+ public bool Fired { get; private set; }
- private bool fired;
private bool confirming;
///
@@ -27,35 +30,35 @@ namespace osu.Game.Graphics.Containers
public Bindable Progress = new BindableDouble();
- ///
- /// Create a new instance.
- ///
- /// The time requried before an action is confirmed.
- protected HoldToConfirmContainer(double activationDelay = default_activation_delay)
+ private Bindable holdActivationDelay;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
{
- this.activationDelay = activationDelay;
+ holdActivationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay);
}
protected void BeginConfirm()
{
- if (confirming || (!AllowMultipleFires && fired)) return;
+ if (confirming || (!AllowMultipleFires && Fired)) return;
confirming = true;
- this.TransformBindableTo(Progress, 1, activationDelay * (1 - Progress.Value), Easing.Out).OnComplete(_ => Confirm());
+ this.TransformBindableTo(Progress, 1, holdActivationDelay.Value * (1 - Progress.Value), Easing.Out).OnComplete(_ => Confirm());
}
protected virtual void Confirm()
{
Action?.Invoke();
- fired = true;
+ Fired = true;
}
protected void AbortConfirm()
{
- if (!AllowMultipleFires && fired) return;
+ if (!AllowMultipleFires && Fired) return;
confirming = false;
+ Fired = false;
this.TransformBindableTo(Progress, 0, fadeout_delay, Easing.Out);
}
diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs
index 370d6786f5..bf4e881ed0 100644
--- a/osu.Game/IO/FileStore.cs
+++ b/osu.Game/IO/FileStore.cs
@@ -50,7 +50,16 @@ namespace osu.Game.IO
string path = info.StoragePath;
// we may be re-adding a file to fix missing store entries.
- if (!Storage.Exists(path))
+ bool requiresCopy = !Storage.Exists(path);
+
+ if (!requiresCopy)
+ {
+ // even if the file already exists, check the existing checksum for safety.
+ using (var stream = Storage.GetStream(path))
+ requiresCopy |= stream.ComputeSHA2Hash() != hash;
+ }
+
+ if (requiresCopy)
{
data.Seek(0, SeekOrigin.Begin);
diff --git a/osu.Game/Online/API/Requests/Responses/APIChangelogEntry.cs b/osu.Game/Online/API/Requests/Responses/APIChangelogEntry.cs
index 140e228acd..f949ab5da5 100644
--- a/osu.Game/Online/API/Requests/Responses/APIChangelogEntry.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIChangelogEntry.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Online.API.Requests.Responses
public string Url { get; set; }
[JsonProperty("type")]
- public string Type { get; set; }
+ public ChangelogEntryType Type { get; set; }
[JsonProperty("category")]
public string Category { get; set; }
@@ -44,4 +44,10 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty("github_user")]
public APIChangelogUser GithubUser { get; set; }
}
+
+ public enum ChangelogEntryType
+ {
+ Add,
+ Fix
+ }
}
diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs
index 147556b78b..83de0635fb 100644
--- a/osu.Game/Online/Leaderboards/Leaderboard.cs
+++ b/osu.Game/Online/Leaderboards/Leaderboard.cs
@@ -35,6 +35,10 @@ namespace osu.Game.Online.Leaderboards
private bool scoresLoadedOnce;
+ private readonly Container content;
+
+ protected override Container Content => content;
+
private IEnumerable scores;
public IEnumerable Scores
@@ -60,13 +64,13 @@ namespace osu.Game.Online.Leaderboards
// ensure placeholder is hidden when displaying scores
PlaceholderState = PlaceholderState.Successful;
- var sf = CreateScoreFlow();
- sf.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1));
+ var scoreFlow = CreateScoreFlow();
+ scoreFlow.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1));
// schedule because we may not be loaded yet (LoadComponentAsync complains).
- showScoresDelegate = Schedule(() => LoadComponentAsync(sf, _ =>
+ showScoresDelegate = Schedule(() => LoadComponentAsync(scoreFlow, _ =>
{
- scrollContainer.Add(scrollFlow = sf);
+ scrollContainer.Add(scrollFlow = scoreFlow);
int i = 0;
@@ -116,9 +120,7 @@ namespace osu.Game.Online.Leaderboards
{
if (value != PlaceholderState.Successful)
{
- getScoresRequest?.Cancel();
- getScoresRequest = null;
- Scores = null;
+ Reset();
}
if (value == placeholderState)
@@ -162,12 +164,35 @@ namespace osu.Game.Online.Leaderboards
protected Leaderboard()
{
- Children = new Drawable[]
+ InternalChildren = new Drawable[]
{
- scrollContainer = new OsuScrollContainer
+ new GridContainer
{
RelativeSizeAxes = Axes.Both,
- ScrollbarVisible = false,
+ RowDimensions = new[]
+ {
+ new Dimension(),
+ new Dimension(GridSizeMode.AutoSize),
+ },
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ scrollContainer = new OsuScrollContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ ScrollbarVisible = false,
+ }
+ },
+ new Drawable[]
+ {
+ content = new Container
+ {
+ AutoSizeAxes = Axes.Y,
+ RelativeSizeAxes = Axes.X,
+ },
+ }
+ },
},
loading = new LoadingAnimation(),
placeholderContainer = new Container
@@ -177,6 +202,13 @@ namespace osu.Game.Online.Leaderboards
};
}
+ protected virtual void Reset()
+ {
+ getScoresRequest?.Cancel();
+ getScoresRequest = null;
+ Scores = null;
+ }
+
private IAPIProvider api;
private ScheduledDelegate pendingUpdateScores;
diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs
index 0b84cfc28a..9387482f14 100644
--- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs
+++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs
@@ -20,23 +20,23 @@ using osu.Game.Scoring;
using osu.Game.Users.Drawables;
using osuTK;
using osuTK.Graphics;
+using Humanizer;
namespace osu.Game.Online.Leaderboards
{
public class LeaderboardScore : OsuClickableContainer
{
- public readonly int RankPosition;
-
public const float HEIGHT = 60;
private const float corner_radius = 5;
private const float edge_margin = 5;
private const float background_alpha = 0.25f;
- private const float rank_width = 30;
+ private const float rank_width = 35;
protected Container RankContainer { get; private set; }
private readonly ScoreInfo score;
+ private readonly int rank;
private Box background;
private Container content;
@@ -52,7 +52,7 @@ namespace osu.Game.Online.Leaderboards
public LeaderboardScore(ScoreInfo score, int rank)
{
this.score = score;
- RankPosition = rank;
+ this.rank = rank;
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
@@ -79,8 +79,8 @@ namespace osu.Game.Online.Leaderboards
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Font = OsuFont.GetFont(size: 22, italics: true),
- Text = RankPosition.ToString(),
+ Font = OsuFont.GetFont(size: 20, italics: true),
+ Text = rank.ToMetric(decimals: rank < 100000 ? 1 : 0),
},
},
},
diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs
index 3d145af562..11dc2049fd 100644
--- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs
+++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs
@@ -14,6 +14,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Users;
using osuTK.Graphics;
using osu.Framework.Allocation;
+using System.Net;
namespace osu.Game.Overlays.Changelog
{
@@ -76,7 +77,7 @@ namespace osu.Game.Overlays.Changelog
var entryColour = entry.Major ? colours.YellowLight : Color4.White;
- title.AddIcon(FontAwesome.Solid.Check, t =>
+ title.AddIcon(entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus, t =>
{
t.Font = fontSmall;
t.Colour = entryColour;
@@ -149,7 +150,7 @@ namespace osu.Game.Overlays.Changelog
};
// todo: use markdown parsing once API returns markdown
- message.AddText(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty), t =>
+ message.AddText(WebUtility.HtmlDecode(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty)), t =>
{
t.Font = fontSmall;
t.Colour = new Color4(235, 184, 254, 255);
diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs
index 6aaeff8554..59d748bc5d 100644
--- a/osu.Game/Overlays/DialogOverlay.cs
+++ b/osu.Game/Overlays/DialogOverlay.cs
@@ -13,7 +13,8 @@ namespace osu.Game.Overlays
public class DialogOverlay : OsuFocusedOverlayContainer
{
private readonly Container dialogContainer;
- private PopupDialog currentDialog;
+
+ public PopupDialog CurrentDialog { get; private set; }
public DialogOverlay()
{
@@ -31,15 +32,15 @@ namespace osu.Game.Overlays
public void Push(PopupDialog dialog)
{
- if (dialog == currentDialog) return;
+ if (dialog == CurrentDialog) return;
- currentDialog?.Hide();
- currentDialog = dialog;
+ CurrentDialog?.Hide();
+ CurrentDialog = dialog;
- dialogContainer.Add(currentDialog);
+ dialogContainer.Add(CurrentDialog);
- currentDialog.Show();
- currentDialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue);
+ CurrentDialog.Show();
+ CurrentDialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue);
Show();
}
@@ -52,8 +53,11 @@ namespace osu.Game.Overlays
//handle the dialog being dismissed.
dialog.Delay(PopupDialog.EXIT_DURATION).Expire();
- if (dialog == currentDialog)
+ if (dialog == CurrentDialog)
+ {
Hide();
+ CurrentDialog = null;
+ }
}
protected override void PopIn()
@@ -66,9 +70,9 @@ namespace osu.Game.Overlays
{
base.PopOut();
- if (currentDialog?.State.Value == Visibility.Visible)
+ if (CurrentDialog?.State.Value == Visibility.Visible)
{
- currentDialog.Hide();
+ CurrentDialog.Hide();
return;
}
@@ -80,7 +84,7 @@ namespace osu.Game.Overlays
switch (action)
{
case GlobalAction.Select:
- currentDialog?.Buttons.OfType().FirstOrDefault()?.Click();
+ CurrentDialog?.Buttons.OfType().FirstOrDefault()?.Click();
return true;
}
diff --git a/osu.Game/Overlays/HoldToConfirmOverlay.cs b/osu.Game/Overlays/HoldToConfirmOverlay.cs
index fdc6f096bc..eb325d8dd3 100644
--- a/osu.Game/Overlays/HoldToConfirmOverlay.cs
+++ b/osu.Game/Overlays/HoldToConfirmOverlay.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Overlays
protected override void Dispose(bool isDisposing)
{
- audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, audioVolume);
+ audio?.Tracks.RemoveAdjustment(AdjustableProperty.Volume, audioVolume);
base.Dispose(isDisposing);
}
}
diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs
index 539601c359..5b528c5ab2 100644
--- a/osu.Game/Overlays/Music/PlaylistList.cs
+++ b/osu.Game/Overlays/Music/PlaylistList.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
@@ -18,7 +19,6 @@ namespace osu.Game.Overlays.Music
public class PlaylistList : CompositeDrawable
{
public Action Selected;
- public Action OrderChanged;
private readonly ItemsScrollContainer items;
@@ -28,7 +28,6 @@ namespace osu.Game.Overlays.Music
{
RelativeSizeAxes = Axes.Both,
Selected = set => Selected?.Invoke(set),
- OrderChanged = (s, i) => OrderChanged?.Invoke(s, i)
};
}
@@ -45,13 +44,17 @@ namespace osu.Game.Overlays.Music
private class ItemsScrollContainer : OsuScrollContainer
{
public Action Selected;
- public Action OrderChanged;
private readonly SearchContainer search;
private readonly FillFlowContainer items;
private readonly IBindable beatmapBacking = new Bindable();
+ private IBindableList beatmaps;
+
+ [Resolved]
+ private MusicController musicController { get; set; }
+
public ItemsScrollContainer()
{
Children = new Drawable[]
@@ -73,27 +76,35 @@ namespace osu.Game.Overlays.Music
}
[BackgroundDependencyLoader]
- private void load(BeatmapManager beatmaps, IBindable beatmap)
+ private void load(IBindable beatmap)
{
- beatmaps.GetAllUsableBeatmapSets().ForEach(addBeatmapSet);
- beatmaps.ItemAdded += addBeatmapSet;
- beatmaps.ItemRemoved += removeBeatmapSet;
+ beatmaps = musicController.BeatmapSets.GetBoundCopy();
+ beatmaps.ItemsAdded += i => i.ForEach(addBeatmapSet);
+ beatmaps.ItemsRemoved += i => i.ForEach(removeBeatmapSet);
+ beatmaps.ForEach(addBeatmapSet);
beatmapBacking.BindTo(beatmap);
beatmapBacking.ValueChanged += _ => updateSelectedSet();
}
- private void addBeatmapSet(BeatmapSetInfo obj) => Schedule(() =>
+ private void addBeatmapSet(BeatmapSetInfo obj)
{
- items.Insert(items.Count - 1, new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) });
- });
+ if (obj == draggedItem?.BeatmapSetInfo) return;
- private void removeBeatmapSet(BeatmapSetInfo obj) => Schedule(() =>
+ Schedule(() => items.Insert(items.Count - 1, new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) }));
+ }
+
+ private void removeBeatmapSet(BeatmapSetInfo obj)
{
- var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == obj.ID);
- if (itemToRemove != null)
- items.Remove(itemToRemove);
- });
+ if (obj == draggedItem?.BeatmapSetInfo) return;
+
+ Schedule(() =>
+ {
+ var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == obj.ID);
+ if (itemToRemove != null)
+ items.Remove(itemToRemove);
+ });
+ }
private void updateSelectedSet()
{
@@ -112,6 +123,8 @@ namespace osu.Game.Overlays.Music
private Vector2 nativeDragPosition;
private PlaylistItem draggedItem;
+ private int? dragDestination;
+
protected override bool OnDragStart(DragStartEvent e)
{
nativeDragPosition = e.ScreenSpaceMousePosition;
@@ -131,10 +144,17 @@ namespace osu.Game.Overlays.Music
protected override bool OnDragEnd(DragEndEvent e)
{
nativeDragPosition = e.ScreenSpaceMousePosition;
- var handled = draggedItem != null || base.OnDragEnd(e);
- draggedItem = null;
- return handled;
+ if (draggedItem == null)
+ return base.OnDragEnd(e);
+
+ if (dragDestination != null)
+ musicController.ChangeBeatmapSetPosition(draggedItem.BeatmapSetInfo, dragDestination.Value);
+
+ draggedItem = null;
+ dragDestination = null;
+
+ return true;
}
protected override void Update()
@@ -210,7 +230,7 @@ namespace osu.Game.Overlays.Music
}
items.SetLayoutPosition(draggedItem, dstIndex);
- OrderChanged?.Invoke(draggedItem.BeatmapSetInfo, dstIndex);
+ dragDestination = dstIndex;
}
private class ItemSearchContainer : FillFlowContainer, IHasFilterableChildren
diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs
index ec3d708645..ae81a6c117 100644
--- a/osu.Game/Overlays/Music/PlaylistOverlay.cs
+++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -22,12 +21,6 @@ namespace osu.Game.Overlays.Music
private const float transition_duration = 600;
private const float playlist_height = 510;
- ///
- /// Invoked when the order of an item in the list has changed.
- /// The second parameter indicates the new index of the item.
- ///
- public Action OrderChanged;
-
private readonly Bindable beatmap = new Bindable();
private BeatmapManager beatmaps;
@@ -65,7 +58,6 @@ namespace osu.Game.Overlays.Music
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 },
Selected = itemSelected,
- OrderChanged = (s, i) => OrderChanged?.Invoke(s, i)
},
filter = new FilterControl
{
diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs
index 6ad147735b..db94b0278f 100644
--- a/osu.Game/Overlays/MusicController.cs
+++ b/osu.Game/Overlays/MusicController.cs
@@ -8,6 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
+using osu.Framework.MathUtils;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Input.Bindings;
@@ -24,7 +25,9 @@ namespace osu.Game.Overlays
[Resolved]
private BeatmapManager beatmaps { get; set; }
- private List beatmapSets;
+ public IBindableList BeatmapSets => beatmapSets;
+
+ private readonly BindableList beatmapSets = new BindableList();
public bool IsUserPaused { get; private set; }
@@ -46,7 +49,7 @@ namespace osu.Game.Overlays
[BackgroundDependencyLoader]
private void load()
{
- beatmapSets = beatmaps.GetAllUsableBeatmapSets();
+ beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()));
beatmaps.ItemAdded += handleBeatmapAdded;
beatmaps.ItemRemoved += handleBeatmapRemoved;
}
@@ -140,7 +143,7 @@ namespace osu.Game.Overlays
{
queuedDirection = TrackChangeDirection.Prev;
- var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault();
+ var playable = BeatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? BeatmapSets.LastOrDefault();
if (playable != null)
{
@@ -165,7 +168,7 @@ namespace osu.Game.Overlays
if (!instant)
queuedDirection = TrackChangeDirection.Next;
- var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault();
+ var playable = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? BeatmapSets.FirstOrDefault();
if (playable != null)
{
@@ -200,8 +203,8 @@ namespace osu.Game.Overlays
else
{
//figure out the best direction based on order in playlist.
- var last = beatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
- var next = beatmap.NewValue == null ? -1 : beatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count();
+ var last = BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
+ var next = beatmap.NewValue == null ? -1 : BeatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count();
direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next;
}
diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs
index cf42c8005a..6b79f2af07 100644
--- a/osu.Game/Overlays/NowPlayingOverlay.cs
+++ b/osu.Game/Overlays/NowPlayingOverlay.cs
@@ -81,7 +81,6 @@ namespace osu.Game.Overlays
{
RelativeSizeAxes = Axes.X,
Y = player_height + 10,
- OrderChanged = musicController.ChangeBeatmapSetPosition
},
playerContainer = new Container
{
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs
index 56e56f6ca8..ea2811e5cd 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs
@@ -28,8 +28,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
},
new SettingsCheckbox
{
- LabelText = "Rotate cursor when dragging",
- Bindable = config.GetBindable(OsuSetting.CursorRotation)
+ LabelText = "Hit Lighting",
+ Bindable = config.GetBindable(OsuSetting.HitLighting)
},
new SettingsEnumDropdown
{
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/MainMenuSettings.cs
deleted file mode 100644
index 92f64d0e14..0000000000
--- a/osu.Game/Overlays/Settings/Sections/Graphics/MainMenuSettings.cs
+++ /dev/null
@@ -1,26 +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.Game.Configuration;
-
-namespace osu.Game.Overlays.Settings.Sections.Graphics
-{
- public class MainMenuSettings : SettingsSubsection
- {
- protected override string Header => "User Interface";
-
- [BackgroundDependencyLoader]
- private void load(OsuConfigManager config)
- {
- Children = new[]
- {
- new SettingsCheckbox
- {
- LabelText = "Parallax",
- Bindable = config.GetBindable(OsuSetting.MenuParallax)
- },
- };
- }
- }
-}
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs
new file mode 100644
index 0000000000..a6956b7d9a
--- /dev/null
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs
@@ -0,0 +1,44 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Configuration;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Overlays.Settings.Sections.Graphics
+{
+ public class UserInterfaceSettings : SettingsSubsection
+ {
+ protected override string Header => "User Interface";
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ Children = new Drawable[]
+ {
+ new SettingsCheckbox
+ {
+ LabelText = "Rotate cursor when dragging",
+ Bindable = config.GetBindable(OsuSetting.CursorRotation)
+ },
+ new SettingsCheckbox
+ {
+ LabelText = "Parallax",
+ Bindable = config.GetBindable(OsuSetting.MenuParallax)
+ },
+ new SettingsSlider
+ {
+ LabelText = "Hold-to-confirm activation time",
+ Bindable = config.GetBindable(OsuSetting.UIHoldActivationDelay),
+ KeyboardStep = 50
+ },
+ };
+ }
+
+ private class TimeSlider : OsuSliderBar
+ {
+ public override string TooltipText => Current.Value.ToString("N0") + "ms";
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs
index 3d6086d3ea..89caa3dc8f 100644
--- a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections
new RendererSettings(),
new LayoutSettings(),
new DetailSettings(),
- new MainMenuSettings(),
+ new UserInterfaceSettings(),
};
}
}
diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
index 4f8cb7660b..8289ca175d 100644
--- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
+++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Judgements
///
public class DrawableJudgement : CompositeDrawable
{
- private const float judgement_size = 80;
+ private const float judgement_size = 128;
private OsuColour colours;
@@ -34,10 +34,14 @@ namespace osu.Game.Rulesets.Judgements
///
/// Duration of initial fade in.
- /// Default fade out will start immediately after this duration.
///
protected virtual double FadeInDuration => 100;
+ ///
+ /// Duration to wait until fade out begins. Defaults to .
+ ///
+ protected virtual double FadeOutDelay => FadeInDuration;
+
///
/// Creates a drawable which visualises a .
///
@@ -64,10 +68,10 @@ namespace osu.Game.Rulesets.Judgements
Child = new SkinnableDrawable(new GameplaySkinComponent(Result.Type), _ => JudgementText = new OsuSpriteText
{
Text = Result.Type.GetDescription().ToUpperInvariant(),
- Font = OsuFont.Numeric.With(size: 12),
+ Font = OsuFont.Numeric.With(size: 20),
Colour = judgementColour(Result.Type),
Scale = new Vector2(0.85f, 1),
- })
+ }, confineMode: ConfineMode.NoScaling)
};
}
@@ -76,7 +80,7 @@ namespace osu.Game.Rulesets.Judgements
JudgementBody.ScaleTo(0.9f);
JudgementBody.ScaleTo(1, 500, Easing.OutElastic);
- this.Delay(FadeInDuration).FadeOut(400);
+ this.Delay(FadeOutDelay).FadeOut(400);
}
protected override void LoadComplete()
diff --git a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs
index c0262b783b..120bfc9a23 100644
--- a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs
+++ b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs
@@ -12,5 +12,10 @@ namespace osu.Game.Rulesets.Mods
/// Whether we should allow failing at the current point in time.
///
bool AllowFail { get; }
+
+ ///
+ /// Whether we want to restart on fail. Only used if is true.
+ ///
+ bool RestartOnFail { get; }
}
}
diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs
index e70d58acea..070a10b1c8 100644
--- a/osu.Game/Rulesets/Mods/ModAutoplay.cs
+++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs
@@ -26,7 +26,10 @@ namespace osu.Game.Rulesets.Mods
public override ModType Type => ModType.Automation;
public override string Description => "Watch a perfect automated play through the song.";
public override double ScoreMultiplier => 1;
+
public bool AllowFail => false;
+ public bool RestartOnFail => false;
+
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) };
public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0;
diff --git a/osu.Game/Rulesets/Mods/ModBlockFail.cs b/osu.Game/Rulesets/Mods/ModBlockFail.cs
index 26efc3932d..7d7ecfa416 100644
--- a/osu.Game/Rulesets/Mods/ModBlockFail.cs
+++ b/osu.Game/Rulesets/Mods/ModBlockFail.cs
@@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Mods
///
public bool AllowFail => false;
+ public bool RestartOnFail => false;
+
public void ReadFromConfig(OsuConfigManager config)
{
showHealthBar = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail);
diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs
index 56ec0bec06..a55ebc51d6 100644
--- a/osu.Game/Rulesets/Mods/ModEasy.cs
+++ b/osu.Game/Rulesets/Mods/ModEasy.cs
@@ -2,13 +2,16 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mods
{
- public abstract class ModEasy : Mod, IApplicableToDifficulty
+ public abstract class ModEasy : Mod, IApplicableToDifficulty, IApplicableFailOverride, IApplicableToScoreProcessor
{
public override string Name => "Easy";
public override string Acronym => "EZ";
@@ -18,6 +21,10 @@ namespace osu.Game.Rulesets.Mods
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) };
+ private int retries = 2;
+
+ private BindableNumber health;
+
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
const float ratio = 0.5f;
@@ -26,5 +33,27 @@ namespace osu.Game.Rulesets.Mods
difficulty.DrainRate *= ratio;
difficulty.OverallDifficulty *= ratio;
}
+
+ public bool AllowFail
+ {
+ get
+ {
+ if (retries == 0) return true;
+
+ health.Value = health.MaxValue;
+ retries--;
+
+ return false;
+ }
+ }
+
+ public bool RestartOnFail => false;
+
+ public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
+ {
+ health = scoreProcessor.Health.GetBoundCopy();
+ }
+
+ public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
}
}
diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs
index e332abd914..c4c4ab1f04 100644
--- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs
+++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs
@@ -10,7 +10,7 @@ using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mods
{
- public abstract class ModSuddenDeath : Mod, IApplicableToScoreProcessor
+ public abstract class ModSuddenDeath : Mod, IApplicableToScoreProcessor, IApplicableFailOverride
{
public override string Name => "Sudden Death";
public override string Acronym => "SD";
@@ -21,6 +21,9 @@ namespace osu.Game.Rulesets.Mods
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) };
+ public bool AllowFail => true;
+ public bool RestartOnFail => true;
+
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
{
scoreProcessor.FailConditions += FailCondition;
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 00b57f7249..b94de0df89 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -240,7 +240,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
#endregion
- protected override void SkinChanged(ISkinSource skin, bool allowFallback)
+ protected sealed override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
@@ -250,6 +250,20 @@ namespace osu.Game.Rulesets.Objects.Drawables
AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White;
}
+
+ ApplySkin(skin, allowFallback);
+
+ if (IsLoaded)
+ updateState(State.Value, true);
+ }
+
+ ///
+ /// Called when a change is made to the skin.
+ ///
+ /// The new skin.
+ /// Whether fallback to default skin should be allowed if the custom skin is missing this resource.
+ protected virtual void ApplySkin(ISkinSource skin, bool allowFallback)
+ {
}
///
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index 197c089f71..dd1b3615c7 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -84,7 +84,7 @@ namespace osu.Game.Rulesets
public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle };
- public virtual IResourceStore CreateReourceStore() => new NamespacedResourceStore(new DllResourceStore(GetType().Assembly.Location), @"Resources");
+ public virtual IResourceStore CreateResourceStore() => new NamespacedResourceStore(new DllResourceStore(GetType().Assembly.Location), @"Resources");
public abstract string Description { get; }
diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs
index a34bb6e8ea..d68b0e94c5 100644
--- a/osu.Game/Rulesets/UI/DrawableRuleset.cs
+++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs
@@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.UI
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
- var resources = Ruleset.CreateReourceStore();
+ var resources = Ruleset.CreateResourceStore();
if (resources != null)
{
diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs
index e25c3bd0e7..98e27240d3 100644
--- a/osu.Game/Rulesets/UI/RulesetInputManager.cs
+++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs
@@ -137,9 +137,9 @@ namespace osu.Game.Rulesets.UI
{
}
- public bool OnPressed(T action) => Target.Children.OfType>().Any(c => c.OnPressed(action));
+ public bool OnPressed(T action) => Target.Children.OfType>().Any(c => c.OnPressed(action, Clock.ElapsedFrameTime > 0));
- public bool OnReleased(T action) => Target.Children.OfType>().Any(c => c.OnReleased(action));
+ public bool OnReleased(T action) => Target.Children.OfType>().Any(c => c.OnReleased(action, Clock.ElapsedFrameTime > 0));
}
#endregion
diff --git a/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs
index 77edd24612..2115d784a0 100644
--- a/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs
+++ b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs
@@ -22,6 +22,6 @@ namespace osu.Game.Scoring.Legacy
}
protected override Ruleset GetRuleset(int rulesetId) => rulesets.GetRuleset(rulesetId).CreateInstance();
- protected override WorkingBeatmap GetBeatmap(string md5Hash) => beatmaps.GetWorkingBeatmap(beatmaps.QueryBeatmap(b => b.MD5Hash == md5Hash));
+ protected override WorkingBeatmap GetBeatmap(string md5Hash) => beatmaps.GetWorkingBeatmap(beatmaps.QueryBeatmap(b => !b.BeatmapSet.DeletePending && b.MD5Hash == md5Hash));
}
}
diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs
new file mode 100644
index 0000000000..19e9c329d6
--- /dev/null
+++ b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.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 osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osuTK;
+
+namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents
+{
+ public abstract class LabelledComponent : CompositeDrawable
+ {
+ protected const float CONTENT_PADDING_VERTICAL = 10;
+ protected const float CONTENT_PADDING_HORIZONTAL = 15;
+ protected const float CORNER_RADIUS = 15;
+
+ ///
+ /// The component that is being displayed.
+ ///
+ protected readonly Drawable Component;
+
+ private readonly OsuTextFlowContainer labelText;
+ private readonly OsuTextFlowContainer descriptionText;
+
+ ///
+ /// Creates a new .
+ ///
+ /// Whether the component should be padded or should be expanded to the bounds of this .
+ protected LabelledComponent(bool padded)
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+
+ CornerRadius = CORNER_RADIUS;
+ Masking = true;
+
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = OsuColour.FromHex("1c2125"),
+ },
+ new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Padding = padded
+ ? new MarginPadding { Horizontal = CONTENT_PADDING_HORIZONTAL, Vertical = CONTENT_PADDING_VERTICAL }
+ : new MarginPadding { Left = CONTENT_PADDING_HORIZONTAL },
+ Spacing = new Vector2(0, 12),
+ Children = new Drawable[]
+ {
+ new GridContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ labelText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Bold))
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ AutoSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Right = 20 }
+ },
+ new Container
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Child = Component = CreateComponent().With(d =>
+ {
+ d.Anchor = Anchor.CentreRight;
+ d.Origin = Anchor.CentreRight;
+ })
+ }
+ },
+ },
+ RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
+ ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
+ },
+ descriptionText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true))
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Padding = new MarginPadding { Bottom = padded ? 0 : CONTENT_PADDING_VERTICAL },
+ Alpha = 0,
+ }
+ }
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour osuColour)
+ {
+ descriptionText.Colour = osuColour.Yellow;
+ }
+
+ public string Label
+ {
+ set => labelText.Text = value;
+ }
+
+ public string Description
+ {
+ set
+ {
+ descriptionText.Text = value;
+
+ if (!string.IsNullOrEmpty(value))
+ descriptionText.Show();
+ else
+ descriptionText.Hide();
+ }
+ }
+
+ ///
+ /// Creates the component that should be displayed.
+ ///
+ /// The component.
+ protected abstract Drawable CreateComponent();
+ }
+}
diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs
index 073ab639e3..17f999d519 100644
--- a/osu.Game/Screens/Menu/Disclaimer.cs
+++ b/osu.Game/Screens/Menu/Disclaimer.cs
@@ -173,7 +173,11 @@ namespace osu.Game.Screens.Menu
.Then(5500)
.FadeOut(250)
.ScaleTo(0.9f, 250, Easing.InQuint)
- .Finally(d => this.Push(nextScreen));
+ .Finally(d =>
+ {
+ if (nextScreen != null)
+ this.Push(nextScreen);
+ });
}
}
}
diff --git a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs
index 519834859d..aaa3a77e74 100644
--- a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs
+++ b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs
@@ -9,6 +9,10 @@ namespace osu.Game.Screens.Menu
{
public class ExitConfirmOverlay : HoldToConfirmOverlay, IKeyBindingHandler
{
+ protected override bool AllowMultipleFires => true;
+
+ public void Abort() => AbortConfirm();
+
public bool OnPressed(GlobalAction action)
{
if (action == GlobalAction.Back)
@@ -24,7 +28,8 @@ namespace osu.Game.Screens.Menu
{
if (action == GlobalAction.Back)
{
- AbortConfirm();
+ if (!Fired)
+ AbortConfirm();
return true;
}
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index a006877082..dd81569e26 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -1,18 +1,22 @@
// 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 osuTK;
using osuTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
+using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osu.Game.Overlays;
+using osu.Game.Overlays.Dialog;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Charts;
using osu.Game.Screens.Edit;
@@ -51,15 +55,35 @@ namespace osu.Game.Screens.Menu
[Resolved]
private IAPIProvider api { get; set; }
+ [Resolved(canBeNull: true)]
+ private DialogOverlay dialogOverlay { get; set; }
+
private BackgroundScreenDefault background;
protected override BackgroundScreen CreateBackground() => background;
+ private Bindable holdDelay;
+
+ private ExitConfirmOverlay exitConfirmOverlay;
+
[BackgroundDependencyLoader(true)]
- private void load(DirectOverlay direct, SettingsOverlay settings)
+ private void load(DirectOverlay direct, SettingsOverlay settings, OsuConfigManager config)
{
+ holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay);
+
if (host.CanExit)
- AddInternal(new ExitConfirmOverlay { Action = this.Exit });
+ {
+ AddInternal(exitConfirmOverlay = new ExitConfirmOverlay
+ {
+ Action = () =>
+ {
+ if (holdDelay.Value > 0)
+ confirmAndExit();
+ else
+ this.Exit();
+ }
+ });
+ }
AddRangeInternal(new Drawable[]
{
@@ -74,7 +98,7 @@ namespace osu.Game.Screens.Menu
OnEdit = delegate { this.Push(new Editor()); },
OnSolo = onSolo,
OnMulti = delegate { this.Push(new Multiplayer()); },
- OnExit = this.Exit,
+ OnExit = confirmAndExit,
}
}
},
@@ -103,6 +127,12 @@ namespace osu.Game.Screens.Menu
preloadSongSelect();
}
+ private void confirmAndExit()
+ {
+ exitConfirmed = true;
+ this.Exit();
+ }
+
private void preloadSongSelect()
{
if (songSelect == null)
@@ -141,6 +171,7 @@ namespace osu.Game.Screens.Menu
}
private bool loginDisplayed;
+ private bool exitConfirmed;
protected override void LogoArriving(OsuLogo logo, bool resuming)
{
@@ -221,9 +252,40 @@ namespace osu.Game.Screens.Menu
public override bool OnExiting(IScreen next)
{
+ if (!exitConfirmed && dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog))
+ {
+ dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort()));
+ return true;
+ }
+
buttons.State = ButtonSystemState.Exit;
this.FadeOut(3000);
return base.OnExiting(next);
}
+
+ private class ConfirmExitDialog : PopupDialog
+ {
+ public ConfirmExitDialog(Action confirm, Action cancel)
+ {
+ HeaderText = "Are you sure you want to exit?";
+ BodyText = "Last chance to back out.";
+
+ Icon = FontAwesome.Solid.ExclamationTriangle;
+
+ Buttons = new PopupDialogButton[]
+ {
+ new PopupDialogOkButton
+ {
+ Text = @"Good bye",
+ Action = confirm
+ },
+ new PopupDialogCancelButton
+ {
+ Text = @"Just a little more",
+ Action = cancel
+ },
+ };
+ }
+ }
}
}
diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs
index b1948d02d5..379c4c89ed 100644
--- a/osu.Game/Screens/Play/GameplayClock.cs
+++ b/osu.Game/Screens/Play/GameplayClock.cs
@@ -42,5 +42,7 @@ namespace osu.Game.Screens.Play
public double FramesPerSecond => underlyingClock.FramesPerSecond;
public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo;
+
+ public IClock Source => underlyingClock;
}
}
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index eee7235a6e..0f9edf5606 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -231,7 +231,6 @@ namespace osu.Game.Screens.Play
protected virtual KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay
{
- FadeTime = 50,
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Margin = new MarginPadding(10),
diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs
index 88a62ac8d4..f4109a63d0 100644
--- a/osu.Game/Screens/Play/KeyCounter.cs
+++ b/osu.Game/Screens/Play/KeyCounter.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Collections.Generic;
-using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -22,9 +20,6 @@ namespace osu.Game.Screens.Play
private Container textLayer;
private SpriteText countSpriteText;
- private readonly List states = new List();
- private KeyCounterState currentState;
-
public bool IsCounting { get; set; } = true;
private int countPresses;
@@ -52,20 +47,30 @@ namespace osu.Game.Screens.Play
{
isLit = value;
updateGlowSprite(value);
-
- if (value && IsCounting)
- {
- CountPresses++;
- saveState();
- }
}
}
}
+ public void Increment()
+ {
+ if (!IsCounting)
+ return;
+
+ CountPresses++;
+ }
+
+ public void Decrement()
+ {
+ if (!IsCounting)
+ return;
+
+ CountPresses--;
+ }
+
//further: change default values here and in KeyCounterCollection if needed, instead of passing them in every constructor
public Color4 KeyDownTextColor { get; set; } = Color4.DarkGray;
public Color4 KeyUpTextColor { get; set; } = Color4.White;
- public int FadeTime { get; set; }
+ public double FadeTime { get; set; }
protected KeyCounter(string name)
{
@@ -73,11 +78,8 @@ namespace osu.Game.Screens.Play
}
[BackgroundDependencyLoader(true)]
- private void load(TextureStore textures, GameplayClock clock)
+ private void load(TextureStore textures)
{
- if (clock != null)
- Clock = clock;
-
Children = new Drawable[]
{
buttonSprite = new Sprite
@@ -132,42 +134,16 @@ namespace osu.Game.Screens.Play
{
if (show)
{
- glowSprite.FadeIn(FadeTime);
- textLayer.FadeColour(KeyDownTextColor, FadeTime);
+ double remainingFadeTime = FadeTime * (1 - glowSprite.Alpha);
+ glowSprite.FadeIn(remainingFadeTime, Easing.OutQuint);
+ textLayer.FadeColour(KeyDownTextColor, remainingFadeTime, Easing.OutQuint);
}
else
{
- glowSprite.FadeOut(FadeTime);
- textLayer.FadeColour(KeyUpTextColor, FadeTime);
+ double remainingFadeTime = 8 * FadeTime * glowSprite.Alpha;
+ glowSprite.FadeOut(remainingFadeTime, Easing.OutQuint);
+ textLayer.FadeColour(KeyUpTextColor, remainingFadeTime, Easing.OutQuint);
}
}
-
- public void ResetCount()
- {
- CountPresses = 0;
- states.Clear();
- }
-
- protected override void Update()
- {
- base.Update();
-
- if (currentState?.Time > Clock.CurrentTime)
- restoreStateTo(Clock.CurrentTime);
- }
-
- private void saveState()
- {
- if (currentState == null || currentState.Time < Clock.CurrentTime)
- states.Add(currentState = new KeyCounterState(Clock.CurrentTime, CountPresses));
- }
-
- private void restoreStateTo(double time)
- {
- states.RemoveAll(state => state.Time > time);
-
- currentState = states.LastOrDefault();
- CountPresses = currentState?.Count ?? 0;
- }
}
}
diff --git a/osu.Game/Screens/Play/KeyCounterAction.cs b/osu.Game/Screens/Play/KeyCounterAction.cs
index 8deac653ad..f60ad7aa5a 100644
--- a/osu.Game/Screens/Play/KeyCounterAction.cs
+++ b/osu.Game/Screens/Play/KeyCounterAction.cs
@@ -1,11 +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.Input.Bindings;
-
namespace osu.Game.Screens.Play
{
- public class KeyCounterAction : KeyCounter, IKeyBindingHandler
+ public class KeyCounterAction : KeyCounter
where T : struct
{
public T Action { get; }
@@ -16,15 +14,25 @@ namespace osu.Game.Screens.Play
Action = action;
}
- public bool OnPressed(T action)
+ public bool OnPressed(T action, bool forwards)
{
- if (action.Equals(Action)) IsLit = true;
+ if (!action.Equals(Action))
+ return false;
+
+ IsLit = true;
+ if (forwards)
+ Increment();
return false;
}
- public bool OnReleased(T action)
+ public bool OnReleased(T action, bool forwards)
{
- if (action.Equals(Action)) IsLit = false;
+ if (!action.Equals(Action))
+ return false;
+
+ IsLit = false;
+ if (!forwards)
+ Decrement();
return false;
}
}
diff --git a/osu.Game/Screens/Play/KeyCounterDisplay.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs
index d5967f5899..1edb95ca46 100644
--- a/osu.Game/Screens/Play/KeyCounterDisplay.cs
+++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs
@@ -17,6 +17,7 @@ namespace osu.Game.Screens.Play
public class KeyCounterDisplay : FillFlowContainer
{
private const int duration = 100;
+ private const double key_fade_time = 80;
public readonly Bindable Visible = new Bindable(true);
private readonly Bindable configVisibility = new Bindable();
@@ -33,17 +34,11 @@ namespace osu.Game.Screens.Play
base.Add(key);
key.IsCounting = IsCounting;
- key.FadeTime = FadeTime;
+ key.FadeTime = key_fade_time;
key.KeyDownTextColor = KeyDownTextColor;
key.KeyUpTextColor = KeyUpTextColor;
}
- public void ResetCount()
- {
- foreach (var counter in Children)
- counter.ResetCount();
- }
-
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
@@ -68,22 +63,6 @@ namespace osu.Game.Screens.Play
}
}
- private int fadeTime;
-
- public int FadeTime
- {
- get => fadeTime;
- set
- {
- if (value != fadeTime)
- {
- fadeTime = value;
- foreach (var child in Children)
- child.FadeTime = value;
- }
- }
- }
-
private Color4 keyDownTextColor = Color4.DarkGray;
public Color4 KeyDownTextColor
@@ -123,11 +102,6 @@ namespace osu.Game.Screens.Play
private Receptor receptor;
- public Receptor GetReceptor()
- {
- return receptor ?? (receptor = new Receptor(this));
- }
-
public void SetReceptor(Receptor receptor)
{
if (this.receptor != null)
diff --git a/osu.Game/Screens/Play/KeyCounterKeyboard.cs b/osu.Game/Screens/Play/KeyCounterKeyboard.cs
index d9b6dca79d..29188b6b59 100644
--- a/osu.Game/Screens/Play/KeyCounterKeyboard.cs
+++ b/osu.Game/Screens/Play/KeyCounterKeyboard.cs
@@ -18,7 +18,12 @@ namespace osu.Game.Screens.Play
protected override bool OnKeyDown(KeyDownEvent e)
{
- if (e.Key == Key) IsLit = true;
+ if (e.Key == Key)
+ {
+ IsLit = true;
+ Increment();
+ }
+
return base.OnKeyDown(e);
}
diff --git a/osu.Game/Screens/Play/KeyCounterMouse.cs b/osu.Game/Screens/Play/KeyCounterMouse.cs
index 95fa58e5c0..828441de6e 100644
--- a/osu.Game/Screens/Play/KeyCounterMouse.cs
+++ b/osu.Game/Screens/Play/KeyCounterMouse.cs
@@ -36,7 +36,12 @@ namespace osu.Game.Screens.Play
protected override bool OnMouseDown(MouseDownEvent e)
{
- if (e.Button == Button) IsLit = true;
+ if (e.Button == Button)
+ {
+ IsLit = true;
+ Increment();
+ }
+
return base.OnMouseDown(e);
}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 309f4837e5..44be73b089 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -86,6 +86,12 @@ namespace osu.Game.Screens.Play
[Cached(Type = typeof(IBindable>))]
protected new readonly Bindable> Mods = new Bindable>(Array.Empty());
+ ///
+ /// Whether failing should be allowed.
+ /// By default, this checks whether all selected mods allow failing.
+ ///
+ protected virtual bool AllowFail => Mods.Value.OfType().All(m => m.AllowFail);
+
private readonly bool allowPause;
private readonly bool showResults;
@@ -360,7 +366,7 @@ namespace osu.Game.Screens.Play
private bool onFail()
{
- if (Mods.Value.OfType().Any(m => !m.AllowFail))
+ if (!AllowFail)
return false;
HasFailed = true;
@@ -372,6 +378,10 @@ namespace osu.Game.Screens.Play
PauseOverlay.Hide();
failAnimation.Start();
+
+ if (Mods.Value.OfType().Any(m => m.RestartOnFail))
+ Restart();
+
return true;
}
diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs
index a9c0ee3a15..b040549efc 100644
--- a/osu.Game/Screens/Play/ReplayPlayer.cs
+++ b/osu.Game/Screens/Play/ReplayPlayer.cs
@@ -9,6 +9,9 @@ namespace osu.Game.Screens.Play
{
private readonly Score score;
+ // Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108)
+ protected override bool AllowFail => false;
+
public ReplayPlayer(Score score, bool allowPause = true, bool showResults = true)
: base(allowPause, showResults)
{
diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs
index dd7b5826d5..33c7595b37 100644
--- a/osu.Game/Screens/Play/SongProgressBar.cs
+++ b/osu.Game/Screens/Play/SongProgressBar.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.MathUtils;
+using osu.Framework.Threading;
namespace osu.Game.Screens.Play
{
@@ -121,6 +122,12 @@ namespace osu.Game.Screens.Play
handleBase.X = newX;
}
- protected override void OnUserChange(double value) => OnSeek?.Invoke(value);
+ private ScheduledDelegate scheduledSeek;
+
+ protected override void OnUserChange(double value)
+ {
+ scheduledSeek?.Cancel();
+ scheduledSeek = Schedule(() => OnSeek?.Invoke(value));
+ }
}
}
diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs
index 7f82d3cc12..6caef8e2aa 100644
--- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs
+++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
- Text = @"Mods",
+ Text = @"Selected Mods",
Alpha = 0,
},
};
diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
index 712ab7b571..9cc84c8bdd 100644
--- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
+++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
@@ -24,12 +24,26 @@ namespace osu.Game.Screens.Select.Carousel
{
base.Filter(criteria);
- bool match = criteria.Ruleset == null || Beatmap.RulesetID == criteria.Ruleset.ID || (Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps);
+ bool match =
+ criteria.Ruleset == null ||
+ Beatmap.RulesetID == criteria.Ruleset.ID ||
+ (Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps);
- foreach (var criteriaTerm in criteria.SearchTerms)
- match &=
- Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0) ||
- Beatmap.Version.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0;
+ match &= criteria.StarDifficulty.IsInRange(Beatmap.StarDifficulty);
+ match &= criteria.ApproachRate.IsInRange(Beatmap.BaseDifficulty.ApproachRate);
+ match &= criteria.DrainRate.IsInRange(Beatmap.BaseDifficulty.DrainRate);
+ match &= criteria.CircleSize.IsInRange(Beatmap.BaseDifficulty.CircleSize);
+ match &= criteria.Length.IsInRange(Beatmap.Length);
+ match &= criteria.BPM.IsInRange(Beatmap.BPM);
+
+ match &= criteria.BeatDivisor.IsInRange(Beatmap.BeatDivisor);
+ match &= criteria.OnlineStatus.IsInRange(Beatmap.Status);
+
+ if (match)
+ foreach (var criteriaTerm in criteria.SearchTerms)
+ match &=
+ Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0) ||
+ Beatmap.Version.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0;
Filtered.Value = !match;
}
diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs
index ed74b01fc9..e3c23f7e22 100644
--- a/osu.Game/Screens/Select/FilterControl.cs
+++ b/osu.Game/Screens/Select/FilterControl.cs
@@ -16,6 +16,8 @@ using Container = osu.Framework.Graphics.Containers.Container;
using osu.Framework.Graphics.Shapes;
using osu.Game.Configuration;
using osu.Game.Rulesets;
+using System.Text.RegularExpressions;
+using osu.Game.Beatmaps;
namespace osu.Game.Screens.Select
{
@@ -33,14 +35,24 @@ namespace osu.Game.Screens.Select
private Bindable groupMode;
- public FilterCriteria CreateCriteria() => new FilterCriteria
+ public FilterCriteria CreateCriteria()
{
- Group = groupMode.Value,
- Sort = sortMode.Value,
- SearchText = searchTextBox.Text,
- AllowConvertedBeatmaps = showConverted.Value,
- Ruleset = ruleset.Value
- };
+ var query = searchTextBox.Text;
+
+ var criteria = new FilterCriteria
+ {
+ Group = groupMode.Value,
+ Sort = sortMode.Value,
+ AllowConvertedBeatmaps = showConverted.Value,
+ Ruleset = ruleset.Value
+ };
+
+ applyQueries(criteria, ref query);
+
+ criteria.SearchText = query;
+
+ return criteria;
+ }
public Action Exit;
@@ -169,5 +181,129 @@ namespace osu.Game.Screens.Select
}
private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria());
+
+ private static readonly Regex query_syntax_regex = new Regex(
+ @"\b(?stars|ar|dr|cs|divisor|length|objects|bpm|status)(?[=:><]+)(?\S*)",
+ RegexOptions.Compiled | RegexOptions.IgnoreCase);
+
+ private void applyQueries(FilterCriteria criteria, ref string query)
+ {
+ foreach (Match match in query_syntax_regex.Matches(query))
+ {
+ var key = match.Groups["key"].Value.ToLower();
+ var op = match.Groups["op"].Value;
+ var value = match.Groups["value"].Value;
+
+ switch (key)
+ {
+ case "stars" when float.TryParse(value, out var stars):
+ updateCriteriaRange(ref criteria.StarDifficulty, op, stars);
+ break;
+
+ case "ar" when float.TryParse(value, out var ar):
+ updateCriteriaRange(ref criteria.ApproachRate, op, ar);
+ break;
+
+ case "dr" when float.TryParse(value, out var dr):
+ updateCriteriaRange(ref criteria.DrainRate, op, dr);
+ break;
+
+ case "cs" when float.TryParse(value, out var cs):
+ updateCriteriaRange(ref criteria.CircleSize, op, cs);
+ break;
+
+ case "bpm" when double.TryParse(value, out var bpm):
+ updateCriteriaRange(ref criteria.BPM, op, bpm);
+ break;
+
+ case "length" when double.TryParse(value.TrimEnd('m', 's', 'h'), out var length):
+ var scale =
+ value.EndsWith("ms") ? 1 :
+ value.EndsWith("s") ? 1000 :
+ value.EndsWith("m") ? 60000 :
+ value.EndsWith("h") ? 3600000 : 1000;
+
+ updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0);
+ break;
+
+ case "divisor" when int.TryParse(value, out var divisor):
+ updateCriteriaRange(ref criteria.BeatDivisor, op, divisor);
+ break;
+
+ case "status" when Enum.TryParse(value, true, out var statusValue):
+ updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue);
+ break;
+ }
+
+ query = query.Replace(match.ToString(), "");
+ }
+ }
+
+ private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, float value, float tolerance = 0.05f)
+ {
+ updateCriteriaRange(ref range, op, value);
+
+ switch (op)
+ {
+ case "=":
+ case ":":
+ range.Min = value - tolerance;
+ range.Max = value + tolerance;
+ break;
+ }
+ }
+
+ private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, double value, double tolerance = 0.05)
+ {
+ updateCriteriaRange(ref range, op, value);
+
+ switch (op)
+ {
+ case "=":
+ case ":":
+ range.Min = value - tolerance;
+ range.Max = value + tolerance;
+ break;
+ }
+ }
+
+ private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, T value)
+ where T : struct, IComparable
+ {
+ switch (op)
+ {
+ default:
+ return;
+
+ case "=":
+ case ":":
+ range.IsInclusive = true;
+ range.Min = value;
+ range.Max = value;
+ break;
+
+ case ">":
+ range.IsInclusive = false;
+ range.Min = value;
+ break;
+
+ case ">=":
+ case ">:":
+ range.IsInclusive = true;
+ range.Min = value;
+ break;
+
+ case "<":
+ range.IsInclusive = false;
+ range.Max = value;
+ break;
+
+ case "<=":
+ case "<:":
+ range.IsInclusive = true;
+ range.Max = value;
+ break;
+ }
+ }
}
}
diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs
index 140010ff54..a3fa1b10ca 100644
--- a/osu.Game/Screens/Select/FilterCriteria.cs
+++ b/osu.Game/Screens/Select/FilterCriteria.cs
@@ -2,7 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using System.Linq;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Screens.Select.Filter;
@@ -13,6 +15,15 @@ namespace osu.Game.Screens.Select
public GroupMode Group;
public SortMode Sort;
+ public OptionalRange StarDifficulty;
+ public OptionalRange ApproachRate;
+ public OptionalRange DrainRate;
+ public OptionalRange CircleSize;
+ public OptionalRange Length;
+ public OptionalRange BPM;
+ public OptionalRange BeatDivisor;
+ public OptionalRange OnlineStatus;
+
public string[] SearchTerms = Array.Empty();
public RulesetInfo Ruleset;
@@ -26,8 +37,48 @@ namespace osu.Game.Screens.Select
set
{
searchText = value;
- SearchTerms = searchText.Split(',', ' ', '!').Where(s => !string.IsNullOrEmpty(s)).ToArray();
+ SearchTerms = searchText.Split(new[] { ',', ' ', '!' }, StringSplitOptions.RemoveEmptyEntries).ToArray();
}
}
+
+ public struct OptionalRange : IEquatable>
+ where T : struct, IComparable
+ {
+ public bool IsInRange(T value)
+ {
+ if (Min != null)
+ {
+ int comparison = Comparer.Default.Compare(value, Min.Value);
+
+ if (comparison < 0)
+ return false;
+
+ if (comparison == 0 && !IsInclusive)
+ return false;
+ }
+
+ if (Max != null)
+ {
+ int comparison = Comparer.Default.Compare(value, Max.Value);
+
+ if (comparison > 0)
+ return false;
+
+ if (comparison == 0 && !IsInclusive)
+ return false;
+ }
+
+ return true;
+ }
+
+ public T? Min;
+ public T? Max;
+ public bool IsInclusive;
+
+ public bool Equals(OptionalRange other)
+ => Min.Equals(other.Min)
+ && Max.Equals(other.Max)
+ && IsInclusive.Equals(other.IsInclusive);
+ }
}
}
diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
index 33f040755e..337d46ecdd 100644
--- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
+++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
@@ -9,6 +9,7 @@ using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
+using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Leaderboards;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@@ -37,8 +38,25 @@ namespace osu.Game.Screens.Select.Leaderboards
}
}
+ public APILegacyUserTopScoreInfo TopScore
+ {
+ get => topScoreContainer.Score.Value;
+ set
+ {
+ if (value == null)
+ topScoreContainer.Hide();
+ else
+ {
+ topScoreContainer.Show();
+ topScoreContainer.Score.Value = value;
+ }
+ }
+ }
+
private bool filterMods;
+ private UserTopScoreContainer topScoreContainer;
+
///
/// Whether to apply the game's currently selected mods as a filter when retrieving scores.
///
@@ -77,6 +95,17 @@ namespace osu.Game.Screens.Select.Leaderboards
if (filterMods)
UpdateScores();
};
+
+ Content.Add(topScoreContainer = new UserTopScoreContainer
+ {
+ ScoreSelected = s => ScoreSelected?.Invoke(s)
+ });
+ }
+
+ protected override void Reset()
+ {
+ base.Reset();
+ TopScore = null;
}
protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local;
@@ -141,7 +170,11 @@ namespace osu.Game.Screens.Select.Leaderboards
var req = new GetScoresRequest(Beatmap, ruleset.Value ?? Beatmap.Ruleset, Scope, requestMods);
- req.Success += r => scoresCallback?.Invoke(r.Scores);
+ req.Success += r =>
+ {
+ scoresCallback?.Invoke(r.Scores);
+ TopScore = r.UserScore;
+ };
return req;
}
diff --git a/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs
new file mode 100644
index 0000000000..da8f676cd0
--- /dev/null
+++ b/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs
@@ -0,0 +1,94 @@
+// 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.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Online.Leaderboards;
+using osu.Game.Scoring;
+using osuTK;
+
+namespace osu.Game.Screens.Select.Leaderboards
+{
+ public class UserTopScoreContainer : VisibilityContainer
+ {
+ private const int duration = 500;
+
+ private readonly Container scoreContainer;
+
+ public Bindable Score = new Bindable();
+
+ public Action ScoreSelected;
+
+ protected override bool StartHidden => true;
+
+ public UserTopScoreContainer()
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+
+ Margin = new MarginPadding { Vertical = 5 };
+
+ Children = new Drawable[]
+ {
+ new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(5),
+ Children = new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Text = @"your personal best".ToUpper(),
+ Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold),
+ },
+ scoreContainer = new Container
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ }
+ }
+ }
+ };
+
+ Score.BindValueChanged(onScoreChanged);
+ }
+
+ private CancellationTokenSource loadScoreCancellation;
+
+ private void onScoreChanged(ValueChangedEvent score)
+ {
+ var newScore = score.NewValue;
+
+ scoreContainer.Clear();
+ loadScoreCancellation?.Cancel();
+
+ if (newScore == null)
+ return;
+
+ LoadComponentAsync(new LeaderboardScore(newScore.Score, newScore.Position)
+ {
+ Action = () => ScoreSelected?.Invoke(newScore.Score)
+ }, drawableScore =>
+ {
+ scoreContainer.Child = drawableScore;
+ drawableScore.FadeInFromZero(duration, Easing.OutQuint);
+ }, (loadScoreCancellation = new CancellationTokenSource()).Token);
+ }
+
+ protected override void PopIn() => this.FadeIn(duration, Easing.OutQuint);
+
+ protected override void PopOut() => this.FadeOut(duration, Easing.OutQuint);
+ }
+}
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index d0cb5986a8..fca801ce78 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -223,7 +223,7 @@ namespace osu.Game.Screens.Select
});
}
- BeatmapDetails.Leaderboard.ScoreSelected += s => this.Push(new SoloResults(s));
+ BeatmapDetails.Leaderboard.ScoreSelected += score => this.Push(new SoloResults(score));
}
[BackgroundDependencyLoader(true)]
diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs
index 98f158c725..4b6eea6b6e 100644
--- a/osu.Game/Skinning/DefaultLegacySkin.cs
+++ b/osu.Game/Skinning/DefaultLegacySkin.cs
@@ -13,6 +13,13 @@ namespace osu.Game.Skinning
: base(Info, storage, audioManager, string.Empty)
{
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
+ Configuration.ComboColours.AddRange(new[]
+ {
+ new Color4(255, 192, 0, 255),
+ new Color4(0, 202, 0, 255),
+ new Color4(18, 124, 255, 255),
+ new Color4(242, 24, 57, 255),
+ });
}
public static SkinInfo Info { get; } = new SkinInfo
diff --git a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs
new file mode 100644
index 0000000000..e9251f8011
--- /dev/null
+++ b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs
@@ -0,0 +1,35 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets;
+
+namespace osu.Game.Tests.Beatmaps
+{
+ ///
+ /// Base class for tests of converting enumeration flags to ruleset mod instances.
+ ///
+ public abstract class LegacyModConversionTest
+ {
+ ///
+ /// Creates the whose legacy mod conversion is to be tested.
+ ///
+ ///
+ protected abstract Ruleset CreateRuleset();
+
+ protected void Test(LegacyMods legacyMods, Type[] expectedMods)
+ {
+ var ruleset = CreateRuleset();
+ var mods = ruleset.ConvertLegacyMods(legacyMods).ToList();
+ Assert.AreEqual(expectedMods.Length, mods.Count);
+
+ foreach (var modType in expectedMods)
+ {
+ Assert.IsNotNull(mods.SingleOrDefault(mod => mod.GetType() == modType));
+ }
+ }
+ }
+}
diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs
index 2b8baab57c..8e98d51962 100644
--- a/osu.Game/Tests/Visual/OsuTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuTestScene.cs
@@ -1,4 +1,4 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 5703293caf..a27a94b8f9 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 683dccf3ae..a6516e6d1b 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -118,8 +118,8 @@
-
-
+
+
diff --git a/osu.iOS.sln.DotSettings b/osu.iOS.sln.DotSettings
index 3b2b851d45..752b817910 100644
--- a/osu.iOS.sln.DotSettings
+++ b/osu.iOS.sln.DotSettings
@@ -1,4 +1,4 @@
-
+
True
True
True
@@ -165,8 +165,17 @@
HINT
HINT
WARNING
+ WARNING
<?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile>
Code Cleanup (peppy)
+ Required
+ Required
+ Required
+ Explicit
+ ExpressionBody
+ ExpressionBody
+ True
+ NEXT_LINE
True
True
True
@@ -176,12 +185,22 @@
True
True
NEXT_LINE
+ 1
+ 1
+ NEXT_LINE
+ MULTILINE
NEXT_LINE
+ 1
+ 1
True
+ NEXT_LINE
NEVER
NEVER
+ True
False
+ True
NEVER
+ False
False
True
False
@@ -189,6 +208,7 @@
True
True
False
+ False
CHOP_IF_LONG
True
200
diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings
index c3e274569d..ed162eed6e 100644
--- a/osu.sln.DotSettings
+++ b/osu.sln.DotSettings
@@ -218,9 +218,14 @@
WARNING
<?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile>
Code Cleanup (peppy)
+ Required
+ Required
+ Required
+ Explicit
ExpressionBody
ExpressionBody
True
+ NEXT_LINE
True
True
True
@@ -232,14 +237,20 @@
NEXT_LINE
1
1
+ NEXT_LINE
+ MULTILINE
NEXT_LINE
1
1
True
+ NEXT_LINE
NEVER
NEVER
+ True
False
+ True
NEVER
+ False
False
True
False
@@ -247,6 +258,7 @@
True
True
False
+ False
CHOP_IF_LONG
True
200