diff --git a/osu.Android.props b/osu.Android.props
index adc340a734..c57fc342ba 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -1,5 +1,7 @@
+ Debug
+ AnyCPU
bin\$(Configuration)
4
2.0
@@ -35,7 +37,7 @@
None
True
prompt
- true
+ true
false
SdkOnly
False
@@ -49,7 +51,6 @@
osu.licenseheader
-
@@ -60,7 +61,7 @@
-
-
+
+
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.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 538aaf2d7a..2461351110 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -23,10 +23,10 @@
-
+
-
+
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/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
index a603d96201..7b8c699f2c 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
@@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Catch.Tests
{
}
+ protected override bool Autoplay => true;
+
[Test]
public void TestHyperDash()
{
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/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
index 00734810b3..dd4a58a5ef 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
@@ -50,6 +50,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
public Func CheckPosition;
+ public bool IsOnPlate;
+
+ public override bool RemoveWhenNotAlive => IsOnPlate;
+
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (CheckPosition == null) return;
@@ -71,11 +75,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
switch (state)
{
case ArmedState.Miss:
- this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out).Expire();
+ this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out);
break;
case ArmedState.Hit:
- this.FadeOut().Expire();
+ this.FadeOut();
break;
}
}
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
index 8dd00756f2..6c8515eb90 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
@@ -27,6 +27,8 @@ namespace osu.Game.Rulesets.Catch.Replays
protected Replay Replay;
+ private CatchReplayFrame currentFrame;
+
public override Replay Generate()
{
// todo: add support for HT DT
@@ -35,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
- Replay.Frames.Add(new CatchReplayFrame(-100000, lastPosition));
-
void moveToNext(CatchHitObject h)
{
float positionChange = Math.Abs(lastPosition - h.X);
@@ -58,18 +57,18 @@ namespace osu.Game.Rulesets.Catch.Replays
{
//we are already in the correct range.
lastTime = h.StartTime;
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime, lastPosition));
+ addFrame(h.StartTime, lastPosition);
return;
}
if (impossibleJump)
{
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
+ addFrame(h.StartTime, h.X);
}
else if (h.HyperDash)
{
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable, lastPosition));
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
+ addFrame(h.StartTime - timeAvailable, lastPosition);
+ addFrame(h.StartTime, h.X);
}
else if (dashRequired)
{
@@ -81,16 +80,16 @@ namespace osu.Game.Rulesets.Catch.Replays
float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable);
//dash movement
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + 1, lastPosition, true));
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition));
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
+ addFrame(h.StartTime - timeAvailable + 1, lastPosition, true);
+ addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition);
+ addFrame(h.StartTime, h.X);
}
else
{
double timeBefore = positionChange / movement_speed;
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeBefore, lastPosition));
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
+ addFrame(h.StartTime - timeBefore, lastPosition);
+ addFrame(h.StartTime, h.X);
}
lastTime = h.StartTime;
@@ -122,5 +121,16 @@ namespace osu.Game.Rulesets.Catch.Replays
return Replay;
}
+
+ 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.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
index 103aa6c3f1..22532bc9ec 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Diagnostics;
+using System.Linq;
using osu.Framework.Input.StateChanges;
using osu.Framework.MathUtils;
using osu.Game.Replays;
@@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Replays
{
}
- protected override bool IsImportant(CatchReplayFrame frame) => frame.Position > 0;
+ protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any();
protected float? Position
{
@@ -38,21 +39,11 @@ namespace osu.Game.Rulesets.Catch.Replays
{
if (!Position.HasValue) return new List();
- var actions = new List();
-
- if (CurrentFrame.Dashing)
- actions.Add(CatchAction.Dash);
-
- if (Position.Value > CurrentFrame.Position)
- actions.Add(CatchAction.MoveRight);
- else if (Position.Value < CurrentFrame.Position)
- actions.Add(CatchAction.MoveLeft);
-
return new List
{
new CatchReplayState
{
- PressedActions = actions,
+ PressedActions = CurrentFrame?.Actions ?? new List(),
CatcherX = Position.Value
},
};
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
index 1e88b35c3b..b41a5e0612 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Replays.Legacy;
using osu.Game.Rulesets.Catch.UI;
@@ -11,6 +12,8 @@ namespace osu.Game.Rulesets.Catch.Replays
{
public class CatchReplayFrame : ReplayFrame, IConvertibleReplayFrame
{
+ public List Actions = new List();
+
public float Position;
public bool Dashing;
@@ -18,17 +21,40 @@ namespace osu.Game.Rulesets.Catch.Replays
{
}
- public CatchReplayFrame(double time, float? position = null, bool dashing = false)
+ public CatchReplayFrame(double time, float? position = null, bool dashing = false, CatchReplayFrame lastFrame = null)
: base(time)
{
Position = position ?? -1;
Dashing = dashing;
+
+ if (Dashing)
+ Actions.Add(CatchAction.Dash);
+
+ if (lastFrame != null)
+ {
+ if (Position > lastFrame.Position)
+ lastFrame.Actions.Add(CatchAction.MoveRight);
+ else if (Position < lastFrame.Position)
+ lastFrame.Actions.Add(CatchAction.MoveLeft);
+ }
}
- public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap)
+ public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{
- Position = legacyFrame.Position.X / CatchPlayfield.BASE_WIDTH;
- Dashing = legacyFrame.ButtonState == ReplayButtonState.Left1;
+ Position = currentFrame.Position.X / CatchPlayfield.BASE_WIDTH;
+ Dashing = currentFrame.ButtonState == ReplayButtonState.Left1;
+
+ if (Dashing)
+ Actions.Add(CatchAction.Dash);
+
+ // this probably needs some cross-checking with osu-stable to ensure it is actually correct.
+ if (lastFrame is CatchReplayFrame lastCatchFrame)
+ {
+ if (Position > lastCatchFrame.Position)
+ lastCatchFrame.Actions.Add(CatchAction.MoveRight);
+ else if (Position < lastCatchFrame.Position)
+ Actions.Add(CatchAction.MoveLeft);
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index ceda643335..56c8b33e02 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -69,10 +69,12 @@ namespace osu.Game.Rulesets.Catch.UI
caughtFruit.RelativePositionAxes = Axes.None;
caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0);
+ caughtFruit.IsOnPlate = true;
caughtFruit.Anchor = Anchor.TopCentre;
caughtFruit.Origin = Anchor.Centre;
caughtFruit.Scale *= 0.7f;
+ caughtFruit.LifetimeStart = caughtFruit.HitObject.StartTime;
caughtFruit.LifetimeEnd = double.MaxValue;
MovableCatcher.Add(caughtFruit);
@@ -205,7 +207,8 @@ namespace osu.Game.Rulesets.Catch.UI
AdditiveTarget.Add(additive);
- additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire();
+ additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
+ additive.Expire(true);
Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
}
@@ -300,6 +303,7 @@ namespace osu.Game.Rulesets.Catch.UI
{
this.FadeColour(Color4.White, hyper_dash_transition_length, Easing.OutQuint);
this.FadeTo(1, hyper_dash_transition_length, Easing.OutQuint);
+ Trail &= Dashing;
}
}
else
@@ -406,6 +410,9 @@ namespace osu.Game.Rulesets.Catch.UI
f.MoveToY(f.Y + 75, 750, Easing.InSine);
f.FadeOut(750);
+
+ // todo: this shouldn't exist once DrawableHitObject's ClearTransformsAfter overrides are repaired.
+ f.LifetimeStart = Time.Current;
f.Expire();
}
}
@@ -436,10 +443,13 @@ namespace osu.Game.Rulesets.Catch.UI
ExplodingFruitTarget.Add(fruit);
}
+ fruit.ClearTransforms();
fruit.MoveToY(fruit.Y - 50, 250, Easing.OutSine).Then().MoveToY(fruit.Y + 50, 500, Easing.InSine);
fruit.MoveToX(fruit.X + originalX * 6, 1000);
fruit.FadeOut(750);
+ // todo: this shouldn't exist once DrawableHitObject's ClearTransformsAfter overrides are repaired.
+ fruit.LifetimeStart = Time.Current;
fruit.Expire();
}
}
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 20ac5eaa39..a5248c7712 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
@@ -3,6 +3,7 @@
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Testing;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays;
@@ -12,8 +13,14 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
+ [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()
{
@@ -26,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]
@@ -47,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]
@@ -67,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]
@@ -89,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]
@@ -110,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]
@@ -137,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]
@@ -164,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/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs
new file mode 100644
index 0000000000..26a1b1b1ec
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs
@@ -0,0 +1,62 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Tests.Visual;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [TestFixture]
+ public class TestSceneHitExplosion : OsuTestScene
+ {
+ private ScrollingTestContainer scrolling;
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DrawableNote),
+ typeof(DrawableManiaHitObject),
+ };
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Child = scrolling = new ScrollingTestContainer(ScrollingDirection.Down)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativePositionAxes = Axes.Y,
+ Y = -0.25f,
+ Size = new Vector2(Column.COLUMN_WIDTH, NotePiece.NOTE_HEIGHT),
+ };
+
+ int runcount = 0;
+
+ AddRepeatStep("explode", () =>
+ {
+ runcount++;
+
+ if (runcount % 15 > 12)
+ return;
+
+ scrolling.AddRange(new Drawable[]
+ {
+ new HitExplosion((runcount / 15) % 2 == 0 ? new Color4(94, 0, 57, 255) : new Color4(6, 84, 0, 255), runcount % 6 != 0)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ });
+ }, 100);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
index 031abb08e2..8dae5e6d84 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.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;
@@ -11,6 +11,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
@@ -40,6 +41,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
Child = new FillFlowContainer
{
+ Clock = new FramedClock(new ManualClock()),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
@@ -62,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Tests
private Drawable createNoteDisplay(ScrollingDirection direction, int identifier, out DrawableNote hitObject)
{
- var note = new Note { StartTime = 999999999 };
+ var note = new Note { StartTime = 0 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return new ScrollingTestContainer(direction)
@@ -77,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Tests
private Drawable createHoldNoteDisplay(ScrollingDirection direction, int identifier, out DrawableHoldNote hitObject)
{
- var note = new HoldNote { StartTime = 999999999, Duration = 5000 };
+ var note = new HoldNote { StartTime = 0, Duration = 5000 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return new ScrollingTestContainer(direction)
@@ -133,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Tests
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Both,
Width = 1.25f,
- Colour = Color4.Black.Opacity(0.5f)
+ Colour = Color4.Green.Opacity(0.5f)
},
content = new Container { RelativeSizeAxes = Axes.Both }
}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
index 395e6daf0a..e7fd601abe 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
@@ -15,6 +15,7 @@ using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
using osuTK;
@@ -114,8 +115,7 @@ namespace osu.Game.Rulesets.Mania.Tests
var obj = new BarLine
{
StartTime = Time.Current + 2000,
- ControlPoint = new TimingControlPoint(),
- BeatIndex = major ? 0 : 1
+ Major = major,
};
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
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/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
index b591f9da22..f5412dcfc5 100644
--- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
+++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
{
base.InitialiseDefaults();
- Set(ManiaRulesetSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0);
+ Set(ManiaRulesetSetting.ScrollTime, 1500.0, 50.0, 5000.0, 50.0);
Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
}
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/BarLine.cs b/osu.Game.Rulesets.Mania/Objects/BarLine.cs
deleted file mode 100644
index 4c644a8f09..0000000000
--- a/osu.Game.Rulesets.Mania/Objects/BarLine.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Game.Beatmaps.ControlPoints;
-
-namespace osu.Game.Rulesets.Mania.Objects
-{
- public class BarLine : ManiaHitObject
- {
- ///
- /// The control point which this bar line is part of.
- ///
- public TimingControlPoint ControlPoint;
-
- ///
- /// The index of the beat which this bar line represents within the control point.
- /// This is a "major" bar line if % == 0.
- ///
- public int BeatIndex;
- }
-}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
index e9c352c97e..be21610525 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
@@ -4,6 +4,7 @@
using osuTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK.Graphics;
@@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// Visualises a . Although this derives DrawableManiaHitObject,
/// this does not handle input/sound like a normal hit object.
///
- public class DrawableBarLine : DrawableManiaHitObject
+ public class DrawableBarLine : DrawableHitObject
{
///
/// Height of major bar line triangles.
@@ -40,9 +41,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Colour = new Color4(255, 204, 33, 255),
});
- bool isMajor = barLine.BeatIndex % (int)barLine.ControlPoint.TimeSignature == 0;
-
- if (isMajor)
+ if (barLine.Major)
{
AddInternal(new EquilateralTriangle
{
@@ -65,10 +64,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
});
}
- if (!isMajor && barLine.BeatIndex % 2 == 1)
+ if (!barLine.Major)
Alpha = 0.2f;
}
+ protected override void UpdateInitialTransforms()
+ {
+ }
+
protected override void UpdateStateTransforms(ArmedState state)
{
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
index e5b114ca81..5bfa07bd14 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
@@ -51,11 +51,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
switch (state)
{
case ArmedState.Miss:
- this.FadeOut(150, Easing.In).Expire();
+ this.FadeOut(150, Easing.In);
break;
case ArmedState.Hit:
- this.FadeOut(150, Easing.OutQuint).Expire();
+ this.FadeOut(150, Easing.OutQuint);
break;
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
index 2cd81104a3..8f353ae138 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
- Colour = colour.NewValue.Lighten(1f).Opacity(0.6f),
+ Colour = colour.NewValue.Lighten(1f).Opacity(0.2f),
Radius = 10,
};
}, true);
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
index bb33693783..4521af7dfb 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
@@ -18,8 +18,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
///
internal class NotePiece : Container, IHasAccentColour
{
- public const float NOTE_HEIGHT = 10;
- private const float head_colour_height = 6;
+ public const float NOTE_HEIGHT = 12;
private readonly IBindable direction = new Bindable();
@@ -39,8 +38,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
colouredBox = new Box
{
RelativeSizeAxes = Axes.X,
- Height = head_colour_height,
- Alpha = 0.2f
+ Height = NOTE_HEIGHT / 2,
+ Alpha = 0.1f
}
};
}
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.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
index f7277d3669..70ba5cd938 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Replays
Actions.AddRange(actions);
}
- public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap)
+ public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{
// We don't need to fully convert, just create the converter
var converter = new ManiaBeatmapConverter(beatmap);
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index 91dd236ab1..3d2a070b0f 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.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.Linq;
@@ -11,6 +11,8 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
@@ -19,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.UI
{
public class Column : ScrollingPlayfield, IKeyBindingHandler, IHasAccentColour
{
- private const float column_width = 45;
+ public const float COLUMN_WIDTH = 80;
private const float special_column_width = 70;
///
@@ -41,10 +43,7 @@ namespace osu.Game.Rulesets.Mania.UI
Index = index;
RelativeSizeAxes = Axes.Y;
- Width = column_width;
-
- Masking = true;
- CornerRadius = 5;
+ Width = COLUMN_WIDTH;
background = new ColumnBackground { RelativeSizeAxes = Axes.Both };
@@ -67,7 +66,7 @@ namespace osu.Game.Rulesets.Mania.UI
explosionContainer = new Container
{
Name = "Hit explosions",
- RelativeSizeAxes = Axes.Both
+ RelativeSizeAxes = Axes.Both,
}
}
},
@@ -90,6 +89,12 @@ namespace osu.Game.Rulesets.Mania.UI
Bottom = dir.NewValue == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0,
};
+ explosionContainer.Padding = new MarginPadding
+ {
+ Top = dir.NewValue == ScrollingDirection.Up ? NotePiece.NOTE_HEIGHT / 2 : 0,
+ Bottom = dir.NewValue == ScrollingDirection.Down ? NotePiece.NOTE_HEIGHT / 2 : 0
+ };
+
keyArea.Anchor = keyArea.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
}, true);
}
@@ -108,7 +113,7 @@ namespace osu.Game.Rulesets.Mania.UI
isSpecial = value;
- Width = isSpecial ? special_column_width : column_width;
+ Width = isSpecial ? special_column_width : COLUMN_WIDTH;
}
}
@@ -163,9 +168,10 @@ namespace osu.Game.Rulesets.Mania.UI
if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value)
return;
- explosionContainer.Add(new HitExplosion(judgedObject)
+ explosionContainer.Add(new HitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick)
{
- Anchor = Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre
+ Anchor = Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre,
+ Origin = Anchor.Centre
});
}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
index 5ee78aa496..57241da564 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
@@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
Name = "Background",
RelativeSizeAxes = Axes.Both,
- Alpha = 0.3f
},
backgroundOverlay = new Box
{
@@ -82,7 +81,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
if (!IsLoaded)
return;
- background.Colour = AccentColour;
+ background.Colour = AccentColour.Darken(5);
var brightPoint = AccentColour.Opacity(0.6f);
var dimPoint = AccentColour.Opacity(0);
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
index a0d713067d..386bcbb724 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
+using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK.Graphics;
@@ -17,7 +18,6 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
public class ColumnHitObjectArea : CompositeDrawable, IHasAccentColour
{
- private const float hit_target_height = 10;
private const float hit_target_bar_height = 2;
private readonly IBindable direction = new Bindable();
@@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components
hitTargetBar = new Box
{
RelativeSizeAxes = Axes.X,
- Height = hit_target_height,
+ Height = NotePiece.NOTE_HEIGHT,
+ Alpha = 0.6f,
Colour = Color4.Black
},
hitTargetLine = new Container
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
index f26526fe70..29863fba2e 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
@@ -2,14 +2,11 @@
// 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.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Input;
-using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Input.Handlers;
using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Beatmaps;
@@ -19,8 +16,8 @@ using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
@@ -45,33 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI
public DrawableManiaRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
- // Generate the bar lines
- double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
-
- var timingPoints = Beatmap.ControlPointInfo.TimingPoints;
- var barLines = new List();
-
- for (int i = 0; i < timingPoints.Count; i++)
- {
- TimingControlPoint point = timingPoints[i];
-
- // Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object
- double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - point.BeatLength : lastObjectTime + point.BeatLength * (int)point.TimeSignature;
-
- int index = 0;
-
- for (double t = timingPoints[i].Time; Precision.DefinitelyBigger(endTime, t); t += point.BeatLength, index++)
- {
- barLines.Add(new BarLine
- {
- StartTime = t,
- ControlPoint = point,
- BeatIndex = index
- });
- }
- }
-
- BarLines = barLines;
+ BarLines = new BarLineGenerator(Beatmap).BarLines;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
index 48470add8b..ccbff226a9 100644
--- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
@@ -1,16 +1,14 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osuTK.Graphics;
+using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
-using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
-using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
-using osu.Game.Rulesets.Objects.Drawables;
using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI
{
@@ -18,51 +16,112 @@ namespace osu.Game.Rulesets.Mania.UI
{
public override bool RemoveWhenNotAlive => true;
- private readonly CircularContainer circle;
+ private readonly CircularContainer largeFaint;
+ private readonly CircularContainer mainGlow1;
- public HitExplosion(DrawableHitObject judgedObject)
+ public HitExplosion(Color4 objectColour, bool isSmall = false)
{
- bool isTick = judgedObject is DrawableHoldNoteTick;
-
- Origin = Anchor.Centre;
-
RelativeSizeAxes = Axes.X;
- Y = NotePiece.NOTE_HEIGHT / 2;
Height = NotePiece.NOTE_HEIGHT;
// scale roughly in-line with visual appearance of notes
- Scale = new Vector2(isTick ? 0.4f : 0.8f);
+ Scale = new Vector2(1f, 0.6f);
- InternalChild = circle = new CircularContainer
+ if (isSmall)
+ Scale *= 0.5f;
+
+ const float angle_variangle = 15; // should be less than 45
+
+ const float roundness = 80;
+
+ const float initial_height = 10;
+
+ var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1);
+
+ InternalChildren = new Drawable[]
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- // we want our size to be very small so the glow dominates it.
- Size = new Vector2(0.1f),
- EdgeEffect = new EdgeEffectParameters
+ largeFaint = new CircularContainer
{
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.1f, judgedObject.AccentColour.Value, Color4.White, 0, 1),
- Radius = 100,
- },
- Child = new Box
- {
- Alpha = 0,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
- AlwaysPresent = true
+ Masking = true,
+ // we want our size to be very small so the glow dominates it.
+ Size = new Vector2(0.8f),
+ Blending = BlendingParameters.Additive,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
+ Roundness = 160,
+ Radius = 200,
+ },
+ },
+ mainGlow1 = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Blending = BlendingParameters.Additive,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
+ Roundness = 20,
+ Radius = 50,
+ },
+ },
+ new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Size = new Vector2(0.01f, initial_height),
+ Blending = BlendingParameters.Additive,
+ Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = colour,
+ Roundness = roundness,
+ Radius = 40,
+ },
+ },
+ new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Size = new Vector2(0.01f, initial_height),
+ Blending = BlendingParameters.Additive,
+ Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = colour,
+ Roundness = roundness,
+ Radius = 40,
+ },
}
};
}
protected override void LoadComplete()
{
+ const double duration = 200;
+
base.LoadComplete();
- circle.ResizeTo(circle.Size * new Vector2(4, 20), 1000, Easing.OutQuint);
- this.FadeIn(16).Then().FadeOut(500, Easing.OutQuint);
+ largeFaint
+ .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
+ .FadeOut(duration * 2);
+ mainGlow1.ScaleTo(1.4f, duration, Easing.OutQuint);
+
+ this.FadeOut(duration, Easing.Out);
Expire(true);
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
index 5ab07416a6..12faa499ad 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
@@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
index a28de7ea58..98a4b7d0b6 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
@@ -12,6 +12,7 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
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/Resources/default-skin/approachcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/approachcircle@2x.png
deleted file mode 100755
index db2f4a5730..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/approachcircle@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursor@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursor@2x.png
deleted file mode 100755
index 75f9ba5ea6..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursor@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursormiddle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursormiddle@2x.png
deleted file mode 100755
index ebf59c18ba..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursormiddle@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit0@2x.png
deleted file mode 100644
index bdb2bcbc41..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit0@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100@2x.png
deleted file mode 100644
index 7db8eb3124..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100k@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100k@2x.png
deleted file mode 100644
index 206840e467..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100k@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300@2x.png
deleted file mode 100644
index 2c7c07852f..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300g@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300g@2x.png
deleted file mode 100644
index 1ce746e3a4..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300g@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300k@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300k@2x.png
deleted file mode 100755
index b0db9c00af..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300k@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit50@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit50@2x.png
deleted file mode 100644
index 94c09d263a..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit50@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircle@2x.png
deleted file mode 100755
index 6674616472..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircle@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircleoverlay@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircleoverlay@2x.png
deleted file mode 100755
index 1f98c1697e..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircleoverlay@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-nd@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-nd@2x.png
deleted file mode 100644
index 626fd91e38..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-nd@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-spec@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-spec@2x.png
deleted file mode 100644
index 76fd9ab168..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-spec@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb0@2x.png
deleted file mode 100644
index 0a24a72808..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb0@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb1@2x.png
deleted file mode 100644
index e99f076947..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb1@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb2@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb2@2x.png
deleted file mode 100644
index cd36a0ae16..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb2@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb3@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb3@2x.png
deleted file mode 100644
index f494bd3f51..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb3@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb4@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb4@2x.png
deleted file mode 100644
index a5b19887d6..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb4@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb5@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb5@2x.png
deleted file mode 100644
index 4bb01f0e88..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb5@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb6@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb6@2x.png
deleted file mode 100644
index 859e0aa4c1..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb6@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb7@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb7@2x.png
deleted file mode 100644
index 90efda0994..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb7@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb8@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb8@2x.png
deleted file mode 100644
index fcdf4ed4a4..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb8@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb9@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb9@2x.png
deleted file mode 100644
index c990cf0fe6..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb9@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
index 29e5146ff1..38aac50df6 100644
--- a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
+++ b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
@@ -26,12 +26,12 @@ namespace osu.Game.Rulesets.Osu.Tests
}
[BackgroundDependencyLoader]
- private void load(AudioManager audio)
+ private void load(AudioManager audio, SkinManager skinManager)
{
var dllStore = new DllResourceStore("osu.Game.Rulesets.Osu.Tests.dll");
metricsSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true);
- defaultSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/default_skin"), audio, false);
+ defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
specialSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/special_skin"), audio, true);
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
new file mode 100644
index 0000000000..685a51d208
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
@@ -0,0 +1,128 @@
+// 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.Framework.Allocation;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Testing.Input;
+using osu.Game.Audio;
+using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Rulesets.Osu.UI.Cursor;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneCursorTrail : OsuTestScene
+ {
+ [Test]
+ public void TestSmoothCursorTrail()
+ {
+ Container scalingContainer = null;
+
+ createTest(() => scalingContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new CursorTrail()
+ });
+
+ AddStep("set large scale", () => scalingContainer.Scale = new Vector2(10));
+ }
+
+ [Test]
+ public void TestLegacySmoothCursorTrail()
+ {
+ createTest(() => new LegacySkinContainer(false)
+ {
+ Child = new LegacyCursorTrail()
+ });
+ }
+
+ [Test]
+ public void TestLegacyDisjointCursorTrail()
+ {
+ createTest(() => new LegacySkinContainer(true)
+ {
+ Child = new LegacyCursorTrail()
+ });
+ }
+
+ private void createTest(Func createContent) => AddStep("create trail", () =>
+ {
+ Clear();
+
+ Add(new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.8f),
+ Child = new MovingCursorInputManager { Child = createContent?.Invoke() }
+ });
+ });
+
+ [Cached(typeof(ISkinSource))]
+ private class LegacySkinContainer : Container, ISkinSource
+ {
+ private readonly bool disjoint;
+
+ public LegacySkinContainer(bool disjoint)
+ {
+ this.disjoint = disjoint;
+
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException();
+
+ public Texture GetTexture(string componentName)
+ {
+ switch (componentName)
+ {
+ case "cursortrail":
+ var tex = new Texture(Texture.WhitePixel.TextureGL);
+
+ if (disjoint)
+ tex.ScaleAdjust = 1 / 25f;
+ return tex;
+
+ case "cursormiddle":
+ return disjoint ? null : Texture.WhitePixel;
+ }
+
+ return null;
+ }
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
+
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
+
+ public event Action SourceChanged;
+ }
+
+ private class MovingCursorInputManager : ManualInputManager
+ {
+ public MovingCursorInputManager()
+ {
+ UseParentInput = false;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ const double spin_duration = 1000;
+ double currentTime = Time.Current;
+
+ double angle = (currentTime % spin_duration) / spin_duration * 2 * Math.PI;
+ Vector2 rPos = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
+
+ MoveMouseTo(ToScreenSpace(DrawSize / 2 + DrawSize / 3 * rPos));
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
index ebb6cd3a5a..aa170eae1e 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
@@ -6,23 +6,68 @@ using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Testing.Input;
using osu.Game.Rulesets.Osu.UI.Cursor;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
public class TestSceneGameplayCursor : SkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => new[] { typeof(CursorTrail) };
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(OsuCursorContainer),
+ typeof(CursorTrail)
+ };
[BackgroundDependencyLoader]
private void load()
{
- SetContents(() => new OsuCursorContainer
+ SetContents(() => new MovingCursorInputManager
{
- RelativeSizeAxes = Axes.Both,
- Masking = true,
+ Child = new ClickingCursorContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ }
});
}
+
+ private class ClickingCursorContainer : OsuCursorContainer
+ {
+ protected override void Update()
+ {
+ base.Update();
+
+ double currentTime = Time.Current;
+
+ if (((int)(currentTime / 1000)) % 2 == 0)
+ OnPressed(OsuAction.LeftButton);
+ else
+ OnReleased(OsuAction.LeftButton);
+ }
+ }
+
+ private class MovingCursorInputManager : ManualInputManager
+ {
+ public MovingCursorInputManager()
+ {
+ UseParentInput = false;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ const double spin_duration = 5000;
+ double currentTime = Time.Current;
+
+ double angle = (currentTime % spin_duration) / spin_duration * 2 * Math.PI;
+ Vector2 rPos = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
+
+ MoveMouseTo(ToScreenSpace(DrawSize / 2 + DrawSize / 3 * rPos));
+ }
+ }
}
}
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 985dcbca86..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,
@@ -86,6 +86,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AccentColour.BindValueChanged(accent => ApproachCircle.Colour = accent.NewValue, true);
}
+ public override double LifetimeStart
+ {
+ get => base.LifetimeStart;
+ set
+ {
+ base.LifetimeStart = value;
+ ApproachCircle.LifetimeStart = value;
+ }
+ }
+
+ public override double LifetimeEnd
+ {
+ get => base.LifetimeEnd;
+ set
+ {
+ base.LifetimeEnd = value;
+ ApproachCircle.LifetimeEnd = value;
+ }
+ }
+
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
Debug.Assert(HitObject.HitWindows != null);
@@ -113,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);
@@ -122,6 +142,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdateStateTransforms(ArmedState state)
{
+ base.UpdateStateTransforms(state);
+
Debug.Assert(HitObject.HitWindows != null);
switch (state)
@@ -132,22 +154,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Expire(true);
hitArea.HitAction = null;
-
- // override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early.
- LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.WindowFor(HitResult.Miss);
break;
case ArmedState.Miss:
ApproachCircle.FadeOut(50);
this.FadeOut(100);
- Expire();
break;
case ArmedState.Hit:
ApproachCircle.FadeOut(50);
// todo: temporary / arbitrary
- this.Delay(800).Expire();
+ this.Delay(800).FadeOut();
break;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
index fcd42314fc..c46343c73c 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
@@ -41,6 +41,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength);
+ protected override void UpdateStateTransforms(ArmedState state)
+ {
+ base.UpdateStateTransforms(state);
+
+ switch (state)
+ {
+ case ArmedState.Idle:
+ // Manually set to reduce the number of future alive objects to a bare minimum.
+ LifetimeStart = HitObject.StartTime - HitObject.TimePreempt;
+ break;
+ }
+ }
+
protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement);
}
}
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/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
index 00a943a67f..84d2a4af9b 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
@@ -74,6 +74,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdateStateTransforms(ArmedState state)
{
+ base.UpdateStateTransforms(state);
+
switch (state)
{
case ArmedState.Idle:
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 00c953c393..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;
@@ -202,6 +205,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdateStateTransforms(ArmedState state)
{
+ base.UpdateStateTransforms(state);
+
Ball.FadeIn();
Ball.ScaleTo(HitObject.Scale);
@@ -219,10 +224,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
break;
}
- this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
+ this.FadeOut(fade_out_time, Easing.OutQuint);
}
-
- Expire(true);
}
public Drawable ProxiedLayer => HeadCircle.ApproachCircle;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
index ba931976a8..9d4d9958a1 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
@@ -75,6 +75,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdateStateTransforms(ArmedState state)
{
+ base.UpdateStateTransforms(state);
+
switch (state)
{
case ArmedState.Idle:
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index 49aaa2aaea..d1b9ee6cb4 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -215,14 +215,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdateStateTransforms(ArmedState state)
{
+ base.UpdateStateTransforms(state);
+
var sequence = this.Delay(Spinner.Duration).FadeOut(160);
switch (state)
{
- case ArmedState.Idle:
- Expire(true);
- break;
-
case ArmedState.Hit:
sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out);
break;
@@ -231,8 +229,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
sequence.ScaleTo(Scale * 0.8f, 320, Easing.In);
break;
}
-
- Expire();
}
}
}
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/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
index 5971f053c2..8dd48eace0 100644
--- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
@@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Osu
HitCircle,
FollowPoint,
Cursor,
+ CursorTrail,
SliderScorePoint,
ApproachCircle,
ReverseArrow,
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
index 4d90fcadd5..e6c6db5e61 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
@@ -26,11 +26,11 @@ namespace osu.Game.Rulesets.Osu.Replays
Actions.AddRange(actions);
}
- public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap)
+ public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{
- Position = legacyFrame.Position;
- if (legacyFrame.MouseLeft) Actions.Add(OsuAction.LeftButton);
- if (legacyFrame.MouseRight) Actions.Add(OsuAction.RightButton);
+ Position = currentFrame.Position;
+ if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton);
+ if (currentFrame.MouseRight) Actions.Add(OsuAction.RightButton);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs
new file mode 100644
index 0000000000..1885c76fcc
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs
@@ -0,0 +1,55 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Osu.UI.Cursor;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Osu.Skinning
+{
+ public class LegacyCursorTrail : CursorTrail
+ {
+ private const double disjoint_trail_time_separation = 1000 / 60.0;
+
+ private bool disjointTrail;
+ private double lastTrailTime;
+
+ public LegacyCursorTrail()
+ {
+ Blending = BlendingParameters.Additive;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ Texture = skin.GetTexture("cursortrail");
+ disjointTrail = skin.GetTexture("cursormiddle") == null;
+
+ if (Texture != null)
+ {
+ // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation.
+ Texture.ScaleAdjust *= 1.6f;
+ }
+ }
+
+ protected override double FadeDuration => disjointTrail ? 150 : 500;
+
+ protected override bool InterpolateMovements => !disjointTrail;
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ if (!disjointTrail)
+ return base.OnMouseMove(e);
+
+ if (Time.Current - lastTrailTime >= disjoint_trail_time_separation)
+ {
+ lastTrailTime = Time.Current;
+ return base.OnMouseMove(e);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
index 5957b81d7e..479c250eab 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
@@ -45,6 +45,9 @@ namespace osu.Game.Rulesets.Osu.Skinning
switch (osuComponent.Component)
{
+ case OsuSkinComponents.FollowPoint:
+ return this.GetAnimation(component.LookupName, true, false);
+
case OsuSkinComponents.SliderFollowCircle:
return this.GetAnimation("sliderfollowcircle", true, true);
@@ -78,17 +81,23 @@ namespace osu.Game.Rulesets.Osu.Skinning
return null;
+ case OsuSkinComponents.CursorTrail:
+ if (source.GetTexture("cursortrail") != null)
+ return new LegacyCursorTrail();
+
+ return null;
+
case OsuSkinComponents.HitCircleText:
- var font = GetConfig(OsuSkinConfiguration.HitCircleFont)?.Value ?? "default";
+ var font = GetConfig(OsuSkinConfiguration.HitCirclePrefix)?.Value ?? "default";
var overlap = GetConfig(OsuSkinConfiguration.HitCircleOverlap)?.Value ?? 0;
return !hasFont(font)
? null
: new LegacySpriteText(source, font)
{
- // Spacing value was reverse-engineered from the ratio of the rendered sprite size in the visual inspector vs the actual texture size
- Scale = new Vector2(0.96f),
- Spacing = new Vector2(-overlap * 0.89f, 0)
+ // stable applies a blanket 0.8x scale to hitcircle fonts
+ Scale = new Vector2(0.8f),
+ Spacing = new Vector2(-overlap, 0)
};
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
index a6b87150ae..98219cafe8 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
@@ -5,10 +5,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
public enum OsuSkinConfiguration
{
- HitCircleFont,
+ HitCirclePrefix,
HitCircleOverlap,
SliderBorderSize,
SliderPathRadius,
+ AllowSliderBallTint,
CursorExpand,
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 05eb0ffdbf..b32dfd483f 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -5,6 +5,7 @@ using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using osu.Framework.Allocation;
+using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Batches;
using osu.Framework.Graphics.OpenGL.Vertices;
@@ -20,30 +21,15 @@ using osuTK.Graphics.ES30;
namespace osu.Game.Rulesets.Osu.UI.Cursor
{
- internal class CursorTrail : Drawable, IRequireHighFrequencyMousePosition
+ public class CursorTrail : Drawable, IRequireHighFrequencyMousePosition
{
- private int currentIndex;
-
- private IShader shader;
- private Texture texture;
-
- private Vector2 size => texture.Size * Scale;
-
- private double timeOffset;
-
- private float time;
-
- public override bool IsPresent => true;
-
private const int max_sprites = 2048;
private readonly TrailPart[] parts = new TrailPart[max_sprites];
-
- private Vector2? lastPosition;
-
- private readonly InputResampler resampler = new InputResampler();
-
- protected override DrawNode CreateDrawNode() => new TrailDrawNode(this);
+ private int currentIndex;
+ private IShader shader;
+ private double timeOffset;
+ private float time;
public CursorTrail()
{
@@ -60,14 +46,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
}
}
- public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
-
[BackgroundDependencyLoader]
- private void load(ShaderManager shaders, TextureStore textures)
+ private void load(ShaderManager shaders)
{
shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE);
- texture = textures.Get(@"Cursor/cursortrail");
- Scale = new Vector2(1 / texture.ScaleAdjust);
}
protected override void LoadComplete()
@@ -76,6 +58,42 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
resetTime();
}
+ private Texture texture = Texture.WhitePixel;
+
+ public Texture Texture
+ {
+ get => texture;
+ set
+ {
+ if (texture == value)
+ return;
+
+ texture = value;
+ Invalidate(Invalidation.DrawNode);
+ }
+ }
+
+ private readonly Cached partSizeCache = new Cached();
+
+ private Vector2 partSize => partSizeCache.IsValid
+ ? partSizeCache.Value
+ : (partSizeCache.Value = new Vector2(Texture.DisplayWidth, Texture.DisplayHeight) * DrawInfo.Matrix.ExtractScale().Xy);
+
+ public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
+ {
+ if ((invalidation & (Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence)) > 0)
+ partSizeCache.Invalidate();
+
+ return base.Invalidate(invalidation, source, shallPropagate);
+ }
+
+ ///
+ /// The amount of time to fade the cursor trail pieces.
+ ///
+ protected virtual double FadeDuration => 300;
+
+ public override bool IsPresent => true;
+
protected override void Update()
{
base.Update();
@@ -84,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
const int fade_clock_reset_threshold = 1000000;
- time = (float)(Time.Current - timeOffset) / 300f;
+ time = (float)((Time.Current - timeOffset) / FadeDuration);
if (time > fade_clock_reset_threshold)
resetTime();
}
@@ -101,6 +119,16 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
timeOffset = Time.Current;
}
+ ///
+ /// Whether to interpolate mouse movements and add trail pieces at intermediate points.
+ ///
+ protected virtual bool InterpolateMovements => true;
+
+ private Vector2? lastPosition;
+ private readonly InputResampler resampler = new InputResampler();
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
+
protected override bool OnMouseMove(MouseMoveEvent e)
{
Vector2 pos = e.ScreenSpaceMousePosition;
@@ -116,33 +144,43 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
Trace.Assert(lastPosition.HasValue);
- // ReSharper disable once PossibleInvalidOperationException
- Vector2 pos1 = lastPosition.Value;
- Vector2 diff = pos2 - pos1;
- float distance = diff.Length;
- Vector2 direction = diff / distance;
-
- float interval = size.X / 2 * 0.9f;
-
- for (float d = interval; d < distance; d += interval)
+ if (InterpolateMovements)
{
- lastPosition = pos1 + direction * d;
- addPosition(lastPosition.Value);
+ // ReSharper disable once PossibleInvalidOperationException
+ Vector2 pos1 = lastPosition.Value;
+ Vector2 diff = pos2 - pos1;
+ float distance = diff.Length;
+ Vector2 direction = diff / distance;
+
+ float interval = partSize.X / 2.5f;
+
+ for (float d = interval; d < distance; d += interval)
+ {
+ lastPosition = pos1 + direction * d;
+ addPart(lastPosition.Value);
+ }
+ }
+ else
+ {
+ lastPosition = pos2;
+ addPart(lastPosition.Value);
}
}
return base.OnMouseMove(e);
}
- private void addPosition(Vector2 pos)
+ private void addPart(Vector2 screenSpacePosition)
{
- parts[currentIndex].Position = pos;
+ parts[currentIndex].Position = screenSpacePosition;
parts[currentIndex].Time = time;
++parts[currentIndex].InvalidationID;
currentIndex = (currentIndex + 1) % max_sprites;
}
+ protected override DrawNode CreateDrawNode() => new TrailDrawNode(this);
+
private struct TrailPart
{
public Vector2 Position;
@@ -177,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
shader = Source.shader;
texture = Source.texture;
- size = Source.size;
+ size = Source.partSize;
time = Source.time;
for (int i = 0; i < Source.parts.Length; ++i)
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
index 893c7875fa..a944ff88c6 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
@@ -6,9 +6,12 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.UI;
+using osu.Game.Skinning;
+using osuTK;
namespace osu.Game.Rulesets.Osu.UI.Cursor
{
@@ -22,17 +25,14 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private readonly Bindable showTrail = new Bindable(true);
- private readonly CursorTrail cursorTrail;
+ private readonly Drawable cursorTrail;
public OsuCursorContainer()
{
InternalChild = fadeContainer = new Container
{
RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- cursorTrail = new CursorTrail { Depth = 1 }
- }
+ Child = cursorTrail = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling)
};
}
@@ -98,5 +98,15 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint);
ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint);
}
+
+ private class DefaultCursorTrail : CursorTrail
+ {
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ Texture = textures.Get(@"Cursor/cursortrail");
+ Scale = new Vector2(1 / Texture.ScaleAdjust);
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index ea7eee8bb8..d1757de445 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -70,13 +70,7 @@ namespace osu.Game.Rulesets.Osu.UI
base.Add(h);
}
- private void addApproachCircleProxy(Drawable d)
- {
- var proxy = d.CreateProxy();
- proxy.LifetimeStart = d.LifetimeStart;
- proxy.LifetimeEnd = d.LifetimeEnd;
- approachCircles.Add(proxy);
- }
+ private void addApproachCircleProxy(Drawable d) => approachCircles.Add(d.CreateProxy());
public override void PostProcess()
{
@@ -92,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/Objects/BarLine.cs b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs
deleted file mode 100644
index a07012fd71..0000000000
--- a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-namespace osu.Game.Rulesets.Taiko.Objects
-{
- public class BarLine : TaikoHitObject
- {
- }
-}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
index bf89f7e15b..1a5a797f28 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
@@ -3,6 +3,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Objects;
using osuTK;
using osu.Game.Rulesets.Objects.Drawables;
@@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
///
/// A line that scrolls alongside hit objects in the playfield and visualises control points.
///
- public class DrawableBarLine : DrawableHitObject
+ public class DrawableBarLine : DrawableHitObject
{
///
/// The width of the line tracker.
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs
index 4d3a1a3f8a..f5b75a781b 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs
@@ -5,6 +5,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osuTK;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
index f4407a7b54..8e16a21199 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
@@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
case ArmedState.Hit:
case ArmedState.Miss:
- this.Delay(HitObject.Duration).FadeOut(100).Expire();
+ this.Delay(HitObject.Duration).FadeOut(100);
break;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
index cef9a53deb..25b6141a0e 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
switch (state)
{
case ArmedState.Hit:
- this.ScaleTo(0, 100, Easing.OutQuint).Expire();
+ this.ScaleTo(0, 100, Easing.OutQuint);
break;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
index 676ecd5a0b..4b25ff0ecc 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
@@ -105,12 +105,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
validActionPressed = false;
UnproxyContent();
- this.Delay(HitObject.HitWindows.WindowFor(HitResult.Miss)).Expire();
break;
case ArmedState.Miss:
- this.FadeOut(100)
- .Expire();
+ this.FadeOut(100);
break;
case ArmedState.Hit:
@@ -129,9 +127,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
.Then()
.MoveToY(gravity_travel_height * 2, gravity_time * 2, Easing.In);
- this.FadeOut(800)
- .Expire();
-
+ this.FadeOut(800);
break;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
index 094ad1230f..07af7fe7e0 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
@@ -208,8 +208,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
this.FadeOut(transition_duration, Easing.Out);
bodyContainer.ScaleTo(1.4f, transition_duration);
-
- Expire();
}
break;
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs
index 5203415e90..c5ebefc397 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs
@@ -23,12 +23,12 @@ namespace osu.Game.Rulesets.Taiko.Replays
Actions.AddRange(actions);
}
- public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap)
+ public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{
- if (legacyFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim);
- if (legacyFrame.MouseRight2) Actions.Add(TaikoAction.RightRim);
- if (legacyFrame.MouseLeft1) Actions.Add(TaikoAction.LeftCentre);
- if (legacyFrame.MouseLeft2) Actions.Add(TaikoAction.RightCentre);
+ if (currentFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim);
+ if (currentFrame.MouseRight2) Actions.Add(TaikoAction.RightRim);
+ if (currentFrame.MouseLeft1) Actions.Add(TaikoAction.LeftCentre);
+ if (currentFrame.MouseLeft2) Actions.Add(TaikoAction.RightCentre);
}
}
}
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.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
index b03bea578e..5caa9e4626 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
@@ -5,19 +5,18 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Taiko.Replays;
-using System.Linq;
using osu.Framework.Input;
using osu.Game.Configuration;
using osu.Game.Input.Handlers;
using osu.Game.Replays;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Taiko.UI
@@ -38,49 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI
[BackgroundDependencyLoader]
private void load()
{
- loadBarLines();
- }
-
- private void loadBarLines()
- {
- TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1];
- double lastHitTime = 1 + ((lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime);
-
- var timingPoints = Beatmap.ControlPointInfo.TimingPoints.ToList();
-
- if (timingPoints.Count == 0)
- return;
-
- int currentIndex = 0;
- int currentBeat = 0;
- double time = timingPoints[currentIndex].Time;
-
- while (time <= lastHitTime)
- {
- int nextIndex = currentIndex + 1;
-
- if (nextIndex < timingPoints.Count && time > timingPoints[nextIndex].Time)
- {
- currentIndex = nextIndex;
- time = timingPoints[currentIndex].Time;
- currentBeat = 0;
- }
-
- var currentPoint = timingPoints[currentIndex];
-
- var barLine = new BarLine
- {
- StartTime = time,
- };
-
- barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty);
-
- bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0;
- Playfield.Add(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine));
-
- time += currentPoint.BeatLength * (int)currentPoint.TimeSignature;
- currentBeat++;
- }
+ new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar)));
}
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this);
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/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
new file mode 100644
index 0000000000..30686cb947
--- /dev/null
+++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
@@ -0,0 +1,201 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+using osu.Game.Screens.Select;
+using osu.Game.Screens.Select.Carousel;
+
+namespace osu.Game.Tests.NonVisual.Filtering
+{
+ [TestFixture]
+ public class FilterMatchingTest
+ {
+ private BeatmapInfo getExampleBeatmap() => new BeatmapInfo
+ {
+ Ruleset = new RulesetInfo { ID = 5 },
+ StarDifficulty = 4.0d,
+ BaseDifficulty = new BeatmapDifficulty
+ {
+ ApproachRate = 5.0f,
+ DrainRate = 3.0f,
+ CircleSize = 2.0f,
+ },
+ Metadata = new BeatmapMetadata
+ {
+ Artist = "The Artist",
+ ArtistUnicode = "check unicode too",
+ Title = "Title goes here",
+ TitleUnicode = "Title goes here",
+ AuthorString = "The Author",
+ Source = "unit tests",
+ Tags = "look for tags too",
+ },
+ Version = "version as well",
+ Length = 2500,
+ BPM = 160,
+ BeatDivisor = 12,
+ Status = BeatmapSetOnlineStatus.Loved
+ };
+
+ [Test]
+ public void TestCriteriaMatchingNoRuleset()
+ {
+ var exampleBeatmapInfo = getExampleBeatmap();
+ var criteria = new FilterCriteria();
+ var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
+ carouselItem.Filter(criteria);
+ Assert.IsFalse(carouselItem.Filtered.Value);
+ }
+
+ [Test]
+ public void TestCriteriaMatchingSpecificRuleset()
+ {
+ var exampleBeatmapInfo = getExampleBeatmap();
+ var criteria = new FilterCriteria
+ {
+ Ruleset = new RulesetInfo { ID = 6 }
+ };
+ var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
+ carouselItem.Filter(criteria);
+ Assert.IsTrue(carouselItem.Filtered.Value);
+ }
+
+ [Test]
+ public void TestCriteriaMatchingConvertedBeatmaps()
+ {
+ var exampleBeatmapInfo = getExampleBeatmap();
+ var criteria = new FilterCriteria
+ {
+ Ruleset = new RulesetInfo { ID = 6 },
+ AllowConvertedBeatmaps = true
+ };
+ var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
+ carouselItem.Filter(criteria);
+ Assert.IsFalse(carouselItem.Filtered.Value);
+ }
+
+ [Test]
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestCriteriaMatchingRangeMin(bool inclusive)
+ {
+ var exampleBeatmapInfo = getExampleBeatmap();
+ var criteria = new FilterCriteria
+ {
+ Ruleset = new RulesetInfo { ID = 6 },
+ AllowConvertedBeatmaps = true,
+ ApproachRate = new FilterCriteria.OptionalRange
+ {
+ IsLowerInclusive = inclusive,
+ Min = 5.0f
+ }
+ };
+ var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
+ carouselItem.Filter(criteria);
+ Assert.AreEqual(!inclusive, carouselItem.Filtered.Value);
+ }
+
+ [Test]
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestCriteriaMatchingRangeMax(bool inclusive)
+ {
+ var exampleBeatmapInfo = getExampleBeatmap();
+ var criteria = new FilterCriteria
+ {
+ Ruleset = new RulesetInfo { ID = 6 },
+ AllowConvertedBeatmaps = true,
+ BPM = new FilterCriteria.OptionalRange
+ {
+ IsUpperInclusive = inclusive,
+ Max = 160d
+ }
+ };
+ var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
+ carouselItem.Filter(criteria);
+ Assert.AreEqual(!inclusive, carouselItem.Filtered.Value);
+ }
+
+ [Test]
+ [TestCase("artist", false)]
+ [TestCase("artist title author", false)]
+ [TestCase("an artist", true)]
+ [TestCase("tags too", false)]
+ [TestCase("version", false)]
+ [TestCase("an auteur", true)]
+ public void TestCriteriaMatchingTerms(string terms, bool filtered)
+ {
+ var exampleBeatmapInfo = getExampleBeatmap();
+ var criteria = new FilterCriteria
+ {
+ Ruleset = new RulesetInfo { ID = 6 },
+ AllowConvertedBeatmaps = true,
+ SearchText = terms
+ };
+ var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
+ carouselItem.Filter(criteria);
+ Assert.AreEqual(filtered, carouselItem.Filtered.Value);
+ }
+
+ [Test]
+ [TestCase("", false)]
+ [TestCase("The", false)]
+ [TestCase("THE", false)]
+ [TestCase("author", false)]
+ [TestCase("the author", false)]
+ [TestCase("the author AND then something else", true)]
+ [TestCase("unknown", true)]
+ public void TestCriteriaMatchingCreator(string creatorName, bool filtered)
+ {
+ var exampleBeatmapInfo = getExampleBeatmap();
+ var criteria = new FilterCriteria
+ {
+ Creator = new FilterCriteria.OptionalTextFilter { SearchTerm = creatorName }
+ };
+ var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
+ carouselItem.Filter(criteria);
+ Assert.AreEqual(filtered, carouselItem.Filtered.Value);
+ }
+
+ [Test]
+ [TestCase("", false)]
+ [TestCase("The", false)]
+ [TestCase("THE", false)]
+ [TestCase("artist", false)]
+ [TestCase("the artist", false)]
+ [TestCase("the artist AND then something else", true)]
+ [TestCase("unicode too", false)]
+ [TestCase("unknown", true)]
+ public void TestCriteriaMatchingArtist(string artistName, bool filtered)
+ {
+ var exampleBeatmapInfo = getExampleBeatmap();
+ var criteria = new FilterCriteria
+ {
+ Artist = new FilterCriteria.OptionalTextFilter { SearchTerm = artistName }
+ };
+ var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
+ carouselItem.Filter(criteria);
+ Assert.AreEqual(filtered, carouselItem.Filtered.Value);
+ }
+
+ [Test]
+ [TestCase("", false)]
+ [TestCase("artist", false)]
+ [TestCase("unknown", true)]
+ public void TestCriteriaMatchingArtistWithNullUnicodeName(string artistName, bool filtered)
+ {
+ var exampleBeatmapInfo = getExampleBeatmap();
+ exampleBeatmapInfo.Metadata.ArtistUnicode = null;
+
+ var criteria = new FilterCriteria
+ {
+ Artist = new FilterCriteria.OptionalTextFilter { SearchTerm = artistName }
+ };
+ var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
+ carouselItem.Filter(criteria);
+ Assert.AreEqual(filtered, carouselItem.Filtered.Value);
+ }
+ }
+}
diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
new file mode 100644
index 0000000000..9869ddde41
--- /dev/null
+++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
@@ -0,0 +1,184 @@
+// 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;
+using osu.Game.Screens.Select;
+
+namespace osu.Game.Tests.NonVisual.Filtering
+{
+ [TestFixture]
+ public class FilterQueryParserTest
+ {
+ [Test]
+ public void TestApplyQueriesBareWords()
+ {
+ const string query = "looking for a beatmap";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual("looking for a beatmap", filterCriteria.SearchText);
+ Assert.AreEqual(4, filterCriteria.SearchTerms.Length);
+ }
+
+ /*
+ * The following tests have been written a bit strangely (they don't check exact
+ * bound equality with what the filter says).
+ * This is to account for floating-point arithmetic issues.
+ * For example, specifying a bpm<140 filter would previously match beatmaps with BPM
+ * of 139.99999, which would be displayed in the UI as 140.
+ * Due to this the tests check the last tick inside the range and the first tick
+ * outside of the range.
+ */
+
+ [Test]
+ public void TestApplyStarQueries()
+ {
+ const string query = "stars<4 easy";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual("easy", filterCriteria.SearchText.Trim());
+ Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
+ Assert.IsNotNull(filterCriteria.StarDifficulty.Max);
+ Assert.Greater(filterCriteria.StarDifficulty.Max, 3.99d);
+ Assert.Less(filterCriteria.StarDifficulty.Max, 4.00d);
+ Assert.IsNull(filterCriteria.StarDifficulty.Min);
+ }
+
+ [Test]
+ public void TestApplyApproachRateQueries()
+ {
+ const string query = "ar>=9 difficult";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual("difficult", filterCriteria.SearchText.Trim());
+ Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
+ Assert.IsNotNull(filterCriteria.ApproachRate.Min);
+ Assert.Greater(filterCriteria.ApproachRate.Min, 8.9f);
+ Assert.Less(filterCriteria.ApproachRate.Min, 9.0f);
+ Assert.IsNull(filterCriteria.ApproachRate.Max);
+ }
+
+ [Test]
+ public void TestApplyDrainRateQueries()
+ {
+ const string query = "dr>2 quite specific dr<:6";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual("quite specific", filterCriteria.SearchText.Trim());
+ Assert.AreEqual(2, filterCriteria.SearchTerms.Length);
+ Assert.Greater(filterCriteria.DrainRate.Min, 2.0f);
+ Assert.Less(filterCriteria.DrainRate.Min, 2.1f);
+ Assert.Greater(filterCriteria.DrainRate.Max, 6.0f);
+ Assert.Less(filterCriteria.DrainRate.Min, 6.1f);
+ }
+
+ [Test]
+ public void TestApplyBPMQueries()
+ {
+ const string query = "bpm>:200 gotta go fast";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual("gotta go fast", filterCriteria.SearchText.Trim());
+ Assert.AreEqual(3, filterCriteria.SearchTerms.Length);
+ Assert.IsNotNull(filterCriteria.BPM.Min);
+ Assert.Greater(filterCriteria.BPM.Min, 199.99d);
+ Assert.Less(filterCriteria.BPM.Min, 200.00d);
+ Assert.IsNull(filterCriteria.BPM.Max);
+ }
+
+ private static object[] lengthQueryExamples =
+ {
+ new object[] { "6ms", TimeSpan.FromMilliseconds(6), TimeSpan.FromMilliseconds(1) },
+ new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1) },
+ new object[] { "9m", TimeSpan.FromMinutes(9), TimeSpan.FromMinutes(1) },
+ new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1) },
+ new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1) },
+ };
+
+ [Test]
+ [TestCaseSource(nameof(lengthQueryExamples))]
+ public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale)
+ {
+ string query = $"length={lengthQuery} time";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual("time", filterCriteria.SearchText.Trim());
+ Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
+ Assert.AreEqual(expectedLength.TotalMilliseconds - scale.TotalMilliseconds / 2.0, filterCriteria.Length.Min);
+ Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max);
+ }
+
+ [Test]
+ public void TestApplyDivisorQueries()
+ {
+ const string query = "that's a time signature alright! divisor:12";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual("that's a time signature alright!", filterCriteria.SearchText.Trim());
+ Assert.AreEqual(5, filterCriteria.SearchTerms.Length);
+ Assert.AreEqual(12, filterCriteria.BeatDivisor.Min);
+ Assert.IsTrue(filterCriteria.BeatDivisor.IsLowerInclusive);
+ Assert.AreEqual(12, filterCriteria.BeatDivisor.Max);
+ Assert.IsTrue(filterCriteria.BeatDivisor.IsUpperInclusive);
+ }
+
+ [Test]
+ public void TestApplyStatusQueries()
+ {
+ const string query = "I want the pp status=ranked";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual("I want the pp", filterCriteria.SearchText.Trim());
+ Assert.AreEqual(4, filterCriteria.SearchTerms.Length);
+ Assert.AreEqual(BeatmapSetOnlineStatus.Ranked, filterCriteria.OnlineStatus.Min);
+ Assert.IsTrue(filterCriteria.OnlineStatus.IsLowerInclusive);
+ Assert.AreEqual(BeatmapSetOnlineStatus.Ranked, filterCriteria.OnlineStatus.Max);
+ Assert.IsTrue(filterCriteria.OnlineStatus.IsUpperInclusive);
+ }
+
+ [Test]
+ public void TestApplyCreatorQueries()
+ {
+ const string query = "beatmap specifically by creator=my_fav";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual("beatmap specifically by", filterCriteria.SearchText.Trim());
+ Assert.AreEqual(3, filterCriteria.SearchTerms.Length);
+ Assert.AreEqual("my_fav", filterCriteria.Creator.SearchTerm);
+ }
+
+ [Test]
+ public void TestApplyArtistQueries()
+ {
+ const string query = "find me songs by artist=singer please";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual("find me songs by please", filterCriteria.SearchText.Trim());
+ Assert.AreEqual(5, filterCriteria.SearchTerms.Length);
+ Assert.AreEqual("singer", filterCriteria.Artist.SearchTerm);
+ }
+
+ [Test]
+ public void TestApplyArtistQueriesWithSpaces()
+ {
+ const string query = "really like artist=\"name with space\" yes";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual("really like yes", filterCriteria.SearchText.Trim());
+ Assert.AreEqual(3, filterCriteria.SearchTerms.Length);
+ Assert.AreEqual("name with space", filterCriteria.Artist.SearchTerm);
+ }
+
+ [Test]
+ public void TestApplyArtistQueriesOneDoubleQuote()
+ {
+ const string query = "weird artist=double\"quote";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual("weird", filterCriteria.SearchText.Trim());
+ Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
+ Assert.AreEqual("double\"quote", filterCriteria.Artist.SearchTerm);
+ }
+ }
+}
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/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
index 5808a78056..50583e43c4 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
@@ -160,6 +160,15 @@ namespace osu.Game.Tests.Visual.Gameplay
exitAndConfirm();
}
+ [Test]
+ public void TestRestartAfterResume()
+ {
+ pauseAndConfirm();
+ resumeAndConfirm();
+ restart();
+ confirmExited();
+ }
+
private void pauseAndConfirm()
{
pause();
@@ -198,6 +207,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("player exited", () => !Player.IsCurrentScreen());
}
+ private void restart() => AddStep("restart", () => Player.Restart());
private void pause() => AddStep("pause", () => Player.Pause());
private void resume() => AddStep("resume", () => Player.Resume());
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/Menus/TestSceneDisclaimer.cs b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs
index 13116de320..681bf1b40b 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Game.Online.API;
using osu.Game.Screens.Menu;
using osu.Game.Users;
@@ -11,17 +10,17 @@ namespace osu.Game.Tests.Visual.Menus
public class TestSceneDisclaimer : ScreenTestScene
{
[BackgroundDependencyLoader]
- private void load(IAPIProvider api)
+ private void load()
{
AddStep("load disclaimer", () => LoadScreen(new Disclaimer()));
AddStep("toggle support", () =>
{
- api.LocalUser.Value = new User
+ API.LocalUser.Value = new User
{
- Username = api.LocalUser.Value.Username,
- Id = api.LocalUser.Value.Id,
- IsSupporter = !api.LocalUser.Value.IsSupporter,
+ Username = API.LocalUser.Value.Username,
+ Id = API.LocalUser.Value.Id,
+ IsSupporter = !API.LocalUser.Value.IsSupporter,
};
});
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
index 723e5fc03d..7ba1782a28 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchLeaderboard : MultiplayerTestScene
{
- protected override bool RequiresAPIAccess => true;
+ protected override bool UseOnlineAPI => true;
public TestSceneMatchLeaderboard()
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs
index b646433846..dfe61a4dda 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[TestFixture]
public class TestSceneMultiScreen : ScreenTestScene
{
- protected override bool RequiresAPIAccess => true;
+ protected override bool UseOnlineAPI => true;
public override IReadOnlyList RequiredTypes => new[]
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
index 66ab1fe18a..31eab7f74e 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
@@ -4,9 +4,9 @@
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Overlays.AccountCreation;
using osu.Game.Users;
@@ -27,6 +27,8 @@ namespace osu.Game.Tests.Visual.Online
private readonly Container userPanelArea;
+ private Bindable localUser;
+
public TestSceneAccountCreationOverlay()
{
AccountCreationOverlay accountCreation;
@@ -47,12 +49,14 @@ namespace osu.Game.Tests.Visual.Online
}
[BackgroundDependencyLoader]
- private void load(IAPIProvider api)
+ private void load()
{
- api.Logout();
- api.LocalUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true);
+ API.Logout();
- AddStep("logout", api.Logout);
+ localUser = API.LocalUser.GetBoundCopy();
+ localUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true);
+
+ AddStep("logout", API.Logout);
}
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
index 5068064a1f..9f03d947b9 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online
typeof(BeatmapAvailability),
};
- protected override bool RequiresAPIAccess => true;
+ protected override bool UseOnlineAPI => true;
private RulesetInfo taikoRuleset;
private RulesetInfo maniaRuleset;
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
index 324291c9d7..658f678b10 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Online
typeof(Comments),
};
- protected override bool RequiresAPIAccess => true;
+ protected override bool UseOnlineAPI => true;
protected override void LoadComplete()
{
@@ -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/Online/TestSceneChatLineTruncation.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLineTruncation.cs
new file mode 100644
index 0000000000..4773e84a5e
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatLineTruncation.cs
@@ -0,0 +1,108 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics.Containers;
+using osu.Game.Online.Chat;
+using osu.Game.Overlays.Chat;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ [TestFixture]
+ public class TestSceneChatLineTruncation : OsuTestScene
+ {
+ private readonly TestChatLineContainer textContainer;
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(ChatLine),
+ typeof(Message),
+ typeof(LinkFlowContainer),
+ typeof(MessageFormatter)
+ };
+
+ public TestSceneChatLineTruncation()
+ {
+ Add(textContainer = new TestChatLineContainer
+ {
+ Padding = new MarginPadding { Left = 20, Right = 20 },
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ });
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ testFormatting();
+ }
+
+ private void clear() => AddStep("clear messages", textContainer.Clear);
+
+ private void addMessageWithChecks(string text, bool isAction = false, bool isImportant = false, string username = null)
+ {
+ int index = textContainer.Count + 1;
+ var newLine = new ChatLine(new DummyMessage(text, isAction, isImportant, index, username));
+ textContainer.Add(newLine);
+ }
+
+ private void testFormatting()
+ {
+ for (int a = 0; a < 25; a++)
+ addMessageWithChecks($"Wide {a} character username.", username: new string('w', a));
+ addMessageWithChecks("Short name with spaces.", username: "sho rt name");
+ addMessageWithChecks("Long name with spaces.", username: "long name with s p a c e s");
+ }
+
+ private class DummyMessage : Message
+ {
+ private static long messageCounter;
+
+ internal static readonly User TEST_SENDER_BACKGROUND = new User
+ {
+ Username = @"i-am-important",
+ Id = 42,
+ Colour = "#250cc9",
+ };
+
+ internal static readonly User TEST_SENDER = new User
+ {
+ Username = @"Somebody",
+ Id = 1,
+ };
+
+ public new DateTimeOffset Timestamp = DateTimeOffset.Now;
+
+ public DummyMessage(string text, bool isAction = false, bool isImportant = false, int number = 0, string username = null)
+ : base(messageCounter++)
+ {
+ Content = text;
+ IsAction = isAction;
+ Sender = new User
+ {
+ Username = username ?? $"user {number}",
+ Id = number,
+ Colour = isImportant ? "#250cc9" : null,
+ };
+ }
+ }
+
+ private class TestChatLineContainer : FillFlowContainer
+ {
+ protected override int Compare(Drawable x, Drawable y)
+ {
+ var xC = (ChatLine)x;
+ var yC = (ChatLine)y;
+
+ return xC.Message.CompareTo(yC.Message);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs
index 14ae975806..d9873ea243 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual.Online
{
private DirectOverlay direct;
- protected override bool RequiresAPIAccess => true;
+ protected override bool UseOnlineAPI => true;
protected override void LoadComplete()
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs
index c98f98c23d..d3b037f499 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneHistoricalSection : OsuTestScene
{
- protected override bool RequiresAPIAccess => true;
+ protected override bool UseOnlineAPI => true;
public override IReadOnlyList RequiredTypes => new[]
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsDismissableFlag.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsDismissableFlag.cs
new file mode 100644
index 0000000000..db6afa9bf3
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsDismissableFlag.cs
@@ -0,0 +1,65 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+using osu.Game.Overlays.Rankings;
+using osu.Game.Users;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneRankingsDismissableFlag : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DismissableFlag),
+ };
+
+ public TestSceneRankingsDismissableFlag()
+ {
+ DismissableFlag flag;
+ SpriteText text;
+
+ var countryA = new Country
+ {
+ FlagName = "BY",
+ FullName = "Belarus"
+ };
+
+ var countryB = new Country
+ {
+ FlagName = "US",
+ FullName = "United States"
+ };
+
+ AddRange(new Drawable[]
+ {
+ flag = new DismissableFlag
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(30, 20),
+ Country = countryA,
+ },
+ text = new SpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Text = "Invoked",
+ Font = OsuFont.GetFont(size: 30),
+ Alpha = 0,
+ }
+ });
+
+ flag.Action += () => text.FadeIn().Then().FadeOut(1000, Easing.OutQuint);
+
+ AddStep("Trigger click", () => flag.Click());
+ AddStep("Change to country 2", () => flag.Country = countryB);
+ AddStep("Change to country 1", () => flag.Country = countryA);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs
new file mode 100644
index 0000000000..c0da605cdb
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs
@@ -0,0 +1,77 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Overlays.Rankings;
+using osu.Game.Rulesets;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneRankingsHeader : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DismissableFlag),
+ typeof(HeaderTitle),
+ typeof(RankingsRulesetSelector),
+ typeof(RankingsScopeSelector),
+ typeof(RankingsHeader),
+ };
+
+ public TestSceneRankingsHeader()
+ {
+ var countryBindable = new Bindable();
+ var ruleset = new Bindable();
+ var scope = new Bindable();
+
+ Add(new RankingsHeader
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scope = { BindTarget = scope },
+ Country = { BindTarget = countryBindable },
+ Ruleset = { BindTarget = ruleset },
+ Spotlights = new[]
+ {
+ new Spotlight
+ {
+ Id = 1,
+ Text = "Spotlight 1"
+ },
+ new Spotlight
+ {
+ Id = 2,
+ Text = "Spotlight 2"
+ },
+ new Spotlight
+ {
+ Id = 3,
+ Text = "Spotlight 3"
+ }
+ }
+ });
+
+ var country = new Country
+ {
+ FlagName = "BY",
+ FullName = "Belarus"
+ };
+
+ var unknownCountry = new Country
+ {
+ FlagName = "CK",
+ FullName = "Cook Islands"
+ };
+
+ AddStep("Set country", () => countryBindable.Value = country);
+ AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance);
+ AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score);
+ AddAssert("Check country is Null", () => countryBindable.Value == null);
+ AddStep("Set country with no flag", () => countryBindable.Value = unknownCountry);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs
new file mode 100644
index 0000000000..849ca2defc
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs
@@ -0,0 +1,60 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Overlays.Rankings;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneRankingsHeaderTitle : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DismissableFlag),
+ typeof(HeaderTitle),
+ };
+
+ public TestSceneRankingsHeaderTitle()
+ {
+ var countryBindable = new Bindable();
+ var scope = new Bindable();
+
+ Add(new HeaderTitle
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Country = { BindTarget = countryBindable },
+ Scope = { BindTarget = scope },
+ });
+
+ var countryA = new Country
+ {
+ FlagName = "BY",
+ FullName = "Belarus"
+ };
+
+ var countryB = new Country
+ {
+ FlagName = "US",
+ FullName = "United States"
+ };
+
+ AddStep("Set country", () => countryBindable.Value = countryA);
+ AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance);
+ AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score);
+ AddAssert("Check country is Null", () => countryBindable.Value == null);
+
+ AddStep("Set country 1", () => countryBindable.Value = countryA);
+ AddStep("Set country 2", () => countryBindable.Value = countryB);
+ AddStep("Set null country", () => countryBindable.Value = null);
+ AddStep("Set scope to Performance", () => scope.Value = RankingsScope.Performance);
+ AddStep("Set scope to Spotlights", () => scope.Value = RankingsScope.Spotlights);
+ AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score);
+ AddStep("Set scope to Country", () => scope.Value = RankingsScope.Country);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.cs
new file mode 100644
index 0000000000..84515bd3a4
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.cs
@@ -0,0 +1,41 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Game.Overlays.Rankings;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets;
+using osu.Framework.Bindables;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Mania;
+using osu.Game.Rulesets.Taiko;
+using osu.Game.Rulesets.Catch;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneRankingsRulesetSelector : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(RankingsRulesetSelector),
+ };
+
+ public TestSceneRankingsRulesetSelector()
+ {
+ var current = new Bindable();
+
+ Add(new RankingsRulesetSelector
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Current = { BindTarget = current }
+ });
+
+ AddStep("Select osu!", () => current.Value = new OsuRuleset().RulesetInfo);
+ AddStep("Select mania", () => current.Value = new ManiaRuleset().RulesetInfo);
+ AddStep("Select taiko", () => current.Value = new TaikoRuleset().RulesetInfo);
+ AddStep("Select catch", () => current.Value = new CatchRuleset().RulesetInfo);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs
new file mode 100644
index 0000000000..3693d6b5b4
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs
@@ -0,0 +1,54 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Allocation;
+using osu.Game.Graphics;
+using osu.Game.Overlays.Rankings;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneRankingsScopeSelector : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(RankingsScopeSelector),
+ };
+
+ private readonly Box background;
+
+ public TestSceneRankingsScopeSelector()
+ {
+ var scope = new Bindable();
+
+ AddRange(new Drawable[]
+ {
+ background = new Box
+ {
+ RelativeSizeAxes = Axes.Both
+ },
+ new RankingsScopeSelector
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Current = scope,
+ }
+ });
+
+ AddStep(@"Select country", () => scope.Value = RankingsScope.Country);
+ AddStep(@"Select performance", () => scope.Value = RankingsScope.Performance);
+ AddStep(@"Select score", () => scope.Value = RankingsScope.Score);
+ AddStep(@"Select spotlights", () => scope.Value = RankingsScope.Spotlights);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ background.Colour = colours.GreySeafoam;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs
index 806b36e855..dbd7544b38 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneSocialOverlay : OsuTestScene
{
- protected override bool RequiresAPIAccess => true;
+ protected override bool UseOnlineAPI => true;
public override IReadOnlyList RequiredTypes => new[]
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
index 555d5334d8..63b8acb234 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Online
{
public class TestSceneUserProfileHeader : OsuTestScene
{
- protected override bool RequiresAPIAccess => true;
+ protected override bool UseOnlineAPI => true;
public override IReadOnlyList RequiredTypes => new[]
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
index 42c8ffbf0a..93e6607ac5 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneUserProfileOverlay : OsuTestScene
{
- protected override bool RequiresAPIAccess => true;
+ protected override bool UseOnlineAPI => true;
private readonly TestUserProfileOverlay profile;
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs
index d777f9766a..2951f6b63e 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneUserRanks : OsuTestScene
{
- protected override bool RequiresAPIAccess => true;
+ protected override bool UseOnlineAPI => true;
public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableProfileScore), typeof(RanksSection) };
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
index 6669ec7da3..71399106f4 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
@@ -242,6 +242,21 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
AddAssert("Selection is non-null", () => currentSelection != null);
+
+ setSelected(1, 3);
+ AddStep("Apply a range filter", () => carousel.Filter(new FilterCriteria
+ {
+ SearchText = "#3",
+ StarDifficulty = new FilterCriteria.OptionalRange
+ {
+ Min = 2,
+ Max = 5.5,
+ IsLowerInclusive = true
+ }
+ }, false));
+ checkSelected(3, 2);
+
+ AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
}
///
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs
index 7b97a27732..ed9e01a67e 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs
@@ -9,6 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Screens.Select;
+using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual.SongSelect
@@ -30,45 +31,44 @@ namespace osu.Game.Tests.Visual.SongSelect
Size = new Vector2(550f, 450f),
});
- AddStep("all metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
+ AddStep("all metrics", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
+ {
+ BeatmapInfo =
{
- BeatmapSetInfo =
+ BeatmapSet = new BeatmapSetInfo
{
Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
},
- BeatmapInfo =
+ Version = "All Metrics",
+ Metadata = new BeatmapMetadata
{
- Version = "All Metrics",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has all the metrics",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 7,
- DrainRate = 1,
- OverallDifficulty = 5.7f,
- ApproachRate = 3.5f,
- },
- StarDifficulty = 5.3f,
- Metrics = new BeatmapMetrics
- {
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
- },
- }
+ Source = "osu!lazer",
+ Tags = "this beatmap has all the metrics",
+ },
+ BaseDifficulty = new BeatmapDifficulty
+ {
+ CircleSize = 7,
+ DrainRate = 1,
+ OverallDifficulty = 5.7f,
+ ApproachRate = 3.5f,
+ },
+ StarDifficulty = 5.3f,
+ Metrics = new BeatmapMetrics
+ {
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
+ },
}
- );
+ }));
- AddStep("all except source", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
+ AddStep("all except source", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
{
- BeatmapSetInfo =
- {
- Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
- },
BeatmapInfo =
{
+ BeatmapSet = new BeatmapSetInfo
+ {
+ Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
+ },
Version = "All Metrics",
Metadata = new BeatmapMetadata
{
@@ -88,16 +88,16 @@ namespace osu.Game.Tests.Visual.SongSelect
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
}
- });
+ }));
- AddStep("ratings", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
+ AddStep("ratings", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
{
- BeatmapSetInfo =
- {
- Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
- },
BeatmapInfo =
{
+ BeatmapSet = new BeatmapSetInfo
+ {
+ Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
+ },
Version = "Only Ratings",
Metadata = new BeatmapMetadata
{
@@ -113,9 +113,9 @@ namespace osu.Game.Tests.Visual.SongSelect
},
StarDifficulty = 4.8f
}
- });
+ }));
- AddStep("fails+retries", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
+ AddStep("fails+retries", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
{
BeatmapInfo =
{
@@ -139,9 +139,9 @@ namespace osu.Game.Tests.Visual.SongSelect
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
}
- });
+ }));
- AddStep("null metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
+ AddStep("null metrics", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
{
BeatmapInfo =
{
@@ -160,7 +160,7 @@ namespace osu.Game.Tests.Visual.SongSelect
},
StarDifficulty = 1.97f,
}
- });
+ }));
AddStep("null beatmap", () => detailsArea.Beatmap = null);
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
similarity index 86%
rename from osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs
rename to osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
index 8e358a77db..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,15 +40,43 @@ 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));
AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn));
AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable));
+ AddStep(@"None selected", () => leaderboard.SetRetrievalState(PlaceholderState.NoneSelected));
foreach (BeatmapSetOnlineStatus status in Enum.GetValues(typeof(BeatmapSetOnlineStatus)))
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/TestSceneDialogOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs
index a6ff3462d4..cc4a57fb83 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Add(overlay = new DialogOverlay());
- AddStep("dialog #1", () => overlay.Push(new PopupDialog
+ AddStep("dialog #1", () => overlay.Push(new TestPopupDialog
{
Icon = FontAwesome.Regular.TrashAlt,
HeaderText = @"Confirm deletion of",
@@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.UserInterface
},
}));
- AddStep("dialog #2", () => overlay.Push(new PopupDialog
+ AddStep("dialog #2", () => overlay.Push(new TestPopupDialog
{
Icon = FontAwesome.Solid.Cog,
HeaderText = @"What do you want to do with",
@@ -71,5 +71,9 @@ namespace osu.Game.Tests.Visual.UserInterface
},
}));
}
+
+ private class TestPopupDialog : PopupDialog
+ {
+ }
}
}
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/Visual/UserInterface/TestScenePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs
index 9ddd8f4038..3d39bb7003 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs
@@ -13,13 +13,22 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public TestScenePopupDialog()
{
- var popup = new PopupDialog
+ Add(new TestPopupDialog
{
RelativeSizeAxes = Axes.Both,
State = { Value = Framework.Graphics.Containers.Visibility.Visible },
- Icon = FontAwesome.Solid.AssistiveListeningSystems,
- HeaderText = @"This is a test popup",
- BodyText = "I can say lots of stuff and even wrap my words!",
+ });
+ }
+
+ private class TestPopupDialog : PopupDialog
+ {
+ public TestPopupDialog()
+ {
+ Icon = FontAwesome.Solid.AssistiveListeningSystems;
+
+ HeaderText = @"This is a test popup";
+ BodyText = "I can say lots of stuff and even wrap my words!";
+
Buttons = new PopupDialogButton[]
{
new PopupDialogCancelButton
@@ -30,10 +39,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{
Text = @"You're a fake!",
},
- }
- };
-
- Add(popup);
+ };
+ }
}
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
index fdc50be3fa..198cc70e01 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneUpdateableBeatmapBackgroundSprite : OsuTestScene
{
- protected override bool RequiresAPIAccess => true;
+ protected override bool UseOnlineAPI => true;
private BeatmapSetInfo testBeatmap;
private IAPIProvider api;
diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs
index 3e0df8d45e..db9576b5fa 100644
--- a/osu.Game.Tests/WaveformTestBeatmap.cs
+++ b/osu.Game.Tests/WaveformTestBeatmap.cs
@@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
+using osu.Framework.Graphics.Video;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO.Archives;
@@ -42,6 +43,8 @@ namespace osu.Game.Tests
protected override Texture GetBackground() => null;
+ protected override VideoSprite GetVideo() => null;
+
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
protected override Track GetTrack() => trackStore.Get(firstAudioFile);
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.Tournament/osu.Game.Tournament.csproj b/osu.Game.Tournament/osu.Game.Tournament.csproj
index 4790fcbcde..bddaff0a80 100644
--- a/osu.Game.Tournament/osu.Game.Tournament.csproj
+++ b/osu.Game.Tournament/osu.Game.Tournament.csproj
@@ -11,6 +11,6 @@
-
+
\ No newline at end of file
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 166ba5111c..55b8b80e44 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -13,6 +13,7 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Textures;
+using osu.Framework.Graphics.Video;
using osu.Framework.Lists;
using osu.Framework.Logging;
using osu.Framework.Platform;
@@ -88,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)
{
@@ -278,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))
{
@@ -294,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);
@@ -340,6 +342,7 @@ namespace osu.Game.Beatmaps
protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => null;
+ protected override VideoSprite GetVideo() => null;
protected override Track GetTrack() => null;
}
diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
index 5bbffc2f77..1d00c94ef2 100644
--- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
+using osu.Framework.Graphics.Video;
using osu.Framework.IO.Stores;
using osu.Framework.Logging;
using osu.Game.Beatmaps.Formats;
@@ -64,6 +65,21 @@ namespace osu.Game.Beatmaps
}
}
+ protected override VideoSprite GetVideo()
+ {
+ if (Metadata?.VideoFile == null)
+ return null;
+
+ try
+ {
+ return new VideoSprite(textureStore.GetStream(getPathForFile(Metadata.VideoFile)));
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
protected override Track GetTrack()
{
try
diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs
index 001f319307..9267527d79 100644
--- a/osu.Game/Beatmaps/BeatmapMetadata.cs
+++ b/osu.Game/Beatmaps/BeatmapMetadata.cs
@@ -52,6 +52,7 @@ namespace osu.Game.Beatmaps
public int PreviewTime { get; set; }
public string AudioFile { get; set; }
public string BackgroundFile { get; set; }
+ public string VideoFile { get; set; }
public override string ToString() => $"{Artist} - {Title} ({Author})";
@@ -81,7 +82,8 @@ namespace osu.Game.Beatmaps
&& Tags == other.Tags
&& PreviewTime == other.PreviewTime
&& AudioFile == other.AudioFile
- && BackgroundFile == other.BackgroundFile;
+ && BackgroundFile == other.BackgroundFile
+ && VideoFile == other.VideoFile;
}
}
}
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index 29ade24328..a3ab01c886 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -7,6 +7,7 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Textures;
+using osu.Framework.Graphics.Video;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
@@ -44,6 +45,8 @@ namespace osu.Game.Beatmaps
protected override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4");
+ protected override VideoSprite GetVideo() => null;
+
protected override Track GetTrack() => GetVirtualTrack();
private class DummyRulesetInfo : RulesetInfo
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 02d969b571..0532790f0a 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -296,8 +296,13 @@ namespace osu.Game.Beatmaps.Formats
switch (type)
{
case EventType.Background:
- string filename = split[2].Trim('"');
- beatmap.BeatmapInfo.Metadata.BackgroundFile = FileSafety.PathStandardise(filename);
+ string bgFilename = split[2].Trim('"');
+ beatmap.BeatmapInfo.Metadata.BackgroundFile = FileSafety.PathStandardise(bgFilename);
+ break;
+
+ case EventType.Video:
+ string videoFilename = split[2].Trim('"');
+ beatmap.BeatmapInfo.Metadata.VideoFile = FileSafety.PathStandardise(videoFilename);
break;
case EventType.Break:
diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs
index 44071d9cc1..a087a52ada 100644
--- a/osu.Game/Beatmaps/IWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
+using osu.Framework.Graphics.Video;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@@ -25,6 +26,11 @@ namespace osu.Game.Beatmaps
///
Texture Background { get; }
+ ///
+ /// Retrieves the video background file for this .
+ ///
+ VideoSprite Video { get; }
+
///
/// Retrieves the audio track for this .
///
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index d8ab411beb..3fc33e9f52 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -19,6 +19,7 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
+using osu.Framework.Graphics.Video;
namespace osu.Game.Beatmaps
{
@@ -186,6 +187,10 @@ namespace osu.Game.Beatmaps
protected abstract Texture GetBackground();
private readonly RecyclableLazy background;
+ public VideoSprite Video => GetVideo();
+
+ protected abstract VideoSprite GetVideo();
+
public bool TrackLoaded => track.IsResultAvailable;
public Track Track => track.Value;
protected abstract Track GetTrack();
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 185e5d6df8..64b1f2d7bc 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -69,6 +69,7 @@ namespace osu.Game.Configuration
Set(OsuSetting.ShowFpsDisplay, false);
Set(OsuSetting.ShowStoryboard, true);
+ Set(OsuSetting.ShowVideoBackground, true);
Set(OsuSetting.BeatmapSkins, true);
Set(OsuSetting.BeatmapHitsounds, true);
@@ -80,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);
@@ -111,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);
}
@@ -136,6 +141,7 @@ namespace osu.Game.Configuration
DimLevel,
BlurLevel,
ShowStoryboard,
+ ShowVideoBackground,
KeyOverlay,
ScoreMeter,
FloatingComments,
@@ -178,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/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
index a4121acfca..b117d71006 100644
--- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
@@ -21,8 +21,6 @@ namespace osu.Game.Graphics.Containers
private SampleChannel samplePopIn;
private SampleChannel samplePopOut;
- protected virtual bool PlaySamplesOnStateChange => true;
-
protected override bool BlockNonPositionalInput => true;
///
@@ -32,7 +30,7 @@ namespace osu.Game.Graphics.Containers
protected virtual bool DimMainContent => true;
[Resolved(CanBeNull = true)]
- private OsuGame osuGame { get; set; }
+ private OsuGame game { get; set; }
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; }
@@ -42,13 +40,22 @@ namespace osu.Game.Graphics.Containers
[BackgroundDependencyLoader(true)]
private void load(AudioManager audio)
{
- if (osuGame != null)
- OverlayActivationMode.BindTo(osuGame.OverlayActivationMode);
-
samplePopIn = audio.Samples.Get(@"UI/overlay-pop-in");
samplePopOut = audio.Samples.Get(@"UI/overlay-pop-out");
+ }
- State.ValueChanged += onStateChanged;
+ protected override void LoadComplete()
+ {
+ if (game != null)
+ OverlayActivationMode.BindTo(game.OverlayActivationMode);
+
+ OverlayActivationMode.BindValueChanged(mode =>
+ {
+ if (mode.NewValue == OverlayActivation.Disabled)
+ State.Value = Visibility.Hidden;
+ }, true);
+
+ base.LoadComplete();
}
///
@@ -106,26 +113,28 @@ namespace osu.Game.Graphics.Containers
public bool OnReleased(GlobalAction action) => false;
- private void onStateChanged(ValueChangedEvent state)
+ protected override void UpdateState(ValueChangedEvent state)
{
switch (state.NewValue)
{
case Visibility.Visible:
- if (OverlayActivationMode.Value != OverlayActivation.Disabled)
+ if (OverlayActivationMode.Value == OverlayActivation.Disabled)
{
- if (PlaySamplesOnStateChange) samplePopIn?.Play();
- if (BlockScreenWideMouse && DimMainContent) osuGame?.AddBlockingOverlay(this);
+ State.Value = Visibility.Hidden;
+ return;
}
- else
- Hide();
+ samplePopIn?.Play();
+ if (BlockScreenWideMouse && DimMainContent) game?.AddBlockingOverlay(this);
break;
case Visibility.Hidden:
- if (PlaySamplesOnStateChange) samplePopOut?.Play();
- if (BlockScreenWideMouse) osuGame?.RemoveBlockingOverlay(this);
+ samplePopOut?.Play();
+ if (BlockScreenWideMouse) game?.RemoveBlockingOverlay(this);
break;
}
+
+ base.UpdateState(state);
}
protected override void PopOut()
@@ -137,7 +146,7 @@ namespace osu.Game.Graphics.Containers
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
- osuGame?.RemoveBlockingOverlay(this);
+ game?.RemoveBlockingOverlay(this);
}
}
}
diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs
index 2b7635cc88..7683bbcd63 100644
--- a/osu.Game/Graphics/Containers/UserDimContainer.cs
+++ b/osu.Game/Graphics/Containers/UserDimContainer.cs
@@ -35,6 +35,8 @@ namespace osu.Game.Graphics.Containers
protected Bindable ShowStoryboard { get; private set; }
+ protected Bindable ShowVideo { get; private set; }
+
protected double DimLevel => EnableUserDim.Value ? UserDimLevel.Value : 0;
protected override Container Content => dimContent;
@@ -54,10 +56,12 @@ namespace osu.Game.Graphics.Containers
{
UserDimLevel = config.GetBindable(OsuSetting.DimLevel);
ShowStoryboard = config.GetBindable(OsuSetting.ShowStoryboard);
+ ShowVideo = config.GetBindable(OsuSetting.ShowVideoBackground);
EnableUserDim.ValueChanged += _ => UpdateVisuals();
UserDimLevel.ValueChanged += _ => UpdateVisuals();
ShowStoryboard.ValueChanged += _ => UpdateVisuals();
+ ShowVideo.ValueChanged += _ => UpdateVisuals();
StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals();
}
diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs
index f532302de2..02d928ec66 100644
--- a/osu.Game/Graphics/ScreenshotManager.cs
+++ b/osu.Game/Graphics/ScreenshotManager.cs
@@ -83,11 +83,19 @@ namespace osu.Game.Graphics
const int frames_to_wait = 3;
int framesWaited = 0;
- ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() => framesWaited++, 0, true);
- while (framesWaited < frames_to_wait)
- Thread.Sleep(10);
- waitDelegate.Cancel();
+ using (var framesWaitedEvent = new ManualResetEventSlim(false))
+ {
+ ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() =>
+ {
+ if (framesWaited++ < frames_to_wait)
+ // ReSharper disable once AccessToDisposedClosure
+ framesWaitedEvent.Set();
+ }, 10, true);
+
+ framesWaitedEvent.Wait();
+ waitDelegate.Cancel();
+ }
}
using (var image = await host.TakeScreenshotAsync())
diff --git a/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs b/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs
new file mode 100644
index 0000000000..baca57ea89
--- /dev/null
+++ b/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs
@@ -0,0 +1,84 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osuTK;
+using osu.Framework.Graphics.Shapes;
+using osuTK.Graphics;
+using osu.Framework.Graphics.Colour;
+
+namespace osu.Game.Graphics.UserInterface
+{
+ public abstract class GradientLineTabControl : PageTabControl
+ {
+ protected Color4 LineColour
+ {
+ get => line.Colour;
+ set => line.Colour = value;
+ }
+
+ private readonly GradientLine line;
+
+ protected GradientLineTabControl()
+ {
+ RelativeSizeAxes = Axes.X;
+
+ AddInternal(line = new GradientLine
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ });
+ }
+
+ protected override Dropdown CreateDropdown() => null;
+
+ protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ AutoSizeAxes = Axes.X,
+ RelativeSizeAxes = Axes.Y,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(20, 0),
+ };
+
+ private class GradientLine : GridContainer
+ {
+ public GradientLine()
+ {
+ RelativeSizeAxes = Axes.X;
+ Size = new Vector2(0.8f, 1.5f);
+
+ ColumnDimensions = new[]
+ {
+ new Dimension(),
+ new Dimension(mode: GridSizeMode.Relative, size: 0.4f),
+ new Dimension(),
+ };
+
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = ColourInfo.GradientHorizontal(Color4.Transparent, Color4.White)
+ },
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Transparent)
+ },
+ }
+ };
+ }
+ }
+ }
+}
diff --git a/osu.Game/Graphics/UserInterface/PageTabControl.cs b/osu.Game/Graphics/UserInterface/PageTabControl.cs
index a0d3745180..ddcb626701 100644
--- a/osu.Game/Graphics/UserInterface/PageTabControl.cs
+++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Graphics.UserInterface
Margin = new MarginPadding { Top = 8, Bottom = 8 },
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
- Text = (value as Enum)?.GetDescription() ?? value.ToString(),
+ Text = CreateText(),
Font = OsuFont.GetFont(size: 14)
},
box = new Box
@@ -81,6 +81,8 @@ namespace osu.Game.Graphics.UserInterface
Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Exo, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true);
}
+ protected virtual string CreateText() => (Value as Enum)?.GetDescription() ?? Value.ToString();
+
protected override bool OnHover(HoverEvent e)
{
if (!Active.Value)
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/Migrations/20190913104727_AddBeatmapVideo.Designer.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs
new file mode 100644
index 0000000000..826233a2b0
--- /dev/null
+++ b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs
@@ -0,0 +1,506 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using osu.Game.Database;
+
+namespace osu.Game.Migrations
+{
+ [DbContext(typeof(OsuDbContext))]
+ [Migration("20190913104727_AddBeatmapVideo")]
+ partial class AddBeatmapVideo
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "2.2.6-servicing-10079");
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ApproachRate");
+
+ b.Property("CircleSize");
+
+ b.Property("DrainRate");
+
+ b.Property("OverallDifficulty");
+
+ b.Property("SliderMultiplier");
+
+ b.Property("SliderTickRate");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapDifficulty");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("AudioLeadIn");
+
+ b.Property("BPM");
+
+ b.Property("BaseDifficultyID");
+
+ b.Property("BeatDivisor");
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("Countdown");
+
+ b.Property("DistanceSpacing");
+
+ b.Property("GridSize");
+
+ b.Property("Hash");
+
+ b.Property("Hidden");
+
+ b.Property("Length");
+
+ b.Property("LetterboxInBreaks");
+
+ b.Property("MD5Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapID");
+
+ b.Property("Path");
+
+ b.Property("RulesetID");
+
+ b.Property("SpecialStyle");
+
+ b.Property("StackLeniency");
+
+ b.Property("StarDifficulty");
+
+ b.Property("Status");
+
+ b.Property("StoredBookmarks");
+
+ b.Property("TimelineZoom");
+
+ b.Property("Version");
+
+ b.Property("WidescreenStoryboard");
+
+ b.HasKey("ID");
+
+ b.HasIndex("BaseDifficultyID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("Hash");
+
+ b.HasIndex("MD5Hash");
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapID")
+ .IsUnique();
+
+ b.HasIndex("RulesetID");
+
+ b.ToTable("BeatmapInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Artist");
+
+ b.Property("ArtistUnicode");
+
+ b.Property("AudioFile");
+
+ b.Property("AuthorString")
+ .HasColumnName("Author");
+
+ b.Property("BackgroundFile");
+
+ b.Property("PreviewTime");
+
+ b.Property("Source");
+
+ b.Property("Tags");
+
+ b.Property("Title");
+
+ b.Property("TitleUnicode");
+
+ b.Property("VideoFile");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapMetadata");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.HasKey("ID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("FileInfoID");
+
+ b.ToTable("BeatmapSetFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("DateAdded");
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapSetID");
+
+ b.Property("Protected");
+
+ b.Property("Status");
+
+ b.HasKey("ID");
+
+ b.HasIndex("DeletePending");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapSetID")
+ .IsUnique();
+
+ b.ToTable("BeatmapSetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Key")
+ .HasColumnName("Key");
+
+ b.Property("RulesetID");
+
+ b.Property("SkinInfoID");
+
+ b.Property("StringValue")
+ .HasColumnName("Value");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("SkinInfoID");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("Settings");
+ });
+
+ modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Hash");
+
+ b.Property("ReferenceCount");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("ReferenceCount");
+
+ b.ToTable("FileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("IntAction")
+ .HasColumnName("Action");
+
+ b.Property("KeysString")
+ .HasColumnName("Keys");
+
+ b.Property("RulesetID");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("IntAction");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("KeyBinding");
+ });
+
+ modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Available");
+
+ b.Property("InstantiationInfo");
+
+ b.Property("Name");
+
+ b.Property("ShortName");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Available");
+
+ b.HasIndex("ShortName")
+ .IsUnique();
+
+ b.ToTable("RulesetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.Property("ScoreInfoID");
+
+ b.HasKey("ID");
+
+ b.HasIndex("FileInfoID");
+
+ b.HasIndex("ScoreInfoID");
+
+ b.ToTable("ScoreFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Accuracy")
+ .HasColumnType("DECIMAL(1,4)");
+
+ b.Property("BeatmapInfoID");
+
+ b.Property("Combo");
+
+ b.Property("Date");
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("MaxCombo");
+
+ b.Property("ModsJson")
+ .HasColumnName("Mods");
+
+ b.Property("OnlineScoreID");
+
+ b.Property("PP");
+
+ b.Property("Rank");
+
+ b.Property("RulesetID");
+
+ b.Property("StatisticsJson")
+ .HasColumnName("Statistics");
+
+ b.Property("TotalScore");
+
+ b.Property("UserID")
+ .HasColumnName("UserID");
+
+ b.Property("UserString")
+ .HasColumnName("User");
+
+ b.HasKey("ID");
+
+ b.HasIndex("BeatmapInfoID");
+
+ b.HasIndex("OnlineScoreID")
+ .IsUnique();
+
+ b.HasIndex("RulesetID");
+
+ b.ToTable("ScoreInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.Property("SkinInfoID");
+
+ b.HasKey("ID");
+
+ b.HasIndex("FileInfoID");
+
+ b.HasIndex("SkinInfoID");
+
+ b.ToTable("SkinFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Creator");
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("Name");
+
+ b.HasKey("ID");
+
+ b.HasIndex("DeletePending");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.ToTable("SkinInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
+ .WithMany()
+ .HasForeignKey("BaseDifficultyID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
+ .WithMany("Beatmaps")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("Beatmaps")
+ .HasForeignKey("MetadataID");
+
+ b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+ .WithMany()
+ .HasForeignKey("RulesetID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
+ .WithMany("Files")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("BeatmapSets")
+ .HasForeignKey("MetadataID");
+ });
+
+ modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+ {
+ b.HasOne("osu.Game.Skinning.SkinInfo")
+ .WithMany("Settings")
+ .HasForeignKey("SkinInfoID");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
+ {
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Scoring.ScoreInfo")
+ .WithMany("Files")
+ .HasForeignKey("ScoreInfoID");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap")
+ .WithMany("Scores")
+ .HasForeignKey("BeatmapInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+ .WithMany()
+ .HasForeignKey("RulesetID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+ {
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Skinning.SkinInfo")
+ .WithMany("Files")
+ .HasForeignKey("SkinInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs
new file mode 100644
index 0000000000..9ed0943acd
--- /dev/null
+++ b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs
@@ -0,0 +1,22 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace osu.Game.Migrations
+{
+ public partial class AddBeatmapVideo : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "VideoFile",
+ table: "BeatmapMetadata",
+ nullable: true);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "VideoFile",
+ table: "BeatmapMetadata");
+ }
+ }
+}
diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
index 761dca2801..a6d9d1f3cb 100644
--- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
+++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Migrations
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "2.2.4-servicing-10062");
+ .HasAnnotation("ProductVersion", "2.2.6-servicing-10079");
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
{
@@ -139,6 +139,8 @@ namespace osu.Game.Migrations
b.Property("TitleUnicode");
+ b.Property("VideoFile");
+
b.HasKey("ID");
b.ToTable("BeatmapMetadata");
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 98f15599fc..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)
@@ -133,6 +135,10 @@ namespace osu.Game.Online.Leaderboards
});
break;
+ case PlaceholderState.NoneSelected:
+ replacePlaceholder(new MessagePlaceholder(@"Please select a beatmap!"));
+ break;
+
case PlaceholderState.Unavailable:
replacePlaceholder(new MessagePlaceholder(@"Leaderboards are not available for this beatmap!"));
break;
@@ -158,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
@@ -173,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 008f8208eb..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),
},
},
},
@@ -215,6 +215,7 @@ namespace osu.Game.Online.Leaderboards
Origin = Anchor.BottomRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(1),
ChildrenEnumerable = score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) })
},
},
diff --git a/osu.Game/Online/Leaderboards/PlaceholderState.cs b/osu.Game/Online/Leaderboards/PlaceholderState.cs
index 930e1df484..297241fa73 100644
--- a/osu.Game/Online/Leaderboards/PlaceholderState.cs
+++ b/osu.Game/Online/Leaderboards/PlaceholderState.cs
@@ -9,6 +9,7 @@ namespace osu.Game.Online.Leaderboards
Retrieving,
NetworkFailure,
Unavailable,
+ NoneSelected,
NoScores,
NotLoggedIn,
NotSupporter,
diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs
index dcd58db427..e2a725ec46 100644
--- a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs
+++ b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs
@@ -1,60 +1,37 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Graphics.UserInterface;
using osu.Game.Screens.Select.Leaderboards;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osuTK;
using osu.Game.Graphics.UserInterface;
-using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Framework.Allocation;
using osuTK.Graphics;
-using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
+using osu.Framework.Graphics;
namespace osu.Game.Overlays.BeatmapSet
{
- public class LeaderboardScopeSelector : PageTabControl
+ public class LeaderboardScopeSelector : GradientLineTabControl
{
protected override bool AddEnumEntriesAutomatically => false;
- protected override Dropdown CreateDropdown() => null;
-
protected override TabItem CreateTabItem(BeatmapLeaderboardScope value) => new ScopeSelectorTabItem(value);
public LeaderboardScopeSelector()
{
- RelativeSizeAxes = Axes.X;
-
AddItem(BeatmapLeaderboardScope.Global);
AddItem(BeatmapLeaderboardScope.Country);
AddItem(BeatmapLeaderboardScope.Friend);
-
- AddInternal(new GradientLine
- {
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
- });
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AccentColour = colours.Blue;
+ LineColour = Color4.Gray;
}
- protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
- {
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
- AutoSizeAxes = Axes.X,
- RelativeSizeAxes = Axes.Y,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(20, 0),
- };
-
private class ScopeSelectorTabItem : PageTabItem
{
public ScopeSelectorTabItem(BeatmapLeaderboardScope value)
@@ -77,43 +54,5 @@ namespace osu.Game.Overlays.BeatmapSet
Text.FadeColour(Color4.White);
}
}
-
- private class GradientLine : GridContainer
- {
- public GradientLine()
- {
- RelativeSizeAxes = Axes.X;
- Size = new Vector2(0.8f, 1.5f);
-
- ColumnDimensions = new[]
- {
- new Dimension(),
- new Dimension(mode: GridSizeMode.Relative, size: 0.4f),
- new Dimension(),
- };
-
- Content = new[]
- {
- new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = ColourInfo.GradientHorizontal(Color4.Transparent, Color4.Gray),
- },
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Gray,
- },
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = ColourInfo.GradientHorizontal(Color4.Gray, Color4.Transparent),
- },
- }
- };
- }
- }
}
}
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
index 347522fb48..58f5f02956 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
@@ -171,6 +171,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
+ Spacing = new Vector2(1),
ChildrenEnumerable = score.Mods.Select(m => new ModIcon(m)
{
AutoSizeAxes = Axes.Both,
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs
index 6761d0f710..b9664d7c2f 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs
@@ -172,7 +172,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
: this(new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(1),
})
{
}
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/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs
index 7755c8a6a6..dfe3669813 100644
--- a/osu.Game/Overlays/ChangelogOverlay.cs
+++ b/osu.Game/Overlays/ChangelogOverlay.cs
@@ -170,7 +170,7 @@ namespace osu.Game.Overlays
var tcs = new TaskCompletionSource();
var req = new GetChangelogRequest();
- req.Success += res =>
+ req.Success += res => Schedule(() =>
{
// remap streams to builds to ensure model equality
res.Builds.ForEach(b => b.UpdateStream = res.Streams.Find(s => s.Id == b.UpdateStream.Id));
@@ -182,7 +182,7 @@ namespace osu.Game.Overlays
header.Streams.Populate(res.Streams);
tcs.SetResult(true);
- };
+ });
req.Failure += _ => initialFetchTask = null;
req.Perform(API);
diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs
index 2576b38ec8..7596231a3d 100644
--- a/osu.Game/Overlays/Chat/ChatLine.cs
+++ b/osu.Game/Overlays/Chat/ChatLine.cs
@@ -31,6 +31,8 @@ namespace osu.Game.Overlays.Chat
protected virtual float MessagePadding => default_message_padding;
+ private const float timestamp_padding = 65;
+
private const float default_horizontal_padding = 15;
protected virtual float HorizontalPadding => default_horizontal_padding;
@@ -87,7 +89,12 @@ namespace osu.Game.Overlays.Chat
{
Shadow = false,
Colour = hasBackground ? customUsernameColour : username_colours[message.Sender.Id % username_colours.Length],
- Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true)
+ Truncate = true,
+ EllipsisString = "… :",
+ Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true),
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ MaxWidth = default_message_padding - timestamp_padding
};
if (hasBackground)
@@ -142,6 +149,7 @@ namespace osu.Game.Overlays.Chat
new MessageSender(message.Sender)
{
AutoSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Left = timestamp_padding },
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
Child = effectedUsername,
diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs
index 1022edfe81..cff887865a 100644
--- a/osu.Game/Overlays/Dialog/PopupDialog.cs
+++ b/osu.Game/Overlays/Dialog/PopupDialog.cs
@@ -13,20 +13,17 @@ using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers;
-using osu.Game.Input.Bindings;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Overlays.Dialog
{
- public class PopupDialog : OsuFocusedOverlayContainer
+ public abstract class PopupDialog : VisibilityContainer
{
public static readonly float ENTER_DURATION = 500;
public static readonly float EXIT_DURATION = 200;
- protected override bool BlockPositionalInput => false;
-
private readonly Vector2 ringSize = new Vector2(100f);
private readonly Vector2 ringMinifiedSize = new Vector2(20f);
private readonly Vector2 buttonsEnterSpacing = new Vector2(0f, 50f);
@@ -90,7 +87,7 @@ namespace osu.Game.Overlays.Dialog
}
}
- public PopupDialog()
+ protected PopupDialog()
{
RelativeSizeAxes = Axes.Both;
@@ -202,18 +199,6 @@ namespace osu.Game.Overlays.Dialog
};
}
- public override bool OnPressed(GlobalAction action)
- {
- switch (action)
- {
- case GlobalAction.Select:
- Buttons.OfType().FirstOrDefault()?.Click();
- return true;
- }
-
- return base.OnPressed(action);
- }
-
protected override bool OnKeyDown(KeyDownEvent e)
{
if (e.Repeat) return false;
@@ -238,8 +223,6 @@ namespace osu.Game.Overlays.Dialog
protected override void PopIn()
{
- base.PopIn();
-
actionInvoked = false;
// Reset various animations but only if the dialog animation fully completed
@@ -263,7 +246,6 @@ namespace osu.Game.Overlays.Dialog
// This is presumed to always be a sane default "cancel" action.
buttonsContainer.Last().Click();
- base.PopOut();
content.FadeOut(EXIT_DURATION, Easing.InSine);
}
diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs
index aaae7bcf5c..59d748bc5d 100644
--- a/osu.Game/Overlays/DialogOverlay.cs
+++ b/osu.Game/Overlays/DialogOverlay.cs
@@ -5,13 +5,16 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.Dialog;
using osu.Game.Graphics.Containers;
+using osu.Game.Input.Bindings;
+using System.Linq;
namespace osu.Game.Overlays
{
public class DialogOverlay : OsuFocusedOverlayContainer
{
private readonly Container dialogContainer;
- private PopupDialog currentDialog;
+
+ public PopupDialog CurrentDialog { get; private set; }
public DialogOverlay()
{
@@ -29,20 +32,18 @@ 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();
}
- protected override bool PlaySamplesOnStateChange => false;
-
protected override bool BlockNonPositionalInput => true;
private void onDialogOnStateChanged(VisibilityContainer dialog, Visibility v)
@@ -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,13 +70,25 @@ namespace osu.Game.Overlays
{
base.PopOut();
- if (currentDialog?.State.Value == Visibility.Visible)
+ if (CurrentDialog?.State.Value == Visibility.Visible)
{
- currentDialog.Hide();
+ CurrentDialog.Hide();
return;
}
this.FadeOut(PopupDialog.EXIT_DURATION, Easing.InSine);
}
+
+ public override bool OnPressed(GlobalAction action)
+ {
+ switch (action)
+ {
+ case GlobalAction.Select:
+ CurrentDialog?.Buttons.OfType().FirstOrDefault()?.Click();
+ return true;
+ }
+
+ return base.OnPressed(action);
+ }
}
}
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/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs
index e54ce44ca2..6362d3dfb0 100644
--- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs
+++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs
@@ -12,12 +12,13 @@ using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Beatmaps;
using osu.Framework.Localisation;
+using osu.Framework.Graphics.Containers;
namespace osu.Game.Overlays.Profile.Sections.Ranks
{
public abstract class DrawableProfileScore : DrawableProfileRow
{
- private readonly ScoreModsContainer modsContainer;
+ private readonly FillFlowContainer modsContainer;
protected readonly ScoreInfo Score;
protected DrawableProfileScore(ScoreInfo score)
@@ -28,12 +29,12 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
Height = 60;
Children = new Drawable[]
{
- modsContainer = new ScoreModsContainer
+ modsContainer = new FillFlowContainer
{
- AutoSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.Both,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
- Width = 60,
+ Spacing = new Vector2(1),
Margin = new MarginPadding { Right = 160 }
}
};
diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs
deleted file mode 100644
index 1ce04effa8..0000000000
--- a/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs
+++ /dev/null
@@ -1,21 +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 osuTK;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Rulesets.UI;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace osu.Game.Overlays.Profile.Sections.Ranks
-{
- public class ScoreModsContainer : FlowContainer
- {
- protected override IEnumerable ComputeLayoutPositions()
- {
- int count = FlowingChildren.Count();
- for (int i = 0; i < count; i++)
- yield return new Vector2(DrawWidth * i * (count == 1 ? 0 : 1f / (count - 1)), 0);
- }
- }
-}
diff --git a/osu.Game/Overlays/Rankings/DismissableFlag.cs b/osu.Game/Overlays/Rankings/DismissableFlag.cs
new file mode 100644
index 0000000000..7a55b0bba6
--- /dev/null
+++ b/osu.Game/Overlays/Rankings/DismissableFlag.cs
@@ -0,0 +1,55 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Users.Drawables;
+using osuTK.Graphics;
+using osuTK;
+using osu.Framework.Input.Events;
+using System;
+
+namespace osu.Game.Overlays.Rankings
+{
+ public class DismissableFlag : UpdateableFlag
+ {
+ private const int duration = 200;
+
+ public Action Action;
+
+ private readonly SpriteIcon hoverIcon;
+
+ public DismissableFlag()
+ {
+ AddInternal(hoverIcon = new SpriteIcon
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Depth = -1,
+ Alpha = 0,
+ Size = new Vector2(10),
+ Icon = FontAwesome.Solid.Times,
+ });
+ }
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ hoverIcon.FadeIn(duration, Easing.OutQuint);
+ this.FadeColour(Color4.Gray, duration, Easing.OutQuint);
+ return base.OnHover(e);
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ base.OnHoverLost(e);
+ hoverIcon.FadeOut(duration, Easing.OutQuint);
+ this.FadeColour(Color4.White, duration, Easing.OutQuint);
+ }
+
+ protected override bool OnClick(ClickEvent e)
+ {
+ Action?.Invoke();
+ return true;
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Rankings/HeaderTitle.cs b/osu.Game/Overlays/Rankings/HeaderTitle.cs
new file mode 100644
index 0000000000..cba407ecf7
--- /dev/null
+++ b/osu.Game/Overlays/Rankings/HeaderTitle.cs
@@ -0,0 +1,98 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Users;
+using osu.Framework.Graphics;
+using osuTK;
+using osu.Game.Graphics;
+using osu.Framework.Allocation;
+
+namespace osu.Game.Overlays.Rankings
+{
+ public class HeaderTitle : CompositeDrawable
+ {
+ private const int spacing = 10;
+ private const int flag_margin = 5;
+ private const int text_size = 40;
+
+ public readonly Bindable Scope = new Bindable();
+ public readonly Bindable Country = new Bindable();
+
+ private readonly SpriteText scopeText;
+ private readonly DismissableFlag flag;
+
+ public HeaderTitle()
+ {
+ AutoSizeAxes = Axes.Both;
+ InternalChild = new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(spacing, 0),
+ Children = new Drawable[]
+ {
+ flag = new DismissableFlag
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Margin = new MarginPadding { Bottom = flag_margin },
+ Size = new Vector2(30, 20),
+ },
+ scopeText = new SpriteText
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Light)
+ },
+ new SpriteText
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Light),
+ Text = @"Ranking"
+ }
+ }
+ };
+
+ flag.Action += () => Country.Value = null;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ scopeText.Colour = colours.Lime;
+ }
+
+ protected override void LoadComplete()
+ {
+ Scope.BindValueChanged(onScopeChanged, true);
+ Country.BindValueChanged(onCountryChanged, true);
+ base.LoadComplete();
+ }
+
+ private void onScopeChanged(ValueChangedEvent scope)
+ {
+ scopeText.Text = scope.NewValue.ToString();
+
+ if (scope.NewValue != RankingsScope.Performance)
+ Country.Value = null;
+ }
+
+ private void onCountryChanged(ValueChangedEvent country)
+ {
+ if (country.NewValue == null)
+ {
+ flag.Hide();
+ return;
+ }
+
+ Scope.Value = RankingsScope.Performance;
+
+ flag.Country = country.NewValue;
+ flag.Show();
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Rankings/RankingsHeader.cs b/osu.Game/Overlays/Rankings/RankingsHeader.cs
new file mode 100644
index 0000000000..6aa3e75df9
--- /dev/null
+++ b/osu.Game/Overlays/Rankings/RankingsHeader.cs
@@ -0,0 +1,129 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Game.Rulesets;
+using osu.Game.Users;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osuTK;
+using osu.Game.Graphics.UserInterface;
+using System.Collections.Generic;
+
+namespace osu.Game.Overlays.Rankings
+{
+ public class RankingsHeader : CompositeDrawable
+ {
+ private const int content_height = 250;
+
+ public IEnumerable Spotlights
+ {
+ get => dropdown.Items;
+ set => dropdown.Items = value;
+ }
+
+ public readonly Bindable Scope = new Bindable();
+ public readonly Bindable Ruleset = new Bindable