diff --git a/appveyor.yml b/appveyor.yml
index 4dcaa7b45e..be1727e7d7 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -2,7 +2,5 @@ clone_depth: 1
version: '{branch}-{build}'
image: Previous Visual Studio 2017
test: off
-install:
- - cmd: git submodule update --init --recursive --depth=5
build_script:
- cmd: PowerShell -Version 2.0 .\build.ps1
diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml
new file mode 100644
index 0000000000..d36298175b
--- /dev/null
+++ b/appveyor_deploy.yml
@@ -0,0 +1,10 @@
+clone_depth: 1
+version: '{build}'
+image: Previous Visual Studio 2017
+test: off
+skip_non_tags: true
+build_script:
+ - cmd: PowerShell -Version 2.0 .\build.ps1
+deploy:
+ - provider: Environment
+ name: nuget
diff --git a/osu.Android.props b/osu.Android.props
index 96706f2bdc..46fd5424df 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/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
index c89cd95f36..720ef1db42 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osuTK.Graphics;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
@@ -86,7 +87,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
switch (component.LookupName)
{
- case "Gameplay/Catch/fruit-catcher-idle":
+ case "Gameplay/catch/fruit-catcher-idle":
return new CatcherCustomSkin();
}
@@ -99,8 +100,7 @@ namespace osu.Game.Rulesets.Catch.Tests
public Texture GetTexture(string componentName) =>
throw new NotImplementedException();
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration =>
- throw new NotImplementedException();
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
}
}
}
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/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index 19a1b59752..a25d9cb67e 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -6,6 +6,7 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Objects
{
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/Objects/CatchHitWindows.cs b/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs
similarity index 87%
rename from osu.Game.Rulesets.Catch/Objects/CatchHitWindows.cs
rename to osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs
index 837662f5fe..ff793a372e 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitWindows.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs
@@ -1,10 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
-namespace osu.Game.Rulesets.Catch.Objects
+namespace osu.Game.Rulesets.Catch.Scoring
{
public class CatchHitWindows : HitWindows
{
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index 99b22b2d56..18785d65ea 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -4,7 +4,6 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
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..d5fd2808b8 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
@@ -66,6 +66,8 @@ namespace osu.Game.Rulesets.Mania.Tests
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[0], Anchor.TopCentre));
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[1], Anchor.BottomCentre));
+ AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[0], Anchor.TopCentre));
+ AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[1], Anchor.BottomCentre));
AddStep("flip direction", () =>
{
@@ -75,10 +77,14 @@ namespace osu.Game.Rulesets.Mania.Tests
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[0], Anchor.BottomCentre));
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[1], Anchor.TopCentre));
+ AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[0], Anchor.BottomCentre));
+ AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[1], Anchor.TopCentre));
}
private bool notesInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.Columns.SelectMany(c => c.AllHitObjects).All(o => o.Anchor == anchor);
+ private bool barsInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.AllHitObjects.Where(obj => obj is DrawableBarLine).All(o => o.Anchor == anchor);
+
private void createNote()
{
foreach (var stage in stages)
@@ -114,8 +120,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/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index d945abdb04..37cba1fd3c 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -11,9 +11,9 @@ using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mania.Difficulty.Skills;
using osu.Game.Rulesets.Mania.Mods;
-using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Difficulty
{
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
StarRating = difficultyValue(skills) * star_scaling_factor,
Mods = mods,
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
- GreatHitWindow = (int)(hitWindows.Great / 2) / clockRate,
+ GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate,
Skills = skills
};
}
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
index 4c644a8f09..0981b028b2 100644
--- a/osu.Game.Rulesets.Mania/Objects/BarLine.cs
+++ b/osu.Game.Rulesets.Mania/Objects/BarLine.cs
@@ -1,21 +1,12 @@
-// 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 osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Objects
{
- public class BarLine : ManiaHitObject
+ public class BarLine : ManiaHitObject, IBarLine
{
- ///
- /// 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;
+ public bool Major { get; set; }
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
index e9c352c97e..56bc797c7f 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
@@ -40,9 +40,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 +63,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/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index d28d04b3c1..0c82cf7bbc 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -5,8 +5,8 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects
{
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs
index 6bb21633b6..d0125f8793 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs
@@ -3,7 +3,7 @@
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
-using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects
{
diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
index 70720a926b..995e1516cb 100644
--- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
@@ -3,7 +3,9 @@
using osu.Framework.Bindables;
using osu.Game.Rulesets.Mania.Objects.Types;
+using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects
{
diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
deleted file mode 100644
index 5f2ceab48b..0000000000
--- a/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Scoring;
-
-namespace osu.Game.Rulesets.Mania.Objects
-{
- public class ManiaHitWindows : HitWindows
- {
- private static readonly IReadOnlyDictionary base_ranges = new Dictionary
- {
- { HitResult.Perfect, (44.8, 38.8, 27.8) },
- { HitResult.Great, (128, 98, 68) },
- { HitResult.Good, (194, 164, 134) },
- { HitResult.Ok, (254, 224, 194) },
- { HitResult.Meh, (302, 272, 242) },
- { HitResult.Miss, (376, 346, 316) },
- };
-
- public override bool IsHitResultAllowed(HitResult result) => true;
-
- public override void SetDifficulty(double difficulty)
- {
- Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]);
- Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
- Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
- Ok = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Ok]);
- Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
- Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
- }
- }
-}
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/Scoring/ManiaHitWindows.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
new file mode 100644
index 0000000000..549f0f9214
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
@@ -0,0 +1,11 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Scoring
+{
+ public class ManiaHitWindows : HitWindows
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
index 5caf08fb1e..49894a644c 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
@@ -4,7 +4,6 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
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..d371c1f7a8 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.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/TestSceneShaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
index 585fdb9cb4..863d0eda09 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Debug.Assert(drawableHitObject.HitObject.HitWindows != null);
- double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.HalfWindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current;
+ double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current;
Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay);
return drawableHitObject;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
index fe73e7c861..02c65db6ad 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
@@ -7,6 +7,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
@@ -135,6 +136,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public SampleChannel GetSample(ISampleInfo sampleInfo) => null;
public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default;
+ public IBindable GetConfig(TLookup lookup) => null;
public event Action SourceChanged;
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/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 61e9f60cdd..b0d261a1cc 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -9,11 +9,12 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Skills;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Scoring;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Difficulty
{
@@ -39,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
- double hitWindowGreat = (int)(hitWindows.Great / 2) / clockRate;
+ double hitWindowGreat = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate;
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
int maxCombo = beatmap.HitObjects.Count;
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 3decc4e51f..c90f230f93 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -10,15 +10,15 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
-using osuTK;
using osu.Game.Rulesets.Scoring;
+using osuTK;
using osu.Game.Skinning;
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);
@@ -102,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (result == HitResult.None)
{
- Shake(Math.Abs(timeOffset) - HitObject.HitWindows.HalfWindowFor(HitResult.Miss));
+ Shake(Math.Abs(timeOffset) - HitObject.HitWindows.WindowFor(HitResult.Miss));
return;
}
@@ -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.HalfWindowFor(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 50187781f6..84d2a4af9b 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
@@ -9,8 +9,8 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Drawables;
-using osuTK;
using osu.Game.Rulesets.Scoring;
+using osuTK;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
@@ -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 1749ea1f60..9e8ad9851c 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -12,6 +12,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Configuration;
+using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Scoring;
using osuTK.Graphics;
using osu.Game.Skinning;
@@ -162,16 +163,19 @@ 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.GetValue(s => s.SliderBorderSize) ?? SliderBody.DEFAULT_BORDER_SIZE;
- sliderPathRadius = skin.GetValue(s => s.SliderPathRadius) ?? OsuHitObject.OBJECT_RADIUS;
+ Body.BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE;
+ sliderPathRadius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
updatePathRadius();
- Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour.Value;
- Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Color4.White;
+ 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;
@@ -201,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);
@@ -218,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 c5fa5f0af5..9d4d9958a1 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
@@ -8,9 +8,9 @@ using osu.Game.Rulesets.Objects.Drawables;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@@ -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 a0bd301fdb..d1b9ee6cb4 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -13,8 +13,8 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
-using osu.Game.Screens.Ranking;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Screens.Ranking;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@@ -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/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
index 7c871c6ccd..ef7b077480 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
@@ -11,6 +11,7 @@ using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Skinning;
using osuTK.Graphics;
using osu.Game.Skinning;
using osuTK;
@@ -218,7 +219,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
RelativeSizeAxes = Axes.Both;
- float radius = skin.GetValue(s => s.SliderPathRadius) ?? OsuHitObject.OBJECT_RADIUS;
+ float radius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
InternalChild = new CircularContainer
{
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index b52bfcd181..2cf877b000 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -7,6 +7,8 @@ using osu.Game.Rulesets.Objects;
using osuTK;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Osu.Scoring;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs
deleted file mode 100644
index add8fd53c7..0000000000
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Scoring;
-
-namespace osu.Game.Rulesets.Osu.Objects
-{
- public class OsuHitWindows : HitWindows
- {
- private static readonly IReadOnlyDictionary base_ranges = new Dictionary
- {
- { HitResult.Great, (160, 100, 40) },
- { HitResult.Good, (280, 200, 120) },
- { HitResult.Meh, (400, 300, 200) },
- { HitResult.Miss, (400, 400, 400) },
- };
-
- public override void SetDifficulty(double difficulty)
- {
- Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
- Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
- Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
- Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs
index 63713541b4..a794e57c9e 100644
--- a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs
@@ -6,6 +6,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
@@ -28,5 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public override Judgement CreateJudgement() => new OsuJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 93231844bb..2805494021 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -13,6 +13,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
index 4f2af64161..7e540a577b 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
@@ -5,6 +5,7 @@ using osu.Framework.Bindables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
@@ -23,5 +24,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public override Judgement CreateJudgement() => new OsuSliderTailJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
index 60e9084ed3..af7cf5b144 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
@@ -4,8 +4,8 @@
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 69c779a182..2e7b763966 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -6,8 +6,8 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 27899ab56e..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:
@@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu
public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this);
- public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkin(source);
+ public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkinTransformer(source);
public override int? LegacyID => 0;
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/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
index e5fa571d4d..24320b6579 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
@@ -10,9 +10,9 @@ using System.Diagnostics;
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Replays;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Replays
@@ -118,29 +118,29 @@ namespace osu.Game.Rulesets.Osu.Replays
Debug.Assert(hitWindows != null);
// Make the cursor stay at a hitObject as long as possible (mainly for autopilot).
- if (h.StartTime - hitWindows.HalfWindowFor(HitResult.Miss) > endTime + hitWindows.HalfWindowFor(HitResult.Meh) + 50)
+ if (h.StartTime - hitWindows.WindowFor(HitResult.Miss) > endTime + hitWindows.WindowFor(HitResult.Meh) + 50)
{
if (!(prev is Spinner) && h.StartTime - endTime < 1000)
- AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
+ AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
if (!(h is Spinner))
- AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.HalfWindowFor(HitResult.Miss), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
+ AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Miss), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
}
- else if (h.StartTime - hitWindows.HalfWindowFor(HitResult.Meh) > endTime + hitWindows.HalfWindowFor(HitResult.Meh) + 50)
+ else if (h.StartTime - hitWindows.WindowFor(HitResult.Meh) > endTime + hitWindows.WindowFor(HitResult.Meh) + 50)
{
if (!(prev is Spinner) && h.StartTime - endTime < 1000)
- AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
+ AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
if (!(h is Spinner))
- AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
+ AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
}
- else if (h.StartTime - hitWindows.HalfWindowFor(HitResult.Good) > endTime + hitWindows.HalfWindowFor(HitResult.Good) + 50)
+ else if (h.StartTime - hitWindows.WindowFor(HitResult.Good) > endTime + hitWindows.WindowFor(HitResult.Good) + 50)
{
if (!(prev is Spinner) && h.StartTime - endTime < 1000)
- AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.HalfWindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
+ AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
if (!(h is Spinner))
- AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.HalfWindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
+ AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
}
}
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/Scoring/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs
new file mode 100644
index 0000000000..a6491bb3f3
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs
@@ -0,0 +1,34 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Osu.Scoring
+{
+ public class OsuHitWindows : HitWindows
+ {
+ private static readonly DifficultyRange[] osu_ranges =
+ {
+ new DifficultyRange(HitResult.Great, 80, 50, 20),
+ new DifficultyRange(HitResult.Good, 140, 100, 60),
+ new DifficultyRange(HitResult.Meh, 200, 150, 100),
+ new DifficultyRange(HitResult.Miss, 200, 200, 200),
+ };
+
+ public override bool IsHitResultAllowed(HitResult result)
+ {
+ switch (result)
+ {
+ case HitResult.Great:
+ case HitResult.Good:
+ case HitResult.Meh:
+ case HitResult.Miss:
+ return true;
+ }
+
+ return false;
+ }
+
+ protected override DifficultyRange[] GetRanges() => osu_ranges;
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
index 66ef020d09..affe18a30d 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Collections.Generic;
-using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
@@ -22,8 +20,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
private float hpDrainRate;
- private readonly Dictionary comboResultCounts = new Dictionary();
-
protected override void ApplyBeatmap(Beatmap beatmap)
{
base.ApplyBeatmap(beatmap);
@@ -31,22 +27,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
}
- protected override void Reset(bool storeResults)
- {
- base.Reset(storeResults);
- comboResultCounts.Clear();
- }
-
- protected override void ApplyResult(JudgementResult result)
- {
- base.ApplyResult(result);
-
- var osuResult = (OsuJudgementResult)result;
-
- if (result.Type != HitResult.None)
- comboResultCounts[osuResult.ComboType] = comboResultCounts.GetOrDefault(osuResult.ComboType) + 1;
- }
-
protected override double HealthAdjustmentFactorFor(JudgementResult result)
{
switch (result.Type)
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/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
index ec838c596d..81c02199d0 100644
--- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
[BackgroundDependencyLoader]
private void load(ISkinSource skin, DrawableHitObject drawableObject)
{
- animationContent.Colour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White;
+ animationContent.Colour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White;
InternalChildren = new[]
{
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
similarity index 64%
rename from osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs
rename to osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
index e3e302b81c..479c250eab 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
@@ -3,21 +3,19 @@
using System;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
using osu.Game.Skinning;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Skinning
{
- public class OsuLegacySkin : ISkin
+ public class OsuLegacySkinTransformer : ISkin
{
private readonly ISkin source;
- private Lazy configuration;
-
private Lazy hasHitCircle;
///
@@ -27,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
///
private const float legacy_circle_radius = 64 - 5;
- public OsuLegacySkin(ISkinSource source)
+ public OsuLegacySkinTransformer(ISkinSource source)
{
this.source = source;
@@ -37,21 +35,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
private void sourceChanged()
{
- // these need to be lazy in order to ensure they aren't called before the dependencies have been loaded into our source.
- configuration = new Lazy(() =>
- {
- var config = new SkinConfiguration();
- if (hasHitCircle.Value)
- config.SliderPathRadius = legacy_circle_radius;
-
- // defaults should only be applied for non-beatmap skins (which are parsed via this constructor).
- config.CustomColours["SliderBall"] =
- source.GetValue(s => s.CustomColours.TryGetValue("SliderBall", out var val) ? val : (Color4?)null)
- ?? new Color4(2, 170, 255, 255);
-
- return config;
- });
-
hasHitCircle = new Lazy(() => source.GetTexture("hitcircle") != null);
}
@@ -62,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);
@@ -95,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:
- string font = GetValue(config => config.HitCircleFont);
- var overlap = GetValue(config => config.HitCircleOverlap);
+ 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)
};
}
@@ -116,13 +108,27 @@ namespace osu.Game.Rulesets.Osu.Skinning
public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration
+ public IBindable GetConfig(TLookup lookup)
{
- TValue val;
- if (configuration.Value is TConfiguration conf && (val = query.Invoke(conf)) != null)
- return val;
+ switch (lookup)
+ {
+ case OsuSkinColour colour:
+ return source.GetConfig(new SkinCustomColourLookup(colour));
- return source.GetValue(query);
+ case OsuSkinConfiguration osuLookup:
+ switch (osuLookup)
+ {
+ case OsuSkinConfiguration.SliderPathRadius:
+ if (hasHitCircle.Value)
+ return SkinUtils.As(new BindableFloat(legacy_circle_radius));
+
+ break;
+ }
+
+ break;
+ }
+
+ return source.GetConfig(lookup);
}
private bool hasFont(string fontName) => source.GetTexture($"{fontName}-0") != null;
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
new file mode 100644
index 0000000000..4e6d3ef0e4
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
@@ -0,0 +1,12 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Osu.Skinning
+{
+ public enum OsuSkinColour
+ {
+ SliderTrackOverride,
+ SliderBorder,
+ SliderBall
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
new file mode 100644
index 0000000000..98219cafe8
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
@@ -0,0 +1,15 @@
+// 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.Osu.Skinning
+{
+ public enum OsuSkinConfiguration
+ {
+ 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/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
index 869c27dcac..41a02deaca 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
+using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private bool cursorExpand;
- private Bindable cursorScale;
+ private Bindable cursorScale;
private Bindable autoCursorScale;
private readonly IBindable beatmap = new Bindable();
@@ -38,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
- cursorExpand = skin.GetValue(s => s.CursorExpand ?? true);
+ cursorExpand = skin.GetConfig(OsuSkinConfiguration.CursorExpand)?.Value ?? true;
}
[BackgroundDependencyLoader]
@@ -59,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
this.beatmap.BindTo(beatmap);
this.beatmap.ValueChanged += _ => calculateScale();
- cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize);
+ cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize);
cursorScale.ValueChanged += _ => calculateScale();
autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize);
@@ -70,12 +71,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private void calculateScale()
{
- float scale = (float)cursorScale.Value;
+ float scale = cursorScale.Value;
if (autoCursorScale.Value && beatmap.Value != null)
{
// if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier.
- scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY);
+ scale *= 1f - 0.7f * (1f + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY;
}
scaleTarget.Scale = new Vector2(scale);
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/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
index 6fd16c213b..cbbf5b0c09 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
@@ -14,13 +14,13 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Tests.Visual;
using osuTK;
-using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Tests
{
@@ -53,6 +53,11 @@ namespace osu.Game.Rulesets.Taiko.Tests
AddStep("Strong Rim", () => addRimHit(true));
AddStep("Add bar line", () => addBarLine(false));
AddStep("Add major bar line", () => addBarLine(true));
+ AddStep("Add centre w/ bar line", () =>
+ {
+ addCentreHit(false);
+ addBarLine(true);
+ });
AddStep("Height test 1", () => changePlayfieldSize(1));
AddStep("Height test 2", () => changePlayfieldSize(2));
AddStep("Height test 3", () => changePlayfieldSize(3));
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/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index fc93bccb94..32d49ea39c 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -8,11 +8,12 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Difficulty.Skills;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.Scoring;
namespace osu.Game.Rulesets.Taiko.Difficulty
{
@@ -38,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
StarRating = skills.Single().DifficultyValue() * star_scaling_factor,
Mods = mods,
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
- GreatHitWindow = (int)(hitWindows.Great / 2) / clockRate,
+ GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate,
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
Skills = skills
};
diff --git a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs
index a07012fd71..2afbbc737c 100644
--- a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs
@@ -1,9 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Rulesets.Objects;
+
namespace osu.Game.Rulesets.Taiko.Objects
{
- public class BarLine : TaikoHitObject
+ public class BarLine : TaikoHitObject, IBarLine
{
+ public bool Major { get; set; }
}
}
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/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 0942b37f58..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.HalfWindowFor(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/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index 5424ccb4de..423f65b2d3 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
// Normal and clap samples are handled by the drum
protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
- protected override string SampleNamespace => "Taiko";
+ protected override string SampleNamespace => "taiko";
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
index 3ed52f21f0..4e02c76a8b 100644
--- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
@@ -6,7 +6,7 @@ using System;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs
index 39e2b45e24..c466ca7c8a 100644
--- a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
diff --git a/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs
index 830e640242..d660149528 100644
--- a/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs
index e7812841bf..f96c033dce 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs
@@ -4,7 +4,7 @@
using System;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
diff --git a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs
index 049fa7de5f..68212e8f12 100644
--- a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
index 3592d73004..6f4fbd0651 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
@@ -4,7 +4,9 @@
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
+using osu.Game.Rulesets.Taiko.Scoring;
namespace osu.Game.Rulesets.Taiko.Objects
{
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs
deleted file mode 100644
index f232919cbf..0000000000
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Scoring;
-
-namespace osu.Game.Rulesets.Taiko.Objects
-{
- public class TaikoHitWindows : HitWindows
- {
- private static readonly IReadOnlyDictionary base_ranges = new Dictionary
- {
- { HitResult.Great, (100, 70, 40) },
- { HitResult.Good, (240, 160, 100) },
- { HitResult.Miss, (270, 190, 140) },
- };
-
- public override bool IsHitResultAllowed(HitResult result)
- {
- switch (result)
- {
- case HitResult.Great:
- case HitResult.Good:
- case HitResult.Miss:
- return true;
-
- default:
- return false;
- }
- }
-
- public override void SetDifficulty(double difficulty)
- {
- Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
- Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
- Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
- }
- }
-}
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/Scoring/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs
new file mode 100644
index 0000000000..9d273392ff
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs
@@ -0,0 +1,32 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Taiko.Scoring
+{
+ public class TaikoHitWindows : HitWindows
+ {
+ private static readonly DifficultyRange[] taiko_ranges =
+ {
+ new DifficultyRange(HitResult.Great, 50, 35, 20),
+ new DifficultyRange(HitResult.Good, 120, 80, 50),
+ new DifficultyRange(HitResult.Miss, 135, 95, 70),
+ };
+
+ public override bool IsHitResultAllowed(HitResult result)
+ {
+ switch (result)
+ {
+ case HitResult.Great:
+ case HitResult.Good:
+ case HitResult.Miss:
+ return true;
+ }
+
+ return false;
+ }
+
+ protected override DifficultyRange[] GetRanges() => taiko_ranges;
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
index 68ddf2db19..75a27ff639 100644
--- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
@@ -3,7 +3,6 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.UI;
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..fc109bf6a6 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.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
index 9766da9a24..5234ae1f69 100644
--- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
+++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
@@ -132,10 +132,10 @@ namespace osu.Game.Rulesets.Taiko.UI
[BackgroundDependencyLoader]
private void load(TextureStore textures, OsuColour colours)
{
- rim.Texture = textures.Get(@"Gameplay/Taiko/taiko-drum-outer");
- rimHit.Texture = textures.Get(@"Gameplay/Taiko/taiko-drum-outer-hit");
- centre.Texture = textures.Get(@"Gameplay/Taiko/taiko-drum-inner");
- centreHit.Texture = textures.Get(@"Gameplay/Taiko/taiko-drum-inner-hit");
+ rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer");
+ rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit");
+ centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner");
+ centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit");
rimHit.Colour = colours.Blue;
centreHit.Colour = colours.Pink;
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
index a725c58462..4859abbb8e 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
@@ -9,8 +9,8 @@ using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO.Serialization;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Resources;
using osuTK;
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/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs
index 0d6ed67767..9b4a90e9a9 100644
--- a/osu.Game.Tests/Chat/MessageFormatterTests.cs
+++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs
@@ -119,6 +119,53 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(11, result.Links[0].Length);
}
+ [Test]
+ public void TestOldFormatLinkWithBalancedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (tricky (one))[https://osu.ppy.sh]!" });
+
+ Assert.AreEqual("This is a tricky (one)!", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(12, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestOldFormatLinkWithEscapedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is (another loose bracket \\))[https://osu.ppy.sh]." });
+
+ Assert.AreEqual("This is another loose bracket ).", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(8, result.Links[0].Index);
+ Assert.AreEqual(23, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestOldFormatWithBackslashes()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This link (should end with a backslash \\)[https://osu.ppy.sh]." });
+ Assert.AreEqual("This link should end with a backslash \\.", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(29, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestOldFormatLinkWithEscapedAndBalancedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (\\)super\\(\\( tricky (one))[https://osu.ppy.sh]!" });
+
+ Assert.AreEqual("This is a )super(( tricky (one)!", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(21, result.Links[0].Length);
+ }
+
[Test]
public void TestNewFormatLink()
{
@@ -131,6 +178,42 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(11, result.Links[0].Length);
}
+ [Test]
+ public void TestNewFormatLinkWithEscapedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh nasty link with escaped brackets: \\] and \\[]" });
+
+ Assert.AreEqual("This is a nasty link with escaped brackets: ] and [", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(41, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestNewFormatLinkWithBackslashesInside()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh link \\ with \\ backslashes \\]" });
+
+ Assert.AreEqual("This is a link \\ with \\ backslashes \\", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(27, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestNewFormatLinkWithEscapedAndBalancedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh [link [with \\] too many brackets \\[ ]]]" });
+
+ Assert.AreEqual("This is a [link [with ] too many brackets [ ]]", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(36, result.Links[0].Length);
+ }
+
[Test]
public void TestMarkdownFormatLink()
{
@@ -143,6 +226,53 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(11, result.Links[0].Length);
}
+ [Test]
+ public void TestMarkdownFormatLinkWithBalancedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [tricky [one]](https://osu.ppy.sh)!" });
+
+ Assert.AreEqual("This is a tricky [one]!", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(12, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestMarkdownFormatLinkWithEscapedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is [another loose bracket \\]](https://osu.ppy.sh)." });
+
+ Assert.AreEqual("This is another loose bracket ].", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(8, result.Links[0].Index);
+ Assert.AreEqual(23, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestMarkdownFormatWithBackslashes()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This link [should end with a backslash \\](https://osu.ppy.sh)." });
+ Assert.AreEqual("This link should end with a backslash \\.", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(29, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestMarkdownFormatLinkWithEscapedAndBalancedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [\\]super\\[\\[ tricky [one]](https://osu.ppy.sh)!" });
+
+ Assert.AreEqual("This is a ]super[[ tricky [one]!", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(21, result.Links[0].Length);
+ }
+
[Test]
public void TestChannelLink()
{
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/Resources/skin.ini b/osu.Game.Tests/Resources/skin.ini
index 0e5737b4ea..7f7f0b32a6 100644
--- a/osu.Game.Tests/Resources/skin.ini
+++ b/osu.Game.Tests/Resources/skin.ini
@@ -1,5 +1,6 @@
[General]
Name: test skin
+TestLookup: TestValue
[Colours]
Combo1 : 142,199,255
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/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
index 24ef9e4535..8bd846518b 100644
--- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
+++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
@@ -41,5 +41,20 @@ namespace osu.Game.Tests.Skins
Assert.AreEqual(expectedColors[i], comboColors[i]);
}
}
+
+ [Test]
+ public void TestDecodeGeneral()
+ {
+ var decoder = new LegacySkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("skin.ini"))
+ using (var stream = new StreamReader(resStream))
+ {
+ var config = decoder.Decode(stream);
+
+ Assert.AreEqual("test skin", config.SkinInfo.Name);
+ Assert.AreEqual("TestValue", config.ConfigDictionary["TestLookup"]);
+ }
+ }
}
}
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
new file mode 100644
index 0000000000..578030748b
--- /dev/null
+++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
@@ -0,0 +1,158 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.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;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Skins
+{
+ [TestFixture]
+ [HeadlessTest]
+ public class TestSceneSkinConfigurationLookup : OsuTestScene
+ {
+ private LegacySkin source1;
+ private LegacySkin source2;
+ private SkinRequester requester;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ Add(new SkinProvidingContainer(source1 = new SkinSource())
+ .WithChild(new SkinProvidingContainer(source2 = new SkinSource())
+ .WithChild(requester = new SkinRequester())));
+ });
+
+ [Test]
+ public void TestBasicLookup()
+ {
+ AddStep("Add config values", () =>
+ {
+ source1.Configuration.ConfigDictionary["Lookup"] = "source1";
+ source2.Configuration.ConfigDictionary["Lookup"] = "source2";
+ });
+
+ AddAssert("Check lookup finds source2", () => requester.GetConfig("Lookup")?.Value == "source2");
+ }
+
+ [Test]
+ public void TestFloatLookup()
+ {
+ AddStep("Add config values", () => source1.Configuration.ConfigDictionary["FloatTest"] = "1.1");
+ AddAssert("Check float parse lookup", () => requester.GetConfig("FloatTest")?.Value == 1.1f);
+ }
+
+ [Test]
+ public void TestBoolLookup()
+ {
+ AddStep("Add config values", () => source1.Configuration.ConfigDictionary["BoolTest"] = "1");
+ AddAssert("Check bool parse lookup", () => requester.GetConfig("BoolTest")?.Value == true);
+ }
+
+ [Test]
+ public void TestEnumLookup()
+ {
+ AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Test"] = "Test2");
+ AddAssert("Check enum parse lookup", () => requester.GetConfig(LookupType.Test)?.Value == ValueType.Test2);
+ }
+
+ [Test]
+ public void TestLookupFailure()
+ {
+ AddAssert("Check lookup failure", () => requester.GetConfig("Lookup") == null);
+ }
+
+ [Test]
+ public void TestLookupNull()
+ {
+ AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Lookup"] = null);
+
+ AddAssert("Check lookup null", () =>
+ {
+ var bindable = requester.GetConfig("Lookup");
+ return bindable != null && bindable.Value == null;
+ });
+ }
+
+ [Test]
+ public void TestColourLookup()
+ {
+ AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red);
+ AddAssert("Check colour lookup", () => requester.GetConfig(new SkinCustomColourLookup("Lookup"))?.Value == Color4.Red);
+ }
+
+ [Test]
+ public void TestGlobalLookup()
+ {
+ AddAssert("Check combo colours", () => requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0);
+ }
+
+ [Test]
+ public void TestWrongColourType()
+ {
+ AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red);
+
+ AddAssert("perform incorrect lookup", () =>
+ {
+ try
+ {
+ requester.GetConfig(new SkinCustomColourLookup("Lookup"));
+ return false;
+ }
+ catch
+ {
+ return true;
+ }
+ });
+ }
+
+ public enum LookupType
+ {
+ Test
+ }
+
+ public enum ValueType
+ {
+ Test1,
+ Test2,
+ Test3
+ }
+
+ public class SkinSource : LegacySkin
+ {
+ public SkinSource()
+ : base(new SkinInfo(), null, null, string.Empty)
+ {
+ }
+ }
+
+ public class SkinRequester : Drawable, ISkin
+ {
+ private ISkinSource skin;
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ this.skin = skin;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component);
+
+ public Texture GetTexture(string componentName) => skin.GetTexture(componentName);
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
+
+ public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup);
+ }
+ }
+}
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/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs
index e9c15dab9b..a934d22b5d 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs
@@ -3,18 +3,18 @@
using NUnit.Framework;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Taiko.Objects;
-using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Rulesets.Catch.Objects;
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Scoring;
using osu.Framework.MathUtils;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Rulesets.Catch.Scoring;
+using osu.Game.Rulesets.Mania.Scoring;
+using osu.Game.Rulesets.Osu.Scoring;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
namespace osu.Game.Tests.Visual.Gameplay
@@ -36,8 +36,8 @@ namespace osu.Game.Tests.Visual.Gameplay
AddRepeatStep("New random judgement", () => newJudgement(), 40);
- AddRepeatStep("New max negative", () => newJudgement(-hitWindows.HalfWindowFor(HitResult.Meh)), 20);
- AddRepeatStep("New max positive", () => newJudgement(hitWindows.HalfWindowFor(HitResult.Meh)), 20);
+ AddRepeatStep("New max negative", () => newJudgement(-hitWindows.WindowFor(HitResult.Meh)), 20);
+ AddRepeatStep("New max positive", () => newJudgement(hitWindows.WindowFor(HitResult.Meh)), 20);
AddStep("New fixed judgement (50ms)", () => newJudgement(50));
}
@@ -85,9 +85,9 @@ namespace osu.Game.Tests.Visual.Gameplay
AutoSizeAxes = Axes.Both,
Children = new[]
{
- new SpriteText { Text = $@"Great: {hitWindows?.Great}" },
- new SpriteText { Text = $@"Good: {hitWindows?.Good}" },
- new SpriteText { Text = $@"Meh: {hitWindows?.Meh}" },
+ new SpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" },
+ new SpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Good)}" },
+ new SpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" },
}
});
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
new file mode 100644
index 0000000000..cca6301b02
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
@@ -0,0 +1,44 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Screens.Play;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneFailJudgement : AllPlayersTestScene
+ {
+ protected override Player CreatePlayer(Ruleset ruleset)
+ {
+ Mods.Value = Array.Empty();
+ return new FailPlayer();
+ }
+
+ protected override void AddCheckSteps()
+ {
+ AddUntilStep("wait for fail", () => Player.HasFailed);
+ AddUntilStep("wait for multiple judged objects", () => ((FailPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.Count(h => h.AllJudged) > 1);
+ AddAssert("total judgements == 1", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits == 1);
+ }
+
+ private class FailPlayer : TestPlayer
+ {
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+
+ public FailPlayer()
+ : base(false, false)
+ {
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ ScoreProcessor.FailConditions += (_, __) => true;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
index 4727140d99..c1635ffc83 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
@@ -3,12 +3,13 @@
using System;
using System.Collections.Generic;
-using System.ComponentModel;
using System.Linq;
+using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Screens.Play;
using osuTK;
@@ -29,57 +30,118 @@ namespace osu.Game.Tests.Visual.Gameplay
[BackgroundDependencyLoader]
private void load(OsuGameBase game)
{
- Child = globalActionContainer = new GlobalActionContainer(game)
- {
- Children = new Drawable[]
- {
- pauseOverlay = new PauseOverlay
- {
- OnResume = () => Logger.Log(@"Resume"),
- OnRetry = () => Logger.Log(@"Retry"),
- OnQuit = () => Logger.Log(@"Quit"),
- },
- failOverlay = new FailOverlay
+ Child = globalActionContainer = new GlobalActionContainer(game);
+ }
- {
- OnRetry = () => Logger.Log(@"Retry"),
- OnQuit = () => Logger.Log(@"Quit"),
- }
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ globalActionContainer.Children = new Drawable[]
+ {
+ pauseOverlay = new PauseOverlay
+ {
+ OnResume = () => Logger.Log(@"Resume"),
+ OnRetry = () => Logger.Log(@"Retry"),
+ OnQuit = () => Logger.Log(@"Quit"),
+ },
+ failOverlay = new FailOverlay
+
+ {
+ OnRetry = () => Logger.Log(@"Retry"),
+ OnQuit = () => Logger.Log(@"Quit"),
}
};
+ InputManager.MoveMouseTo(Vector2.Zero);
+ });
+
+ [Test]
+ public void TestAdjustRetryCount()
+ {
+ showOverlay();
+
var retryCount = 0;
- AddStep("Add retry", () =>
+ AddRepeatStep("Add retry", () =>
{
retryCount++;
pauseOverlay.Retries = failOverlay.Retries = retryCount;
- });
+ }, 10);
+ }
- AddToggleStep("Toggle pause overlay", t => pauseOverlay.ToggleVisibility());
- AddToggleStep("Toggle fail overlay", t => failOverlay.ToggleVisibility());
+ ///
+ /// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred.
+ ///
+ [Test]
+ public void TestEnterWithoutSelection()
+ {
+ showOverlay();
- testHideResets();
+ AddStep("Press select", () => press(GlobalAction.Select));
+ AddAssert("Overlay still open", () => pauseOverlay.State.Value == Visibility.Visible);
+ }
- testEnterWithoutSelection();
- testKeyUpFromInitial();
- testKeyDownFromInitial();
- testKeyUpWrapping();
- testKeyDownWrapping();
+ ///
+ /// Tests that pressing the up arrow from the initial state selects the last button.
+ ///
+ [Test]
+ public void TestKeyUpFromInitial()
+ {
+ showOverlay();
- testMouseSelectionAfterKeySelection();
- testKeySelectionAfterMouseSelection();
+ AddStep("Up arrow", () => press(Key.Up));
+ AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value);
+ }
- testMouseDeselectionResets();
+ ///
+ /// Tests that pressing the down arrow from the initial state selects the first button.
+ ///
+ [Test]
+ public void TestKeyDownFromInitial()
+ {
+ showOverlay();
- testClickSelection();
- testEnterKeySelection();
+ AddStep("Down arrow", () => press(Key.Down));
+ AddAssert("First button selected", () => getButton(0).Selected.Value);
+ }
+
+ ///
+ /// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly.
+ ///
+ [Test]
+ public void TestKeyUpWrapping()
+ {
+ AddStep("Show overlay", () => failOverlay.Show());
+
+ AddStep("Up arrow", () => press(Key.Up));
+ AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
+ AddStep("Up arrow", () => press(Key.Up));
+ AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
+ AddStep("Up arrow", () => press(Key.Up));
+ AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
+ }
+
+ ///
+ /// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly.
+ ///
+ [Test]
+ public void TestKeyDownWrapping()
+ {
+ AddStep("Show overlay", () => failOverlay.Show());
+
+ AddStep("Down arrow", () => press(Key.Down));
+ AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
+ AddStep("Down arrow", () => press(Key.Down));
+ AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
+ AddStep("Down arrow", () => press(Key.Down));
+ AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
}
///
/// Test that hiding the overlay after hovering a button will reset the overlay to the initial state with no buttons selected.
///
- private void testHideResets()
+ [Test]
+ public void TestHideResets()
{
AddStep("Show overlay", () => failOverlay.Show());
@@ -90,141 +152,78 @@ namespace osu.Game.Tests.Visual.Gameplay
}
///
- /// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred.
+ /// Tests that entering menu with cursor initially on button doesn't selects it immediately.
+ /// This is to allow for stable keyboard navigation.
///
- private void testEnterWithoutSelection()
+ [Test]
+ public void TestInitialButtonHover()
{
- AddStep("Show overlay", () => pauseOverlay.Show());
+ showOverlay();
- AddStep("Press select", () => press(GlobalAction.Select));
- AddAssert("Overlay still open", () => pauseOverlay.State.Value == Visibility.Visible);
+ AddStep("Hover first button", () => InputManager.MoveMouseTo(getButton(0)));
AddStep("Hide overlay", () => pauseOverlay.Hide());
- }
+ showOverlay();
- ///
- /// Tests that pressing the up arrow from the initial state selects the last button.
- ///
- private void testKeyUpFromInitial()
- {
- AddStep("Show overlay", () => pauseOverlay.Show());
+ AddAssert("First button not selected", () => !getButton(0).Selected.Value);
- AddStep("Up arrow", () => press(Key.Up));
- AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value);
+ AddStep("Move slightly", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(1)));
- AddStep("Hide overlay", () => pauseOverlay.Hide());
- }
-
- ///
- /// Tests that pressing the down arrow from the initial state selects the first button.
- ///
- private void testKeyDownFromInitial()
- {
- AddStep("Show overlay", () => pauseOverlay.Show());
-
- AddStep("Down arrow", () => press(Key.Down));
- AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value);
-
- AddStep("Hide overlay", () => pauseOverlay.Hide());
- }
-
- ///
- /// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly.
- ///
- private void testKeyUpWrapping()
- {
- AddStep("Show overlay", () => failOverlay.Show());
-
- AddStep("Up arrow", () => press(Key.Up));
- AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
- AddStep("Up arrow", () => press(Key.Up));
- AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
- AddStep("Up arrow", () => press(Key.Up));
- AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
-
- AddStep("Hide overlay", () => failOverlay.Hide());
- }
-
- ///
- /// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly.
- ///
- private void testKeyDownWrapping()
- {
- AddStep("Show overlay", () => failOverlay.Show());
-
- AddStep("Down arrow", () => press(Key.Down));
- AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
- AddStep("Down arrow", () => press(Key.Down));
- AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
- AddStep("Down arrow", () => press(Key.Down));
- AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
-
- AddStep("Hide overlay", () => failOverlay.Hide());
+ AddAssert("First button selected", () => getButton(0).Selected.Value);
}
///
/// Tests that hovering a button that was previously selected with the keyboard correctly selects the new button and deselects the previous button.
///
- private void testMouseSelectionAfterKeySelection()
+ [Test]
+ public void TestMouseSelectionAfterKeySelection()
{
- AddStep("Show overlay", () => pauseOverlay.Show());
-
- var secondButton = pauseOverlay.Buttons.Skip(1).First();
+ showOverlay();
AddStep("Down arrow", () => press(Key.Down));
- AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
- AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected.Value);
- AddAssert("Second button selected", () => secondButton.Selected.Value);
-
- AddStep("Hide overlay", () => pauseOverlay.Hide());
+ AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
+ AddAssert("First button not selected", () => !getButton(0).Selected.Value);
+ AddAssert("Second button selected", () => getButton(1).Selected.Value);
}
///
/// Tests that pressing a key after selecting a button with a hover event correctly selects a new button and deselects the previous button.
///
- private void testKeySelectionAfterMouseSelection()
+ [Test]
+ public void TestKeySelectionAfterMouseSelection()
{
AddStep("Show overlay", () =>
{
pauseOverlay.Show();
- InputManager.MoveMouseTo(Vector2.Zero);
});
- var secondButton = pauseOverlay.Buttons.Skip(1).First();
-
- AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
+ AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddStep("Up arrow", () => press(Key.Up));
- AddAssert("Second button not selected", () => !secondButton.Selected.Value);
- AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value);
-
- AddStep("Hide overlay", () => pauseOverlay.Hide());
+ AddAssert("Second button not selected", () => !getButton(1).Selected.Value);
+ AddAssert("First button selected", () => getButton(0).Selected.Value);
}
///
/// Tests that deselecting with the mouse by losing hover will reset the overlay to the initial state.
///
- private void testMouseDeselectionResets()
+ [Test]
+ public void TestMouseDeselectionResets()
{
- AddStep("Show overlay", () => pauseOverlay.Show());
+ showOverlay();
- var secondButton = pauseOverlay.Buttons.Skip(1).First();
-
- AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
+ AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero));
AddStep("Down arrow", () => press(Key.Down));
- AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value); // Initial state condition
-
- AddStep("Hide overlay", () => pauseOverlay.Hide());
+ AddAssert("First button selected", () => getButton(0).Selected.Value); // Initial state condition
}
///
/// Tests that clicking on a button correctly causes a click event for that button.
///
- private void testClickSelection()
+ [Test]
+ public void TestClickSelection()
{
- AddStep("Show overlay", () => pauseOverlay.Show());
-
- var retryButton = pauseOverlay.Buttons.Skip(1).First();
+ showOverlay();
bool triggered = false;
AddStep("Click retry button", () =>
@@ -232,7 +231,7 @@ namespace osu.Game.Tests.Visual.Gameplay
var lastAction = pauseOverlay.OnRetry;
pauseOverlay.OnRetry = () => triggered = true;
- retryButton.Click();
+ getButton(1).Click();
pauseOverlay.OnRetry = lastAction;
});
@@ -243,9 +242,10 @@ namespace osu.Game.Tests.Visual.Gameplay
///
/// Tests that pressing the enter key with a button selected correctly causes a click event for that button.
///
- private void testEnterKeySelection()
+ [Test]
+ public void TestEnterKeySelection()
{
- AddStep("Show overlay", () => pauseOverlay.Show());
+ showOverlay();
AddStep("Select second button", () =>
{
@@ -275,6 +275,10 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden);
}
+ private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show());
+
+ private DialogButton getButton(int index) => pauseOverlay.Buttons.Skip(index).First();
+
private void press(Key key)
{
InputManager.PressKey(key);
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/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
index ee5552c6e0..b3d4820737 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
@@ -5,7 +5,9 @@ using System;
using System.Globalization;
using System.Linq;
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.Shapes;
@@ -133,11 +135,54 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
}
+ [Test]
+ public void TestSwitchOff()
+ {
+ SkinConsumer consumer = null;
+ SwitchableSkinProvidingContainer target = null;
+
+ AddStep("setup layout", () =>
+ {
+ Child = new SkinSourceContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = target = new SwitchableSkinProvidingContainer(new SecondarySource())
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ };
+ });
+
+ AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)));
+ AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
+ AddStep("disable", () => target.Disable());
+ AddAssert("consumer using base source", () => consumer.Drawable is BaseSourceBox);
+ }
+
+ private class SwitchableSkinProvidingContainer : SkinProvidingContainer
+ {
+ private bool allow = true;
+
+ protected override bool AllowDrawableLookup(ISkinComponent component) => allow;
+
+ public void Disable()
+ {
+ allow = false;
+ TriggerSourceChanged();
+ }
+
+ public SwitchableSkinProvidingContainer(ISkin skin)
+ : base(skin)
+ {
+ }
+ }
+
private class ExposedSkinnableDrawable : SkinnableDrawable
{
public new Drawable Drawable => base.Drawable;
- public ExposedSkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ public ExposedSkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null,
+ ConfineMode confineMode = ConfineMode.ScaleDownToFit)
: base(new TestSkinComponent(name), defaultImplementation, allowFallback, confineMode)
{
}
@@ -256,7 +301,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
}
private class SecondarySource : ISkin
@@ -267,10 +312,11 @@ namespace osu.Game.Tests.Visual.Gameplay
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
}
- private class SkinSourceContainer : Container, ISkin
+ [Cached(typeof(ISkinSource))]
+ private class SkinSourceContainer : Container, ISkinSource
{
public Drawable GetDrawableComponent(ISkinComponent componentName) => new BaseSourceBox();
@@ -278,7 +324,9 @@ namespace osu.Game.Tests.Visual.Gameplay
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
+
+ public event Action SourceChanged;
}
private class TestSkinComponent : ISkinComponent
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs
index f2718b8e80..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;
@@ -10,23 +9,18 @@ namespace osu.Game.Tests.Visual.Menus
{
public class TestSceneDisclaimer : ScreenTestScene
{
- [Cached(typeof(IAPIProvider))]
- private readonly DummyAPIAccess api = new DummyAPIAccess();
-
[BackgroundDependencyLoader]
private void load()
{
- Add(api);
-
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 fa3c392b2e..7ba1782a28 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
@@ -14,6 +14,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchLeaderboard : MultiplayerTestScene
{
+ protected override bool UseOnlineAPI => true;
+
public TestSceneMatchLeaderboard()
{
Room.RoomID.Value = 3;
@@ -27,11 +29,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
}
- [Resolved]
- private IAPIProvider api { get; set; }
-
[BackgroundDependencyLoader]
- private void load()
+ private void load(IAPIProvider api)
{
var req = new GetRoomScoresRequest();
req.Success += v => { };
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs
index 069e133c2b..dfe61a4dda 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs
@@ -12,6 +12,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[TestFixture]
public class TestSceneMultiScreen : ScreenTestScene
{
+ protected override bool UseOnlineAPI => true;
+
public override IReadOnlyList RequiredTypes => new[]
{
typeof(Screens.Multi.Multiplayer),
diff --git a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
index 35449f5687..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;
@@ -25,17 +25,16 @@ namespace osu.Game.Tests.Visual.Online
typeof(AccountCreationScreen),
};
- [Cached(typeof(IAPIProvider))]
- private DummyAPIAccess api = new DummyAPIAccess();
+ private readonly Container userPanelArea;
+
+ private Bindable localUser;
public TestSceneAccountCreationOverlay()
{
- Container userPanelArea;
AccountCreationOverlay accountCreation;
Children = new Drawable[]
{
- api,
accountCreation = new AccountCreationOverlay(),
userPanelArea = new Container
{
@@ -46,11 +45,18 @@ namespace osu.Game.Tests.Visual.Online
},
};
- api.Logout();
- api.LocalUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true);
-
AddStep("show", () => accountCreation.Show());
- AddStep("logout", () => api.Logout());
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ 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 ee9e088dcc..9f03d947b9 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
@@ -42,6 +42,8 @@ namespace osu.Game.Tests.Visual.Online
typeof(BeatmapAvailability),
};
+ 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 cf8bac7642..658f678b10 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
@@ -27,6 +27,8 @@ namespace osu.Game.Tests.Visual.Online
typeof(Comments),
};
+ protected override bool UseOnlineAPI => true;
+
protected override void LoadComplete()
{
base.LoadComplete();
@@ -66,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/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs
index c18e0e3064..a1c77e2db0 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs
@@ -127,6 +127,9 @@ namespace osu.Game.Tests.Visual.Online
addMessageWithChecks("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmap);
addMessageWithChecks("Let's (try)[https://osu.ppy.sh/home] [https://osu.ppy.sh/b/252238 multiple links] https://osu.ppy.sh/home", 3,
expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External });
+ addMessageWithChecks("[https://osu.ppy.sh/home New link format with escaped [and \\[ paired] braces]", 1, expectedActions: LinkAction.External);
+ addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://osu.ppy.sh/home)", 1, expectedActions: LinkAction.External);
+ addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://osu.ppy.sh/home] and [[also a rogue wiki link]]", 2, expectedActions: new[] { LinkAction.External, LinkAction.External });
// note that there's 0 links here (they get removed if a channel is not found)
addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present).");
addMessageWithChecks("I am important!", 0, false, true);
diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs
index 75c2a2a6a1..d9873ea243 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs
@@ -13,6 +13,8 @@ namespace osu.Game.Tests.Visual.Online
{
private DirectOverlay direct;
+ protected override bool UseOnlineAPI => true;
+
protected override void LoadComplete()
{
base.LoadComplete();
diff --git a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs
index 838347800f..d3b037f499 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs
@@ -17,14 +17,15 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneHistoricalSection : OsuTestScene
{
- public override IReadOnlyList RequiredTypes =>
- new[]
- {
- typeof(HistoricalSection),
- typeof(PaginatedMostPlayedBeatmapContainer),
- typeof(DrawableMostPlayedBeatmap),
- typeof(DrawableProfileRow)
- };
+ protected override bool UseOnlineAPI => true;
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(HistoricalSection),
+ typeof(PaginatedMostPlayedBeatmapContainer),
+ typeof(DrawableMostPlayedBeatmap),
+ typeof(DrawableProfileRow)
+ };
public TestSceneHistoricalSection()
{
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 5cb96c7ed2..dbd7544b38 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs
@@ -13,6 +13,8 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneSocialOverlay : OsuTestScene
{
+ protected override bool UseOnlineAPI => true;
+
public override IReadOnlyList RequiredTypes => new[]
{
typeof(UserPanel),
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
index 2285c9b799..63b8acb234 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
@@ -17,6 +17,8 @@ namespace osu.Game.Tests.Visual.Online
{
public class TestSceneUserProfileHeader : OsuTestScene
{
+ protected override bool UseOnlineAPI => true;
+
public override IReadOnlyList RequiredTypes => new[]
{
typeof(ProfileHeader),
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
index 84c99d8c3a..93e6607ac5 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
@@ -19,6 +19,8 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneUserProfileOverlay : OsuTestScene
{
+ protected override bool UseOnlineAPI => true;
+
private readonly TestUserProfileOverlay profile;
[Resolved]
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs
index 9f0a8c769a..2951f6b63e 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs
@@ -18,6 +18,8 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneUserRanks : OsuTestScene
{
+ protected override bool UseOnlineAPI => true;
+
public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableProfileScore), typeof(RanksSection) };
public TestSceneUserRanks()
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..700adad9cb
--- /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.Graphics.UserInterfaceV2;
+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/TestSceneLabelledTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs
index 395905a30d..53a2bfabbc 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs
@@ -7,7 +7,8 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Screens.Edit.Setup.Components.LabelledComponents;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Graphics.UserInterfaceV2;
namespace osu.Game.Tests.Visual.UserInterface
{
@@ -19,6 +20,36 @@ namespace osu.Game.Tests.Visual.UserInterface
typeof(LabelledTextBox),
};
+ [TestCase(false)]
+ [TestCase(true)]
+ public void TestTextBox(bool hasDescription) => createTextBox(hasDescription);
+
+ private void createTextBox(bool hasDescription = false)
+ {
+ AddStep("create component", () =>
+ {
+ LabelledComponent component;
+
+ Child = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Width = 500,
+ AutoSizeAxes = Axes.Y,
+ Child = component = new LabelledTextBox
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Label = "Testing text",
+ PlaceholderText = "This is definitely working as intended",
+ }
+ };
+
+ component.Label = "a sample component";
+ component.Description = hasDescription ? "this text describes the component" : string.Empty;
+ });
+ }
+
[BackgroundDependencyLoader]
private void load()
{
@@ -32,7 +63,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- LabelText = "Testing text",
+ Label = "Testing text",
PlaceholderText = "This is definitely working as intended",
}
};
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 9cdfcb6cc4..198cc70e01 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
@@ -20,6 +20,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneUpdateableBeatmapBackgroundSprite : OsuTestScene
{
+ protected override bool UseOnlineAPI => true;
+
private BeatmapSetInfo testBeatmap;
private IAPIProvider api;
private RulesetStore rulesets;
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/Screens/TestSceneSetupScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSetupScreen.cs
new file mode 100644
index 0000000000..650b4c5412
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneSetupScreen.cs
@@ -0,0 +1,17 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Game.Tournament.Screens;
+
+namespace osu.Game.Tournament.Tests.Screens
+{
+ public class TestSceneSetupScreen : TournamentTestScene
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Add(new SetupScreen());
+ }
+ }
+}
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/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs
index 4fd858bd12..e05d96e098 100644
--- a/osu.Game.Tournament/IPC/FileBasedIPC.cs
+++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs
@@ -9,6 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Platform.Windows;
+using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Online.API;
@@ -26,103 +27,120 @@ namespace osu.Game.Tournament.IPC
[Resolved]
protected RulesetStore Rulesets { get; private set; }
+ [Resolved]
+ private GameHost host { get; set; }
+
+ [Resolved]
+ private LadderInfo ladder { get; set; }
+
private int lastBeatmapId;
+ private ScheduledDelegate scheduled;
+
+ public Storage Storage { get; private set; }
[BackgroundDependencyLoader]
- private void load(LadderInfo ladder, GameHost host)
+ private void load()
{
- StableStorage stable;
+ LocateStableStorage();
+ }
+
+ public Storage LocateStableStorage()
+ {
+ scheduled?.Cancel();
+
+ Storage = null;
try
{
- stable = new StableStorage(host as DesktopGameHost);
+ Storage = new StableStorage(host as DesktopGameHost);
+
+ const string file_ipc_filename = "ipc.txt";
+ const string file_ipc_state_filename = "ipc-state.txt";
+ const string file_ipc_scores_filename = "ipc-scores.txt";
+ const string file_ipc_channel_filename = "ipc-channel.txt";
+
+ if (Storage.Exists(file_ipc_filename))
+ scheduled = Scheduler.AddDelayed(delegate
+ {
+ try
+ {
+ using (var stream = Storage.GetStream(file_ipc_filename))
+ using (var sr = new StreamReader(stream))
+ {
+ var beatmapId = int.Parse(sr.ReadLine());
+ var mods = int.Parse(sr.ReadLine());
+
+ if (lastBeatmapId != beatmapId)
+ {
+ lastBeatmapId = beatmapId;
+
+ var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId && b.BeatmapInfo != null);
+
+ if (existing != null)
+ Beatmap.Value = existing.BeatmapInfo;
+ else
+ {
+ var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = beatmapId });
+ req.Success += b => Beatmap.Value = b.ToBeatmap(Rulesets);
+ API.Queue(req);
+ }
+ }
+
+ Mods.Value = (LegacyMods)mods;
+ }
+ }
+ catch
+ {
+ // file might be in use.
+ }
+
+ try
+ {
+ using (var stream = Storage.GetStream(file_ipc_channel_filename))
+ using (var sr = new StreamReader(stream))
+ {
+ ChatChannel.Value = sr.ReadLine();
+ }
+ }
+ catch (Exception)
+ {
+ // file might be in use.
+ }
+
+ try
+ {
+ using (var stream = Storage.GetStream(file_ipc_state_filename))
+ using (var sr = new StreamReader(stream))
+ {
+ State.Value = (TourneyState)Enum.Parse(typeof(TourneyState), sr.ReadLine());
+ }
+ }
+ catch (Exception)
+ {
+ // file might be in use.
+ }
+
+ try
+ {
+ using (var stream = Storage.GetStream(file_ipc_scores_filename))
+ using (var sr = new StreamReader(stream))
+ {
+ Score1.Value = int.Parse(sr.ReadLine());
+ Score2.Value = int.Parse(sr.ReadLine());
+ }
+ }
+ catch (Exception)
+ {
+ // file might be in use.
+ }
+ }, 250, true);
}
catch (Exception e)
{
Logger.Error(e, "Stable installation could not be found; disabling file based IPC");
- return;
}
- const string file_ipc_filename = "ipc.txt";
- const string file_ipc_state_filename = "ipc-state.txt";
- const string file_ipc_scores_filename = "ipc-scores.txt";
- const string file_ipc_channel_filename = "ipc-channel.txt";
-
- if (stable.Exists(file_ipc_filename))
- Scheduler.AddDelayed(delegate
- {
- try
- {
- using (var stream = stable.GetStream(file_ipc_filename))
- using (var sr = new StreamReader(stream))
- {
- var beatmapId = int.Parse(sr.ReadLine());
- var mods = int.Parse(sr.ReadLine());
-
- if (lastBeatmapId != beatmapId)
- {
- lastBeatmapId = beatmapId;
-
- var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId && b.BeatmapInfo != null);
-
- if (existing != null)
- Beatmap.Value = existing.BeatmapInfo;
- else
- {
- var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = beatmapId });
- req.Success += b => Beatmap.Value = b.ToBeatmap(Rulesets);
- API.Queue(req);
- }
- }
-
- Mods.Value = (LegacyMods)mods;
- }
- }
- catch
- {
- // file might be in use.
- }
-
- try
- {
- using (var stream = stable.GetStream(file_ipc_channel_filename))
- using (var sr = new StreamReader(stream))
- {
- ChatChannel.Value = sr.ReadLine();
- }
- }
- catch (Exception)
- {
- // file might be in use.
- }
-
- try
- {
- using (var stream = stable.GetStream(file_ipc_state_filename))
- using (var sr = new StreamReader(stream))
- {
- State.Value = (TourneyState)Enum.Parse(typeof(TourneyState), sr.ReadLine());
- }
- }
- catch (Exception)
- {
- // file might be in use.
- }
-
- try
- {
- using (var stream = stable.GetStream(file_ipc_scores_filename))
- using (var sr = new StreamReader(stream))
- {
- Score1.Value = int.Parse(sr.ReadLine());
- Score2.Value = int.Parse(sr.ReadLine());
- }
- }
- catch (Exception)
- {
- // file might be in use.
- }
- }, 250, true);
+ return Storage;
}
///
diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs
new file mode 100644
index 0000000000..091a837745
--- /dev/null
+++ b/osu.Game.Tournament/Screens/SetupScreen.cs
@@ -0,0 +1,142 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Graphics.UserInterfaceV2;
+using osu.Game.Online.API;
+using osu.Game.Overlays;
+using osu.Game.Tournament.IPC;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tournament.Screens
+{
+ public class SetupScreen : TournamentScreen, IProvideVideo
+ {
+ private FillFlowContainer fillFlow;
+
+ private LoginOverlay loginOverlay;
+
+ [Resolved]
+ private MatchIPCInfo ipc { get; set; }
+
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChild = fillFlow = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Padding = new MarginPadding(10),
+ Spacing = new Vector2(10),
+ };
+
+ api.LocalUser.BindValueChanged(_ => Schedule(reload));
+ reload();
+ }
+
+ private void reload()
+ {
+ var fileBasedIpc = ipc as FileBasedIPC;
+
+ fillFlow.Children = new Drawable[]
+ {
+ new ActionableInfo
+ {
+ Label = "Current IPC source",
+ ButtonText = "Refresh",
+ Action = () =>
+ {
+ fileBasedIpc?.LocateStableStorage();
+ reload();
+ },
+ Value = fileBasedIpc?.Storage?.GetFullPath(string.Empty) ?? "Not found",
+ Failing = fileBasedIpc?.Storage == null,
+ Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation, and that it is registered as the default osu! install."
+ },
+ new ActionableInfo
+ {
+ Label = "Current User",
+ ButtonText = "Change Login",
+ Action = () =>
+ {
+ api.Logout();
+
+ if (loginOverlay == null)
+ {
+ AddInternal(loginOverlay = new LoginOverlay
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ });
+ }
+
+ loginOverlay.State.Value = Visibility.Visible;
+ },
+ Value = api?.LocalUser.Value.Username,
+ Failing = api?.IsLoggedIn != true,
+ Description = "In order to access the API and display metadata, a login is required."
+ }
+ };
+ }
+
+ private class ActionableInfo : LabelledComponent
+ {
+ private OsuButton button;
+
+ public ActionableInfo()
+ : base(true)
+ {
+ }
+
+ public string ButtonText
+ {
+ set => button.Text = value;
+ }
+
+ public string Value
+ {
+ set => valueText.Text = value;
+ }
+
+ public bool Failing
+ {
+ set => valueText.Colour = value ? Color4.Red : Color4.White;
+ }
+
+ public Action Action;
+
+ private OsuSpriteText valueText;
+
+ protected override Drawable CreateComponent() => new Container
+ {
+ AutoSizeAxes = Axes.Y,
+ RelativeSizeAxes = Axes.X,
+ Children = new Drawable[]
+ {
+ valueText = new OsuSpriteText
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ },
+ button = new TriangleButton
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ Size = new Vector2(100, 30),
+ Action = () => Action?.Invoke()
+ },
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs
index 4c255be463..02ee1c8603 100644
--- a/osu.Game.Tournament/TournamentSceneManager.cs
+++ b/osu.Game.Tournament/TournamentSceneManager.cs
@@ -69,6 +69,7 @@ namespace osu.Game.Tournament
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
+ new SetupScreen(),
new ScheduleScreen(),
new LadderScreen(),
new LadderEditorScreen(),
@@ -106,6 +107,8 @@ namespace osu.Game.Tournament
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
+ new OsuButton { RelativeSizeAxes = Axes.X, Text = "Setup", Action = () => SetScreen(typeof(SetupScreen)) },
+ new Container { RelativeSizeAxes = Axes.X, Height = 50 },
new OsuButton { RelativeSizeAxes = Axes.X, Text = "Team Editor", Action = () => SetScreen(typeof(TeamEditorScreen)) },
new OsuButton { RelativeSizeAxes = Axes.X, Text = "Rounds Editor", Action = () => SetScreen(typeof(RoundEditorScreen)) },
new OsuButton { RelativeSizeAxes = Axes.X, Text = "Bracket Editor", Action = () => SetScreen(typeof(LadderEditorScreen)) },
@@ -127,7 +130,7 @@ namespace osu.Game.Tournament
},
};
- SetScreen(typeof(ScheduleScreen));
+ SetScreen(typeof(SetupScreen));
}
public void SetScreen(Type screenType)
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/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
index 81f517dd86..8014631eca 100644
--- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
+++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
@@ -84,7 +84,7 @@ namespace osu.Game.Beatmaps.Drawables
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
// the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment)
- Icon = ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }
+ Icon = ruleset?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }
}
};
}
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/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs
index d5cdd7e4bc..02382cfd2b 100644
--- a/osu.Game/Configuration/DatabasedConfigManager.cs
+++ b/osu.Game/Configuration/DatabasedConfigManager.cs
@@ -16,11 +16,11 @@ namespace osu.Game.Configuration
private readonly int? variant;
- private readonly List databasedSettings;
+ private List databasedSettings;
private readonly RulesetInfo ruleset;
- private readonly bool legacySettingsExist;
+ private bool legacySettingsExist;
protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int? variant = null)
{
@@ -28,21 +28,31 @@ namespace osu.Game.Configuration
this.ruleset = ruleset;
this.variant = variant;
- databasedSettings = settings.Query(ruleset?.ID, variant);
- legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out var _));
+ Load();
InitialiseDefaults();
}
protected override void PerformLoad()
{
+ databasedSettings = settings.Query(ruleset?.ID, variant);
+ legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out var _));
}
protected override bool PerformSave()
{
+ lock (dirtySettings)
+ {
+ foreach (var setting in dirtySettings)
+ settings.Update(setting);
+ dirtySettings.Clear();
+ }
+
return true;
}
+ private readonly List dirtySettings = new List();
+
protected override void AddBindable(T lookup, Bindable bindable)
{
base.AddBindable(lookup, bindable);
@@ -80,7 +90,12 @@ namespace osu.Game.Configuration
bindable.ValueChanged += b =>
{
setting.Value = b.NewValue;
- settings.Update(setting);
+
+ lock (dirtySettings)
+ {
+ if (!dirtySettings.Contains(setting))
+ dirtySettings.Add(setting);
+ }
};
}
}
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 0cecbb225f..64b1f2d7bc 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Configuration
Set(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation);
- Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1);
+ Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f);
// Online settings
Set(OsuSetting.Username, string.Empty);
@@ -58,8 +58,8 @@ namespace osu.Game.Configuration
Set(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1);
// Input
- Set(OsuSetting.MenuCursorSize, 1.0, 0.5f, 2, 0.01);
- Set(OsuSetting.GameplayCursorSize, 1.0, 0.1f, 2, 0.01);
+ Set(OsuSetting.MenuCursorSize, 1.0f, 0.5f, 2f, 0.01f);
+ Set(OsuSetting.GameplayCursorSize, 1.0f, 0.1f, 2f, 0.01f);
Set(OsuSetting.AutoCursorSize, false);
Set(OsuSetting.MouseDisableButtons, false);
@@ -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/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs
index d13475189d..0f923c3a28 100644
--- a/osu.Game/Graphics/Backgrounds/Background.cs
+++ b/osu.Game/Graphics/Backgrounds/Background.cs
@@ -57,8 +57,9 @@ namespace osu.Game.Graphics.Backgrounds
AddInternal(bufferedContainer = new BufferedContainer
{
- CacheDrawnFrameBuffer = true,
RelativeSizeAxes = Axes.Both,
+ CacheDrawnFrameBuffer = true,
+ RedrawOnScale = false,
Child = Sprite
});
}
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 9c948d6f90..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();
}
///
@@ -62,21 +69,31 @@ namespace osu.Game.Graphics.Containers
protected override bool OnClick(ClickEvent e)
{
- closeIfOutside(e);
+ if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition))
+ Hide();
return base.OnClick(e);
}
- protected override bool OnDragEnd(DragEndEvent e)
- {
- closeIfOutside(e);
- return base.OnDragEnd(e);
- }
+ private bool closeOnDragEnd;
- private void closeIfOutside(MouseEvent e)
+ protected override bool OnDragStart(DragStartEvent e)
{
if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition))
+ closeOnDragEnd = true;
+
+ return base.OnDragStart(e);
+ }
+
+ protected override bool OnDragEnd(DragEndEvent e)
+ {
+ if (closeOnDragEnd)
+ {
Hide();
+ closeOnDragEnd = false;
+ }
+
+ return base.OnDragEnd(e);
}
public virtual bool OnPressed(GlobalAction action)
@@ -96,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()
@@ -127,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/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs
index e103798355..5a83d8e4ce 100644
--- a/osu.Game/Graphics/Cursor/MenuCursor.cs
+++ b/osu.Game/Graphics/Cursor/MenuCursor.cs
@@ -124,7 +124,7 @@ namespace osu.Game.Graphics.Cursor
public class Cursor : Container
{
private Container cursorContainer;
- private Bindable cursorScale;
+ private Bindable cursorScale;
private const float base_scale = 0.15f;
public Sprite AdditiveLayer;
@@ -159,9 +159,8 @@ namespace osu.Game.Graphics.Cursor
}
};
- cursorScale = config.GetBindable(OsuSetting.MenuCursorSize);
- cursorScale.ValueChanged += scale => cursorContainer.Scale = new Vector2((float)scale.NewValue * base_scale);
- cursorScale.TriggerChange();
+ cursorScale = config.GetBindable(OsuSetting.MenuCursorSize);
+ cursorScale.BindValueChanged(scale => cursorContainer.Scale = new Vector2(scale.NewValue * base_scale), true);
}
}
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/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs
index 24816deeb5..12688da9df 100644
--- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs
+++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs
@@ -55,6 +55,7 @@ namespace osu.Game.Graphics.Sprites
Origin = Anchor.Centre,
BlurSigma = new Vector2(4),
CacheDrawnFrameBuffer = true,
+ RedrawOnScale = false,
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
Size = new Vector2(3f),
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/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs
index 1fb73efa65..4f678b7218 100644
--- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs
+++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs
@@ -34,12 +34,12 @@ namespace osu.Game.Graphics.UserInterface
this.buttons = buttons ?? new[] { MouseButton.Left };
}
- protected override bool OnMouseUp(MouseUpEvent e)
+ protected override bool OnClick(ClickEvent e)
{
if (buttons.Contains(e.Button) && Contains(e.ScreenSpaceMousePosition))
sampleClick?.Play();
- return base.OnMouseUp(e);
+ return base.OnClick(e);
}
[BackgroundDependencyLoader]
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/Graphics/UserInterfaceV2/LabelledComponent.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs
new file mode 100644
index 0000000000..2e659825b7
--- /dev/null
+++ b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs
@@ -0,0 +1,132 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics.Containers;
+using osuTK;
+
+namespace osu.Game.Graphics.UserInterfaceV2
+{
+ public abstract class LabelledComponent : CompositeDrawable
+ where T : Drawable
+ {
+ protected const float CONTENT_PADDING_VERTICAL = 10;
+ protected const float CONTENT_PADDING_HORIZONTAL = 15;
+ protected const float CORNER_RADIUS = 15;
+
+ ///
+ /// The component that is being displayed.
+ ///
+ protected readonly T Component;
+
+ private readonly OsuTextFlowContainer labelText;
+ private readonly OsuTextFlowContainer descriptionText;
+
+ ///
+ /// Creates a new .
+ ///
+ /// Whether the component should be padded or should be expanded to the bounds of this .
+ protected LabelledComponent(bool padded)
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+
+ CornerRadius = CORNER_RADIUS;
+ Masking = true;
+
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = OsuColour.FromHex("1c2125"),
+ },
+ new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Padding = padded
+ ? new MarginPadding { Horizontal = CONTENT_PADDING_HORIZONTAL, Vertical = CONTENT_PADDING_VERTICAL }
+ : new MarginPadding { Left = CONTENT_PADDING_HORIZONTAL },
+ Spacing = new Vector2(0, 12),
+ Children = new Drawable[]
+ {
+ new GridContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ labelText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Bold))
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ AutoSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Right = 20 }
+ },
+ new Container
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Child = Component = CreateComponent().With(d =>
+ {
+ d.Anchor = Anchor.CentreRight;
+ d.Origin = Anchor.CentreRight;
+ })
+ }
+ },
+ },
+ RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
+ ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
+ },
+ descriptionText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true))
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Padding = new MarginPadding { Bottom = padded ? 0 : CONTENT_PADDING_VERTICAL },
+ Alpha = 0,
+ }
+ }
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour osuColour)
+ {
+ descriptionText.Colour = osuColour.Yellow;
+ }
+
+ public string Label
+ {
+ set => labelText.Text = value;
+ }
+
+ public string Description
+ {
+ set
+ {
+ descriptionText.Text = value;
+
+ if (!string.IsNullOrEmpty(value))
+ descriptionText.Show();
+ else
+ descriptionText.Hide();
+ }
+ }
+
+ ///
+ /// Creates the component that should be displayed.
+ ///
+ /// The component.
+ protected abstract T CreateComponent();
+ }
+}
diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs
new file mode 100644
index 0000000000..50d2a14482
--- /dev/null
+++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs
@@ -0,0 +1,49 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Graphics.UserInterfaceV2
+{
+ public class LabelledTextBox : LabelledComponent
+ {
+ public event TextBox.OnCommitHandler OnCommit;
+
+ public LabelledTextBox()
+ : base(false)
+ {
+ }
+
+ public bool ReadOnly
+ {
+ set => Component.ReadOnly = value;
+ }
+
+ public string PlaceholderText
+ {
+ set => Component.PlaceholderText = value;
+ }
+
+ public string Text
+ {
+ set => Component.Text = value;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ Component.BorderColour = colours.Blue;
+ }
+
+ protected override OsuTextBox CreateComponent() => new OsuTextBox
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.X,
+ CornerRadius = CORNER_RADIUS,
+ }.With(t => t.OnCommit += (sender, newText) => OnCommit?.Invoke(sender, newText));
+ }
+}
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