diff --git a/osu.Android.props b/osu.Android.props
index 3b14d85e53..9aba8e236c 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,8 +51,8 @@
-
-
+
+
diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs
index 062f2ce10c..6b88f21bcd 100644
--- a/osu.Android/OsuGameAndroid.cs
+++ b/osu.Android/OsuGameAndroid.cs
@@ -106,9 +106,9 @@ namespace osu.Android
private class AndroidBatteryInfo : BatteryInfo
{
- public override double ChargeLevel => Battery.ChargeLevel;
+ public override double? ChargeLevel => Battery.ChargeLevel;
- public override bool IsCharging => Battery.PowerSource != BatteryPowerSource.Battery;
+ public override bool OnBattery => Battery.PowerSource == BatteryPowerSource.Battery;
}
}
}
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 524436235e..d9ad95f96a 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -29,6 +29,8 @@ using osu.Game.IPC;
using osu.Game.Overlays.Settings;
using osu.Game.Overlays.Settings.Sections;
using osu.Game.Overlays.Settings.Sections.Input;
+using osu.Game.Utils;
+using SDL2;
namespace osu.Desktop
{
@@ -166,6 +168,8 @@ namespace osu.Desktop
}
}
+ protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
+
private readonly List importableFiles = new List();
private ScheduledDelegate? importSchedule;
@@ -206,5 +210,23 @@ namespace osu.Desktop
base.Dispose(isDisposing);
osuSchemeLinkIPCChannel?.Dispose();
}
+
+ private class SDL2BatteryInfo : BatteryInfo
+ {
+ public override double? ChargeLevel
+ {
+ get
+ {
+ SDL.SDL_GetPowerInfo(out _, out int percentage);
+
+ if (percentage == -1)
+ return null;
+
+ return percentage / 100.0;
+ }
+ }
+
+ public override bool OnBattery => SDL.SDL_GetPowerInfo(out _, out _) == SDL.SDL_PowerState.SDL_POWERSTATE_ON_BATTERY;
+ }
}
}
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index cebbcb40b7..c7505e624c 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -21,7 +21,11 @@ namespace osu.Desktop
{
public static class Program
{
+#if DEBUG
+ private const string base_game_name = @"osu-development";
+#else
private const string base_game_name = @"osu";
+#endif
private static LegacyTcpIpcProvider legacyIpc;
@@ -37,9 +41,15 @@ namespace osu.Desktop
// See https://www.mongodb.com/docs/realm/sdk/dotnet/#supported-platforms
if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2))
{
+ // If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider
+ // disabling it ourselves.
+ // We could also better detect compatibility mode if required:
+ // https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730
SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR,
"Your operating system is too old to run osu!",
- "This version of osu! requires at least Windows 8.1 to run.\nPlease upgrade your operating system or consider using an older version of osu!.", IntPtr.Zero);
+ "This version of osu! requires at least Windows 8.1 to run.\n"
+ + "Please upgrade your operating system or consider using an older version of osu!.\n\n"
+ + "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!", IntPtr.Zero);
return;
}
diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs b/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs
index 19321a48b9..fbbfee6b60 100644
--- a/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.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.
-#nullable disable
-
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Utils;
diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs
index ffc5734f01..bbe543e73e 100644
--- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.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.
-#nullable disable
-
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Utils;
diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs
index 886822f9a5..3e06e78dba 100644
--- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.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.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects;
diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs
index 3209be12d5..c01aff0aa0 100644
--- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.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.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
index 8dd6f82c57..2078e1453f 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
@@ -9,7 +9,6 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Framework.Testing;
-using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
using osuTK;
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index b64d860417..f37479f84a 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
private float halfCatcherWidth;
+ public override int Version => 20220701;
+
public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs
index c5ca595fd6..50e48101d3 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.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.
-#nullable disable
-
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Replays;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs
index 10a0809e05..7eda6b37d3 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.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.
-#nullable disable
-
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModClassic.cs b/osu.Game.Rulesets.Catch/Mods/CatchModClassic.cs
index 904656993e..9624e84018 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModClassic.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModClassic.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs
index 8d4b57c244..cae19e9468 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
index 6927d7953f..e59a0a0431 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.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.
-#nullable disable
-
using System.Linq;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs
index 4db0a53005..57c06e1cd1 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs
@@ -1,14 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModDoubleTime : ModDoubleTime
{
- public override double ScoreMultiplier => 1.06;
+ public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs
index bea9b094fa..16ef56d845 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
index e05932ca03..4824106c55 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
@@ -1,9 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Bindables;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Rulesets.Catch.Objects;
@@ -16,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModFlashlight : ModFlashlight
{
- public override double ScoreMultiplier => 1.12;
+ public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
@@ -37,9 +36,9 @@ namespace osu.Game.Rulesets.Catch.Mods
public override float DefaultFlashlightSize => 350;
- protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield);
+ protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield.AsNonNull());
- private CatchPlayfield playfield;
+ private CatchPlayfield? playfield;
public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs
index 1fe892c9b5..63203dd57c 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.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.
-#nullable disable
-
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Catch.Objects;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs
index 0c7886be10..ce06b841aa 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
index 7db9bf2dfd..93eadcc13e 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Beatmaps;
@@ -11,7 +9,7 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModHardRock : ModHardRock, IApplicableToBeatmapProcessor
{
- public override double ScoreMultiplier => 1.12;
+ public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor)
{
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs
index c02eedf936..51516edacd 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.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.
-#nullable disable
-
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects;
@@ -17,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public class CatchModHidden : ModHidden, IApplicableToDrawableRuleset
{
public override string Description => @"Play with fading fruits.";
- public override double ScoreMultiplier => 1.06;
+ public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
private const double fade_out_offset_multiplier = 0.6;
private const double fade_out_duration_multiplier = 0.44;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs b/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs
index 89fc40356d..a97e940a64 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModMirror.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.
-#nullable disable
-
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Beatmaps;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModMuted.cs b/osu.Game.Rulesets.Catch/Mods/CatchModMuted.cs
index 6b28d1a127..6d2565440a 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModMuted.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModMuted.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.
-#nullable disable
-
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs
index 365d987794..9e38913be7 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.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.
-#nullable disable
-
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Mods;
@@ -10,6 +8,6 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModNightcore : ModNightcore
{
- public override double ScoreMultiplier => 1.06;
+ public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs
index 89e7e4bcd6..3c02646e99 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs
index 385d4c50c0..a24a6227fe 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.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.
-#nullable disable
-
using System;
using osu.Framework.Bindables;
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs b/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs
index 0a74ee4fbb..fb92399102 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
index f4d6fb9ab3..0ab6da0363 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
@@ -1,8 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
+using System.Diagnostics;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
@@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public override string Description => @"Use the mouse to control the catcher.";
- private DrawableRuleset drawableRuleset;
+ private DrawableRuleset? drawableRuleset;
public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
@@ -29,6 +28,8 @@ namespace osu.Game.Rulesets.Catch.Mods
public void ApplyToPlayer(Player player)
{
+ Debug.Assert(drawableRuleset != null);
+
if (!drawableRuleset.HasReplayLoaded.Value)
drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield));
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs b/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs
index d98829137c..68e01391ce 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
index c5e5e59dd2..c06d9f520f 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
@@ -6,7 +6,6 @@
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
using osuTK.Graphics;
diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs
index 538c8b13d1..60363aaeef 100644
--- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.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.
-#nullable disable
-
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs
index 4222be0359..7970d5b594 100644
--- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.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.
-#nullable disable
-
using System.Linq;
using NUnit.Framework;
using osu.Game.Beatmaps;
diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs
index 4c97f65b07..f2cc254e38 100644
--- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.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.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Tests.Visual;
diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs
index 9612543483..2e3b21aed7 100644
--- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.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.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Objects;
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index 178094476f..5d30d33190 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
private readonly bool isForCurrentRuleset;
private readonly double originalOverallDifficulty;
+ public override int Version => 20220701;
+
public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
diff --git a/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs b/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs
index 0bae893810..410386c9d5 100644
--- a/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs
+++ b/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs
index 8f8b7cb091..050b302bd8 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.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.
-#nullable disable
-
using System;
using System.Linq;
using osu.Game.Beatmaps;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs
index f9c51bf6a2..d444c9b634 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.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.
-#nullable disable
-
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs
index 8d54923e7b..f0db742eac 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.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.
-#nullable disable
-
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs
index 603d096ed7..073dda9de8 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs
index 20dfc14f09..614ef76a3b 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.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.
-#nullable disable
-
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mania.Objects;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs
index b166f3ebc3..bec0a6a1d3 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs
index d053e64315..0817f8f9fc 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs
index 66627e6ed3..a302f95966 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
index f25a77278b..c78bf72979 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.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.
-#nullable disable
-
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs
index ff236d33bf..4093aeb2a7 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs
index ddbd7c5d6a..f80c9e1f7c 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.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.
-#nullable disable
-
using System;
using System.Linq;
using osu.Game.Rulesets.Mania.UI;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
index 58005df561..8ef5bfd94c 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.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.
-#nullable disable
-
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs
index 87c81c2866..014954dd60 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs
index 380edca515..d9de06a811 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs
index 698555ddc4..e3ac624a6e 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.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.
-#nullable disable
-
using System;
using System.Linq;
using osu.Game.Rulesets.Mania.UI;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs
index 8c4fd0a8fc..a65938184c 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.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.
-#nullable disable
-
using System;
using System.Linq;
using osu.Game.Beatmaps;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs
index f202b859b1..4cbdaee323 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.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.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs
index 9ce4fb6a48..948979505c 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.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.
-#nullable disable
-
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModKey1 : ManiaKeyMod
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs
index f378ce3435..684370fc3d 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.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.
-#nullable disable
-
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModKey10 : ManiaKeyMod
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs
index 5812df80f5..de91902ca8 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.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.
-#nullable disable
-
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModKey2 : ManiaKeyMod
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs
index 4116ed5ceb..8575a96bde 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.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.
-#nullable disable
-
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModKey3 : ManiaKeyMod
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs
index 9879fec686..54ea3afa07 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.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.
-#nullable disable
-
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModKey4 : ManiaKeyMod
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs
index 646386b0d8..e9a9bba5bd 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.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.
-#nullable disable
-
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModKey5 : ManiaKeyMod
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs
index 56af9ed589..b9606d1cb5 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.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.
-#nullable disable
-
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModKey6 : ManiaKeyMod
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs
index a0a7116ed7..b80d794085 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.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.
-#nullable disable
-
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModKey7 : ManiaKeyMod
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs
index fc8ecdb9ea..3462d634a4 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.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.
-#nullable disable
-
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModKey8 : ManiaKeyMod
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs
index c495a6c82f..83c505c048 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.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.
-#nullable disable
-
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModKey9 : ManiaKeyMod
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs
index 6e56981fc8..9c3744ea98 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.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.
-#nullable disable
-
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMuted.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMuted.cs
index 076f634968..33ebcf303a 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModMuted.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMuted.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.
-#nullable disable
-
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs
index 9ae664e1f6..4cc712060c 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.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.
-#nullable disable
-
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs
index 487f32dc26..e8988be548 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs
index 2789a2a06e..2e22e23dbd 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs
index 5da29e5a1d..3c24e91d54 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.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.
-#nullable disable
-
using System;
using System.Linq;
using osu.Framework.Graphics;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
index 22347d21b8..dfb02408d2 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.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.
-#nullable disable
-
using System;
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs
index 17759d718e..ecc343ecaa 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs b/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs
index 4f005a0c70..d3cb3bcf59 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.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.
-#nullable disable
-
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests.Mods
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs
index 3d59e4fb51..5e46498aca 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.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.
-#nullable disable
-
using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps;
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs
index 378b71ccf7..3563995234 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.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.
-#nullable disable
-
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
@@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
private void runSpmTest(Mod mod)
{
- SpinnerSpmCalculator spmCalculator = null;
+ SpinnerSpmCalculator? spmCalculator = null;
CreateModTest(new ModTestData
{
@@ -61,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
return spmCalculator != null;
});
- AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCalculator.Result.Value, 477, 5));
+ AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCalculator.AsNonNull().Result.Value, 477, 5));
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
index 80dc83d7dc..9d06ff5801 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.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.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs
index e1bed5153b..335ef31019 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.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.
-#nullable disable
-
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Rulesets.Osu.Mods;
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
index 5ed25baca3..e692f8ecbc 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.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.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@@ -162,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
private class TestOsuModHidden : OsuModHidden
{
- public new HitObject FirstObject => base.FirstObject;
+ public new HitObject? FirstObject => base.FirstObject;
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs
index 1f1db04c24..9b49e60363 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.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.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Rulesets.Osu.Mods;
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs
index 99c9036ac0..68669d1a53 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.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.
-#nullable disable
-
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
@@ -33,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
[Test]
public void TestModCopy()
{
- OsuModMuted muted = null;
+ OsuModMuted muted = null!;
AddStep("create inversed mod", () => muted = new OsuModMuted
{
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs
index 47e7ad320c..44404ca245 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.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.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs
index b7669624ff..985baa8cf5 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.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.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs
index 4f6d6376bf..e121e6103d 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.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.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -30,8 +28,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
[Test]
public void TestSpinnerAutoCompleted()
{
- DrawableSpinner spinner = null;
- JudgementResult lastResult = null;
+ DrawableSpinner? spinner = null;
+ JudgementResult? lastResult = null;
CreateModTest(new ModTestData
{
@@ -63,11 +61,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
[TestCase(null)]
[TestCase(typeof(OsuModDoubleTime))]
[TestCase(typeof(OsuModHalfTime))]
- public void TestSpinRateUnaffectedByMods(Type additionalModType)
+ public void TestSpinRateUnaffectedByMods(Type? additionalModType)
{
var mods = new List { new OsuModSpunOut() };
if (additionalModType != null)
- mods.Add((Mod)Activator.CreateInstance(additionalModType));
+ mods.Add((Mod)Activator.CreateInstance(additionalModType)!);
CreateModTest(new ModTestData
{
@@ -96,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
[Test]
public void TestSpinnerGetsNoBonusScore()
{
- DrawableSpinner spinner = null;
+ DrawableSpinner? spinner = null;
List results = new List();
CreateModTest(new ModTestData
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
index bb593c2fb3..46f7c461f8 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
@@ -17,18 +17,18 @@ namespace osu.Game.Rulesets.Osu.Tests
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
- [TestCase(6.6972307565739273d, 206, "diffcalc-test")]
- [TestCase(1.4484754139145539d, 45, "zero-length-sliders")]
+ [TestCase(6.6369583000323935d, 206, "diffcalc-test")]
+ [TestCase(1.4476531024675374d, 45, "zero-length-sliders")]
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
=> base.Test(expectedStarRating, expectedMaxCombo, name);
- [TestCase(8.9382559208689809d, 206, "diffcalc-test")]
- [TestCase(1.7548875851757628d, 45, "zero-length-sliders")]
+ [TestCase(8.8816128335486386d, 206, "diffcalc-test")]
+ [TestCase(1.7540389962596916d, 45, "zero-length-sliders")]
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime());
- [TestCase(6.6972307218715166d, 239, "diffcalc-test")]
- [TestCase(1.4484754139145537d, 54, "zero-length-sliders")]
+ [TestCase(6.6369583000323935d, 239, "diffcalc-test")]
+ [TestCase(1.4476531024675374d, 54, "zero-length-sliders")]
public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs
index 0694746cbf..76d5ccf682 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs
@@ -108,13 +108,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// Reward for % distance up to 125 / strainTime for overlaps where velocity is still changing.
double overlapVelocityBuff = Math.Min(125 / Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Abs(prevVelocity - currVelocity));
- // Reward for % distance slowed down compared to previous, paying attention to not award overlap
- double nonOverlapVelocityBuff = Math.Abs(prevVelocity - currVelocity)
- // do not award overlap
- * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, Math.Min(osuCurrObj.LazyJumpDistance, osuLastObj.LazyJumpDistance) / 100)), 2);
-
- // Choose the largest bonus, multiplied by ratio.
- velocityChangeBonus = Math.Max(overlapVelocityBuff, nonOverlapVelocityBuff) * distRatio;
+ velocityChangeBonus = overlapVelocityBuff * distRatio;
// Penalize for rhythm changes.
velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2);
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 75d9469da3..9f4a405113 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private const double difficulty_multiplier = 0.0675;
private double hitWindowGreat;
+ public override int Version => 20220701;
+
public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
index 139bfe7dd3..59be93530c 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
@@ -16,6 +16,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
+using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osuTK;
using osuTK.Input;
@@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public class SliderPlacementBlueprint : PlacementBlueprint
{
- public new Objects.Slider HitObject => (Objects.Slider)base.HitObject;
+ public new Slider HitObject => (Slider)base.HitObject;
private SliderBodyPiece bodyPiece;
private HitCirclePiece headCirclePiece;
@@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private IDistanceSnapProvider snapProvider { get; set; }
public SliderPlacementBlueprint()
- : base(new Objects.Slider())
+ : base(new Slider())
{
RelativeSizeAxes = Axes.Both;
@@ -82,7 +83,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
case SliderPlacementState.Initial:
BeginPlacement();
- var nearestDifficultyPoint = editorBeatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.DifficultyControlPoint?.DeepClone() as DifficultyControlPoint;
+ var nearestDifficultyPoint = editorBeatmap.HitObjects
+ .LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime)?
+ .DifficultyControlPoint?.DeepClone() as DifficultyControlPoint;
HitObject.DifficultyControlPoint = nearestDifficultyPoint ?? new DifficultyControlPoint();
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
diff --git a/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs
index affc0bae6a..4a3b187e83 100644
--- a/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs
+++ b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.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.
-#nullable disable
-
namespace osu.Game.Rulesets.Osu.Mods
{
///
diff --git a/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs
index a108f5fd14..1458abfe05 100644
--- a/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs
+++ b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.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.
-#nullable disable
-
namespace osu.Game.Rulesets.Osu.Mods
{
///
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs
index e25845f5ab..e6889403a3 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.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.
-#nullable disable
-
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
index a3f6448457..872fcf7e9b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.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.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -30,11 +28,11 @@ namespace osu.Game.Rulesets.Osu.Mods
public bool RestartOnFail => false;
- private OsuInputManager inputManager;
+ private OsuInputManager inputManager = null!;
- private IFrameStableClock gameplayClock;
+ private IFrameStableClock gameplayClock = null!;
- private List replayFrames;
+ private List replayFrames = null!;
private int currentFrame;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs
index c4de77b8a3..7c1f6be9ed 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.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.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs
index 71bdd98457..9e71f657ce 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
index 199c735787..56665db770 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.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.
-#nullable disable
-
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -30,10 +28,10 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => FontAwesome.Solid.Adjust;
public override ModType Type => ModType.DifficultyIncrease;
- public override double ScoreMultiplier => 1.12;
+ public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight) };
- private DrawableOsuBlinds blinds;
+ private DrawableOsuBlinds blinds = null!;
public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
@@ -55,9 +53,12 @@ namespace osu.Game.Rulesets.Osu.Mods
///
/// Black background boxes behind blind panel textures.
///
- private Box blackBoxLeft, blackBoxRight;
+ private Box blackBoxLeft = null!, blackBoxRight = null!;
- private Drawable panelLeft, panelRight, bgPanelLeft, bgPanelRight;
+ private Drawable panelLeft = null!;
+ private Drawable panelRight = null!;
+ private Drawable bgPanelLeft = null!;
+ private Drawable bgPanelRight = null!;
private readonly Beatmap beatmap;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs
index d5096619b9..769694baf4 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.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.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs
index 00009f4c3d..e021992f86 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.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.
-#nullable disable
-
using System;
using System.Linq;
using osu.Framework.Bindables;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs
index c4cc0b4f48..371dfe6a1a 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
index e95e61312e..ee6a7815e2 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.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.
-#nullable disable
-
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
index be159523b7..3a6b232f9f 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.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.
-#nullable disable
-
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs
index 2d19305509..700a3f44bc 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs
@@ -1,14 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModDoubleTime : ModDoubleTime
{
- public override double ScoreMultiplier => 1.12;
+ public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs
index 90b22e8d9c..06b5b6cfb8 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
index c082805a0e..e5a458488e 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.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.
-#nullable disable
-
using System;
using System.Linq;
using osu.Framework.Bindables;
@@ -21,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModFlashlight : ModFlashlight, IApplicableToDrawableHitObject
{
- public override double ScoreMultiplier => 1.12;
+ public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModBlinds)).ToArray();
private const double default_follow_delay = 120;
@@ -53,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override float DefaultFlashlightSize => 180;
- private OsuFlashlight flashlight;
+ private OsuFlashlight flashlight = null!;
protected override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(this);
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
index 34840de983..182d6eeb4b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.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.
-#nullable disable
-
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs
index 54c5c56ca6..4769e7660b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
index fdddfed4d5..5430929143 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.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.
-#nullable disable
-
using System;
using System.Linq;
using osu.Game.Rulesets.Mods;
@@ -14,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModHardRock : ModHardRock, IApplicableToHitObject
{
- public override double ScoreMultiplier => 1.06;
+ public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModMirror)).ToArray();
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index 11ceb0f710..97f201b2cc 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.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.
-#nullable disable
-
using System;
using System.Diagnostics;
using System.Linq;
@@ -25,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public Bindable OnlyFadeApproachCircles { get; } = new BindableBool();
public override string Description => @"Play with no approach circles and fading circles/sliders.";
- public override double ScoreMultiplier => 1.06;
+ public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) };
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs
index f9a74d2a3a..9316f9ed74 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.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.
-#nullable disable
-
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
@@ -28,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel) };
- private IFrameStableClock gameplayClock;
+ private IFrameStableClock gameplayClock = null!;
[SettingSource("Attraction strength", "How strong the pull is.", 0)]
public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f)
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs
index 1d822a2d4c..3faca0b01f 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModMirror.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.
-#nullable disable
-
using System;
using osu.Framework.Bindables;
using osu.Game.Configuration;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMuted.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMuted.cs
index 1d4650a379..5e3ee37b61 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModMuted.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModMuted.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs
index e9be56fcc5..b7838ebaa7 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
@@ -10,6 +8,6 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModNightcore : ModNightcore
{
- public override double ScoreMultiplier => 1.12;
+ public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs
index c20fcf0b1b..9f707a5aa6 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.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.
-#nullable disable
-
using System;
using System.Linq;
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
index fe415cb967..3eb8982f5d 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.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.
-#nullable disable
-
using System;
using System.Linq;
using osu.Framework.Bindables;
@@ -21,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Description => "Where's the cursor?";
- private PeriodTracker spinnerPeriods;
+ private PeriodTracker spinnerPeriods = null!;
[SettingSource(
"Hidden at combo",
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
index 44942e9e37..59984f9a7b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.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.
-#nullable disable
-
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs
index bde7718da5..33581405a6 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.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.
-#nullable disable
-
using System;
using System.Linq;
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index 2030156f2e..908bb34ed6 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.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.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -31,9 +29,9 @@ namespace osu.Game.Rulesets.Osu.Mods
private bool isDownState;
private bool wasLeft;
- private OsuInputManager osuInputManager;
+ private OsuInputManager osuInputManager = null!;
- private ReplayState state;
+ private ReplayState state = null!;
private double lastStateChangeTime;
private bool hasReplay;
@@ -134,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Mods
wasLeft = !wasLeft;
}
- state?.Apply(osuInputManager.CurrentState, osuInputManager);
+ state.Apply(osuInputManager.CurrentState, osuInputManager);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
index 16e7780af0..95e7d13ee7 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.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.
-#nullable disable
-
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
index 61028a1ee8..d9ab749ad3 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.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.
-#nullable disable
-
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs
index 565ff415be..0b34ab28a3 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.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.
-#nullable disable
-
using System;
using System.Linq;
using System.Threading;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs
index 4eb7659152..429fe30fc5 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.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.
-#nullable disable
-
using System;
using System.Linq;
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
index f03bcffdc8..623157a427 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.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.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -96,11 +94,7 @@ namespace osu.Game.Rulesets.Osu.Mods
#region Private Fields
- private ControlPointInfo controlPointInfo;
-
- private List originalHitObjects;
-
- private Random rng;
+ private ControlPointInfo controlPointInfo = null!;
#endregion
@@ -171,16 +165,17 @@ namespace osu.Game.Rulesets.Osu.Mods
public override void ApplyToBeatmap(IBeatmap beatmap)
{
Seed.Value ??= RNG.Next();
- rng = new Random(Seed.Value.Value);
+
+ var rng = new Random(Seed.Value.Value);
var osuBeatmap = (OsuBeatmap)beatmap;
if (osuBeatmap.HitObjects.Count == 0) return;
controlPointInfo = osuBeatmap.ControlPointInfo;
- originalHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList();
- var hitObjects = generateBeats(osuBeatmap)
+ var originalHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList();
+ var hitObjects = generateBeats(osuBeatmap, originalHitObjects)
.Select(beat =>
{
var newCircle = new HitCircle();
@@ -189,18 +184,18 @@ namespace osu.Game.Rulesets.Osu.Mods
return (OsuHitObject)newCircle;
}).ToList();
- addHitSamples(hitObjects);
+ addHitSamples(hitObjects, originalHitObjects);
- fixComboInfo(hitObjects);
+ fixComboInfo(hitObjects, originalHitObjects);
- randomizeCirclePos(hitObjects);
+ randomizeCirclePos(hitObjects, rng);
osuBeatmap.HitObjects = hitObjects;
base.ApplyToBeatmap(beatmap);
}
- private IEnumerable generateBeats(IBeatmap beatmap)
+ private IEnumerable generateBeats(IBeatmap beatmap, IReadOnlyCollection originalHitObjects)
{
double startTime = originalHitObjects.First().StartTime;
double endTime = originalHitObjects.Last().GetEndTime();
@@ -213,7 +208,7 @@ namespace osu.Game.Rulesets.Osu.Mods
// Remove beats before startTime
.Where(beat => almostBigger(beat, startTime))
// Remove beats during breaks
- .Where(beat => !isInsideBreakPeriod(beatmap.Breaks, beat))
+ .Where(beat => !isInsideBreakPeriod(originalHitObjects, beatmap.Breaks, beat))
.ToList();
// Remove beats that are too close to the next one (e.g. due to timing point changes)
@@ -228,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Mods
return beats;
}
- private void addHitSamples(IEnumerable hitObjects)
+ private void addHitSamples(IEnumerable hitObjects, List originalHitObjects)
{
foreach (var obj in hitObjects)
{
@@ -240,7 +235,7 @@ namespace osu.Game.Rulesets.Osu.Mods
}
}
- private void fixComboInfo(List hitObjects)
+ private void fixComboInfo(List hitObjects, List originalHitObjects)
{
// Copy combo indices from an original object at the same time or from the closest preceding object
// (Objects lying between two combos are assumed to belong to the preceding combo)
@@ -274,7 +269,7 @@ namespace osu.Game.Rulesets.Osu.Mods
}
}
- private void randomizeCirclePos(IReadOnlyList hitObjects)
+ private void randomizeCirclePos(IReadOnlyList hitObjects, Random rng)
{
if (hitObjects.Count == 0) return;
@@ -355,9 +350,10 @@ namespace osu.Game.Rulesets.Osu.Mods
/// The given time is also considered to be inside a break if it is earlier than the
/// start time of the first original hit object after the break.
///
+ /// Hit objects order by time.
/// The breaks of the beatmap.
/// The time to be checked.=
- private bool isInsideBreakPeriod(IEnumerable breaks, double time)
+ private bool isInsideBreakPeriod(IReadOnlyCollection originalHitObjects, IEnumerable breaks, double time)
{
return breaks.Any(breakPeriod =>
{
@@ -405,7 +401,7 @@ namespace osu.Game.Rulesets.Osu.Mods
/// The list of hit objects in a beatmap, ordered by StartTime
/// The point in time to get samples for
/// Hit samples
- private IList getSamplesAtTime(IEnumerable hitObjects, double time)
+ private IList? getSamplesAtTime(IEnumerable hitObjects, double time)
{
// Get a hit object that
// either has StartTime equal to the target time
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs
index f8c1e1639d..7276cc753c 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
index 6e5dd45a7a..d862d36670 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.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.
-#nullable disable
-
using System;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -59,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Mods
}
}
- private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable hitCircle = null)
+ private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable? hitCircle = null)
{
var h = hitObject.HitObject;
using (hitObject.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
index 84906f6eed..4354ecbe9a 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.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.
-#nullable disable
-
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
index 8acd4fc422..6bfbe25471 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.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.
-#nullable disable
-
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs
index a9b7e04caf..01719bfea6 100644
--- a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs
@@ -23,13 +23,29 @@ namespace osu.Game.Rulesets.Taiko.Tests
protected DrawableTaikoRuleset DrawableRuleset { get; private set; }
protected Container PlayfieldContainer { get; private set; }
+ private ControlPointInfo controlPointInfo { get; set; }
+
[BackgroundDependencyLoader]
private void load()
{
- var controlPointInfo = new ControlPointInfo();
+ controlPointInfo = new ControlPointInfo();
controlPointInfo.Add(0, new TimingControlPoint());
- IWorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
+ IWorkingBeatmap beatmap = CreateWorkingBeatmap(CreateBeatmap(new TaikoRuleset().RulesetInfo));
+
+ Add(PlayfieldContainer = new Container
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ RelativeSizeAxes = Axes.X,
+ Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT,
+ Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset)) }
+ });
+ }
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
+ {
+ return new Beatmap
{
HitObjects = new List { new Hit { Type = HitType.Centre } },
BeatmapInfo = new BeatmapInfo
@@ -41,19 +57,10 @@ namespace osu.Game.Rulesets.Taiko.Tests
Title = @"Sample Beatmap",
Author = { Username = @"peppy" },
},
- Ruleset = new TaikoRuleset().RulesetInfo
+ Ruleset = ruleset
},
ControlPointInfo = controlPointInfo
- });
-
- Add(PlayfieldContainer = new Container
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- RelativeSizeAxes = Axes.X,
- Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT,
- Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) }
- });
+ };
}
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs
index ca2f8102b7..3090facf8c 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.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.
-#nullable disable
-
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests.Mods
diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs
index 0b28bfee2e..7abbb9d186 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.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.
-#nullable disable
-
using System.Linq;
using NUnit.Framework;
using osu.Game.Rulesets.Taiko.Mods;
diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs
index 917462c128..a83cc16413 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.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.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Mods;
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs
index ca3e12cfd5..f342bfe78e 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200),
- Child = new InputDrum(playfield.HitObjectContainer)
+ Child = new InputDrum()
}
});
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs
new file mode 100644
index 0000000000..7210419c0e
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.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 NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Testing;
+using osu.Game.Rulesets.Taiko.UI;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ [TestFixture]
+ public class TestSceneDrumTouchInputArea : OsuTestScene
+ {
+ private DrumTouchInputArea drumTouchInputArea = null!;
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("create drum", () =>
+ {
+ Child = new TaikoInputManager(new TaikoRuleset().RulesetInfo)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new InputDrum
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Height = 0.2f,
+ },
+ drumTouchInputArea = new DrumTouchInputArea
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ },
+ },
+ };
+ });
+ }
+
+ [Test]
+ public void TestDrum()
+ {
+ AddStep("show drum", () => drumTouchInputArea.Show());
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs
index 674ac5670f..c674f87f80 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.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.
-#nullable disable
-
using System.Reflection;
using NUnit.Framework;
using osu.Framework.IO.Stores;
@@ -17,7 +15,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
protected override IResourceStore RulesetResources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneTaikoHitObjectSamples)));
[TestCase("taiko-normal-hitnormal")]
- [TestCase("normal-hitnormal")]
[TestCase("hitnormal")]
public void TestDefaultCustomSampleFromBeatmap(string expectedSample)
{
@@ -29,7 +26,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
}
[TestCase("taiko-normal-hitnormal")]
- [TestCase("normal-hitnormal")]
[TestCase("hitnormal")]
public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample)
{
@@ -41,7 +37,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
}
[TestCase("taiko-normal-hitnormal2")]
- [TestCase("normal-hitnormal2")]
public void TestUserSkinLookupIgnoresSampleBank(string unwantedSample)
{
SetupSkins(string.Empty, unwantedSample);
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerLegacySkin.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerLegacySkin.cs
new file mode 100644
index 0000000000..13df24c988
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerLegacySkin.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Taiko.Mods;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ public class TestSceneTaikoPlayerLegacySkin : LegacySkinPlayerTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset();
+
+ protected override TestPlayer CreatePlayer(Ruleset ruleset)
+ {
+ SelectedMods.Value = new[] { new TaikoModClassic() };
+ return base.CreatePlayer(ruleset);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index 228856cbe9..9267d1ee3c 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private const double colour_skill_multiplier = 0.01;
private const double stamina_skill_multiplier = 0.021;
+ public override int Version => 20220701;
+
public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index a9cde62f44..2c2dbddf13 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -35,13 +35,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
- double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
-
- if (score.Mods.Any(m => m is ModNoFail))
- multiplier *= 0.90;
+ double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
if (score.Mods.Any(m => m is ModHidden))
- multiplier *= 1.10;
+ multiplier *= 1.075;
+
+ if (score.Mods.Any(m => m is ModEasy))
+ multiplier *= 0.975;
double difficultyValue = computeDifficultyValue(score, taikoAttributes);
double accuracyValue = computeAccuracyValue(score, taikoAttributes);
@@ -61,12 +61,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
{
- double difficultyValue = Math.Pow(5 * Math.Max(1.0, attributes.StarRating / 0.175) - 4.0, 2.25) / 450.0;
+ double difficultyValue = Math.Pow(5 * Math.Max(1.0, attributes.StarRating / 0.115) - 4.0, 2.25) / 1150.0;
double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
difficultyValue *= lengthBonus;
- difficultyValue *= Math.Pow(0.985, countMiss);
+ difficultyValue *= Math.Pow(0.986, countMiss);
+
+ if (score.Mods.Any(m => m is ModEasy))
+ difficultyValue *= 0.980;
if (score.Mods.Any(m => m is ModHidden))
difficultyValue *= 1.025;
@@ -74,7 +77,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (score.Mods.Any(m => m is ModFlashlight))
difficultyValue *= 1.05 * lengthBonus;
- return difficultyValue * score.Accuracy;
+ return difficultyValue * Math.Pow(score.Accuracy, 1.5);
}
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
@@ -82,10 +85,16 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (attributes.GreatHitWindow <= 0)
return 0;
- double accValue = Math.Pow(150.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 15) * 22.0;
+ double accuracyValue = Math.Pow(140.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 12.0) * 27;
- // Bonus for many objects - it's harder to keep good accuracy up for longer
- return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
+ double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
+ accuracyValue *= lengthBonus;
+
+ // Slight HDFL Bonus for accuracy.
+ if (score.Mods.Any(m => m is ModFlashlight) && score.Mods.Any(m => m is ModHidden))
+ accuracyValue *= 1.10 * lengthBonus;
+
+ return accuracyValue;
}
private int totalHits => countGreat + countOk + countMeh + countMiss;
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs
index 6c01bae027..4b74b4991e 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.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.
-#nullable disable
-
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs
index d1c192f7fa..fee0cb2744 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.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.
-#nullable disable
-
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs
index 6d1a18bb78..f7fdd447d6 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs
@@ -1,8 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
+using System.Diagnostics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI;
@@ -12,16 +11,21 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset, IUpdatableByPlayfield
{
- private DrawableTaikoRuleset drawableTaikoRuleset;
+ private DrawableTaikoRuleset? drawableTaikoRuleset;
public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
drawableTaikoRuleset.LockPlayfieldAspect.Value = false;
+
+ var playfield = (TaikoPlayfield)drawableRuleset.Playfield;
+ playfield.ClassicHitTargetPosition.Value = true;
}
public void Update(Playfield playfield)
{
+ Debug.Assert(drawableTaikoRuleset != null);
+
// Classic taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened.
const float scroll_rate = 10;
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs
index 873aa7f992..84aa5e6bba 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Taiko.Mods
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs
index 564d023c5a..99a064d35f 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.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.
-#nullable disable
-
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs
index 01f1632ae2..89581c57bd 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs
@@ -1,14 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModDoubleTime : ModDoubleTime
{
- public override double ScoreMultiplier => 1.12;
+ public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs
index a37e1c6f5c..ad6fdf59e2 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.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.
-#nullable disable
-
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
index 2f9cccfe86..8872de4d7a 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
@@ -1,9 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Bindables;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Layout;
using osu.Game.Configuration;
@@ -17,7 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModFlashlight : ModFlashlight
{
- public override double ScoreMultiplier => 1.12;
+ public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
@@ -38,9 +37,9 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override float DefaultFlashlightSize => 250;
- protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield);
+ protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield.AsNonNull());
- private TaikoPlayfield playfield;
+ private TaikoPlayfield? playfield;
public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
@@ -50,7 +49,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
private class TaikoFlashlight : Flashlight
{
- private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize);
+ private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo);
private readonly TaikoPlayfield taikoPlayfield;
public TaikoFlashlight(TaikoModFlashlight modFlashlight, TaikoPlayfield taikoPlayfield)
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs
index ddfc2d1174..68d6305fbf 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Taiko.Mods
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs
index 7fcd925c04..ba41175461 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.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.
-#nullable disable
-
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
@@ -10,7 +8,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModHardRock : ModHardRock
{
- public override double ScoreMultiplier => 1.06;
+ public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
///
/// Multiplier factor added to the scrolling speed.
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs
index e065bb43fd..dab2279351 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs
@@ -1,8 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
+using System.Diagnostics;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@@ -18,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
public class TaikoModHidden : ModHidden, IApplicableToDrawableRuleset
{
public override string Description => @"Beats fade out before you hit them!";
- public override double ScoreMultiplier => 1.06;
+ public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
///
/// How far away from the hit target should hitobjects start to fade out.
@@ -32,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
///
private const float fade_out_duration = 0.375f;
- private DrawableTaikoRuleset drawableRuleset;
+ private DrawableTaikoRuleset? drawableRuleset;
public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
@@ -46,6 +45,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
+ Debug.Assert(drawableRuleset != null);
+
switch (hitObject)
{
case DrawableDrumRollTick:
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModMuted.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModMuted.cs
index 874e15406d..0f1e0b2885 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModMuted.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModMuted.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Objects;
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs
index f2a57ecf88..7cb14635ff 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Objects;
@@ -10,6 +8,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModNightcore : ModNightcore
{
- public override double ScoreMultiplier => 1.12;
+ public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs
index 57ecf0224f..bf1006f1aa 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Taiko.Mods
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs
index c65dba243b..b107b14a03 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Taiko.Mods
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs
index f58f59aaf2..307a37bf2e 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.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.
-#nullable disable
-
using System;
using System.Linq;
using osu.Framework.Utils;
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs
index a3a644ab99..7be70d9ac3 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Taiko.Mods
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs
index 037e376ad2..7a0f6c7cd1 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.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.
-#nullable disable
-
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Taiko.Mods
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs
index c9cba59760..3cb337c41d 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.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.
-#nullable disable
-
using System;
using System.Linq;
using osu.Game.Beatmaps;
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs
index f18d8f0537..101f70b97a 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs
@@ -10,8 +10,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
-using osu.Game.Rulesets.Taiko.Objects;
-using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Skinning;
using osuTK;
@@ -22,13 +20,14 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
///
internal class LegacyInputDrum : Container
{
+ private Container content;
private LegacyHalfDrum left;
private LegacyHalfDrum right;
- private Container content;
public LegacyInputDrum()
{
- RelativeSizeAxes = Axes.Both;
+ RelativeSizeAxes = Axes.Y;
+ AutoSizeAxes = Axes.X;
}
[BackgroundDependencyLoader]
@@ -114,9 +113,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
public readonly Sprite Rim;
public readonly Sprite Centre;
- [Resolved]
- private DrumSampleTriggerSource sampleTriggerSource { get; set; }
-
public LegacyHalfDrum(bool flipped)
{
Masking = true;
@@ -151,12 +147,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
if (e.Action == CentreAction)
{
target = Centre;
- sampleTriggerSource.Play(HitType.Centre);
}
else if (e.Action == RimAction)
{
target = Rim;
- sampleTriggerSource.Play(HitType.Rim);
}
if (target != null)
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs
index 888271f32d..992316ca53 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.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.
-#nullable disable
-
using System;
using System.Collections.Generic;
using osu.Framework.Audio.Sample;
@@ -24,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
hasExplosion = new Lazy(() => GetTexture(getHitName(TaikoSkinComponents.TaikoExplosionGreat)) != null);
}
- public override Drawable GetDrawableComponent(ISkinComponent component)
+ public override Drawable? GetDrawableComponent(ISkinComponent component)
{
if (component is GameplaySkinComponent)
{
@@ -151,7 +149,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
throw new ArgumentOutOfRangeException(nameof(component), $"Invalid component type: {component}");
}
- public override ISample GetSample(ISampleInfo sampleInfo)
+ public override ISample? GetSample(ISampleInfo sampleInfo)
{
if (sampleInfo is HitSampleInfo hitSampleInfo)
return base.GetSample(new LegacyTaikoSampleInfo(hitSampleInfo));
@@ -173,9 +171,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
{
foreach (string name in base.LookupNames)
yield return name.Insert(name.LastIndexOf('/') + 1, "taiko-");
-
- foreach (string name in base.LookupNames)
- yield return name;
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs
index 1eab133725..d36fe5d496 100644
--- a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs
@@ -4,11 +4,13 @@
#nullable disable
using System.ComponentModel;
+using osu.Framework.Allocation;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Taiko
{
+ [Cached] // Used for touch input, see DrumTouchInputArea.
public class TaikoInputManager : RulesetInputManager
{
public TaikoInputManager(RulesetInfo ruleset)
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
index b4ea158320..58e703b8be 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
@@ -8,18 +8,18 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Taiko.Objects;
-using osu.Game.Rulesets.UI;
-using osu.Game.Rulesets.Taiko.Replays;
using osu.Framework.Input;
+using osu.Game.Beatmaps;
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.Objects.Drawables;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.Replays;
using osu.Game.Rulesets.Timing;
+using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Scoring;
using osu.Game.Skinning;
@@ -56,6 +56,8 @@ namespace osu.Game.Rulesets.Taiko.UI
RelativeSizeAxes = Axes.X,
Depth = float.MaxValue
});
+
+ KeyBindingInputManager.Add(new DrumTouchInputArea());
}
protected override void UpdateAfterChildren()
diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs
new file mode 100644
index 0000000000..b65e2af3d8
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs
@@ -0,0 +1,59 @@
+// 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.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.Taiko.UI
+{
+ internal class DrumSamplePlayer : CompositeDrawable, IKeyBindingHandler
+ {
+ private readonly DrumSampleTriggerSource leftRimSampleTriggerSource;
+ private readonly DrumSampleTriggerSource leftCentreSampleTriggerSource;
+ private readonly DrumSampleTriggerSource rightCentreSampleTriggerSource;
+ private readonly DrumSampleTriggerSource rightRimSampleTriggerSource;
+
+ public DrumSamplePlayer(HitObjectContainer hitObjectContainer)
+ {
+ InternalChildren = new Drawable[]
+ {
+ leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer),
+ leftCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer),
+ rightCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer),
+ rightRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer),
+ };
+ }
+
+ public bool OnPressed(KeyBindingPressEvent e)
+ {
+ switch (e.Action)
+ {
+ case TaikoAction.LeftRim:
+ leftRimSampleTriggerSource.Play(HitType.Rim);
+ break;
+
+ case TaikoAction.LeftCentre:
+ leftCentreSampleTriggerSource.Play(HitType.Centre);
+ break;
+
+ case TaikoAction.RightCentre:
+ rightCentreSampleTriggerSource.Play(HitType.Centre);
+ break;
+
+ case TaikoAction.RightRim:
+ rightRimSampleTriggerSource.Play(HitType.Rim);
+ break;
+ }
+
+ return false;
+ }
+
+ public void OnReleased(KeyBindingReleaseEvent e)
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs
new file mode 100644
index 0000000000..a7d9bd18c5
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs
@@ -0,0 +1,243 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Game.Graphics;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Taiko.UI
+{
+ ///
+ /// An overlay that captures and displays osu!taiko mouse and touch input.
+ ///
+ public class DrumTouchInputArea : VisibilityContainer
+ {
+ // visibility state affects our child. we always want to handle input.
+ public override bool PropagatePositionalInputSubTree => true;
+ public override bool PropagateNonPositionalInputSubTree => true;
+
+ private KeyBindingContainer keyBindingContainer = null!;
+
+ private readonly Dictionary
public const float DEFAULT_HEIGHT = 200;
+ ///
+ /// Whether the hit target should be nudged further towards the left area, matching the stable "classic" position.
+ ///
+ public Bindable ClassicHitTargetPosition = new BindableBool();
+
private Container hitExplosionContainer;
private Container kiaiExplosionContainer;
private JudgementContainer judgementContainer;
@@ -45,8 +51,8 @@ namespace osu.Game.Rulesets.Taiko.UI
private readonly IDictionary explosionPools = new Dictionary();
private ProxyContainer topLevelHitContainer;
+ private InputDrum inputDrum;
private Container rightArea;
- private Container leftArea;
///
/// is purposefully not called on this to prevent i.e. being able to interact
@@ -54,14 +60,43 @@ namespace osu.Game.Rulesets.Taiko.UI
///
private BarLinePlayfield barLinePlayfield;
- private Container hitTargetOffsetContent;
+ private Container playfieldContent;
+ private Container playfieldOverlay;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
+ inputDrum = new InputDrum
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ AutoSizeAxes = Axes.X,
+ RelativeSizeAxes = Axes.Y,
+ };
+
InternalChildren = new[]
{
new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundRight), _ => new PlayfieldBackgroundRight()),
+ new Container
+ {
+ Name = "Left overlay",
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fit,
+ BorderColour = colours.Gray0,
+ Children = new[]
+ {
+ new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()),
+ inputDrum.CreateProxy(),
+ }
+ },
+ mascot = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Mascot), _ => Empty())
+ {
+ Origin = Anchor.BottomLeft,
+ Anchor = Anchor.TopLeft,
+ RelativePositionAxes = Axes.Y,
+ RelativeSizeAxes = Axes.None,
+ Y = 0.2f
+ },
rightArea = new Container
{
Name = "Right area",
@@ -71,7 +106,7 @@ namespace osu.Game.Rulesets.Taiko.UI
{
new Container
{
- Name = "Masked elements before hit objects",
+ Name = "Elements before hit objects",
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Children = new[]
@@ -86,22 +121,28 @@ namespace osu.Game.Rulesets.Taiko.UI
}
}
},
- hitTargetOffsetContent = new Container
+ new Container
{
+ Name = "Masked hit objects content",
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Child = playfieldContent = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ barLinePlayfield = new BarLinePlayfield(),
+ HitObjectContainer,
+ }
+ }
+ },
+ playfieldOverlay = new Container
+ {
+ Name = "Elements after hit objects",
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
- barLinePlayfield = new BarLinePlayfield(),
- new Container
- {
- Name = "Hit objects",
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- HitObjectContainer,
- drumRollHitContainer = new DrumRollHitContainer()
- }
- },
+ drumRollHitContainer = new DrumRollHitContainer(),
kiaiExplosionContainer = new Container
{
Name = "Kiai hit explosions",
@@ -117,36 +158,16 @@ namespace osu.Game.Rulesets.Taiko.UI
},
}
},
- leftArea = new Container
- {
- Name = "Left overlay",
- RelativeSizeAxes = Axes.Both,
- FillMode = FillMode.Fit,
- BorderColour = colours.Gray0,
- Children = new Drawable[]
- {
- new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()),
- new InputDrum(HitObjectContainer)
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- },
- }
- },
- mascot = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Mascot), _ => Empty())
- {
- Origin = Anchor.BottomLeft,
- Anchor = Anchor.TopLeft,
- RelativePositionAxes = Axes.Y,
- RelativeSizeAxes = Axes.None,
- Y = 0.2f
- },
topLevelHitContainer = new ProxyContainer
{
Name = "Top level hit objects",
RelativeSizeAxes = Axes.Both,
},
drumRollHitContainer.CreateProxy(),
+ new DrumSamplePlayer(HitObjectContainer),
+ // this is added at the end of the hierarchy to receive input before taiko objects.
+ // but is proxied below everything to not cover visual effects such as hit explosions.
+ inputDrum,
};
RegisterPool(50);
@@ -193,8 +214,9 @@ namespace osu.Game.Rulesets.Taiko.UI
// Padding is required to be updated for elements which are based on "absolute" X sized elements.
// This is basically allowing for correct alignment as relative pieces move around them.
- rightArea.Padding = new MarginPadding { Left = leftArea.DrawWidth };
- hitTargetOffsetContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 };
+ rightArea.Padding = new MarginPadding { Left = inputDrum.Width };
+ playfieldContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 };
+ playfieldOverlay.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 };
mascot.Scale = new Vector2(DrawHeight / DEFAULT_HEIGHT);
}
diff --git a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs
new file mode 100644
index 0000000000..f14288e7ba
--- /dev/null
+++ b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs
@@ -0,0 +1,100 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Extensions;
+using osu.Framework.Platform;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Database;
+using osu.Game.Rulesets;
+using osu.Game.Tests.Resources;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Beatmaps
+{
+ [HeadlessTest]
+ public class WorkingBeatmapManagerTest : OsuTestScene
+ {
+ private BeatmapManager beatmaps = null!;
+
+ private BeatmapSetInfo importedSet = null!;
+
+ [BackgroundDependencyLoader]
+ private void load(GameHost host, AudioManager audio, RulesetStore rulesets)
+ {
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
+ }
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("import beatmap", () =>
+ {
+ beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
+ importedSet = beatmaps.GetAllUsableBeatmapSets().First();
+ });
+ }
+
+ [Test]
+ public void TestGetWorkingBeatmap() => AddStep("run test", () =>
+ {
+ Assert.That(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()), Is.Not.Null);
+ });
+
+ [Test]
+ public void TestCachedRetrievalNoFiles() => AddStep("run test", () =>
+ {
+ var beatmap = importedSet.Beatmaps.First();
+
+ Assert.That(beatmap.BeatmapSet?.Files, Is.Empty);
+
+ var first = beatmaps.GetWorkingBeatmap(beatmap);
+ var second = beatmaps.GetWorkingBeatmap(beatmap);
+
+ Assert.That(first, Is.SameAs(second));
+ Assert.That(first.BeatmapInfo.BeatmapSet?.Files, Has.Count.GreaterThan(0));
+ });
+
+ [Test]
+ public void TestCachedRetrievalWithFiles() => AddStep("run test", () =>
+ {
+ var beatmap = Realm.Run(r => r.Find(importedSet.Beatmaps.First().ID).Detach());
+
+ Assert.That(beatmap.BeatmapSet?.Files, Has.Count.GreaterThan(0));
+
+ var first = beatmaps.GetWorkingBeatmap(beatmap);
+ var second = beatmaps.GetWorkingBeatmap(beatmap);
+
+ Assert.That(first, Is.SameAs(second));
+ Assert.That(first.BeatmapInfo.BeatmapSet?.Files, Has.Count.GreaterThan(0));
+ });
+
+ [Test]
+ public void TestForcedRefetchRetrievalNoFiles() => AddStep("run test", () =>
+ {
+ var beatmap = importedSet.Beatmaps.First();
+
+ Assert.That(beatmap.BeatmapSet?.Files, Is.Empty);
+
+ var first = beatmaps.GetWorkingBeatmap(beatmap);
+ var second = beatmaps.GetWorkingBeatmap(beatmap, true);
+ Assert.That(first, Is.Not.SameAs(second));
+ });
+
+ [Test]
+ public void TestForcedRefetchRetrievalWithFiles() => AddStep("run test", () =>
+ {
+ var beatmap = Realm.Run(r => r.Find(importedSet.Beatmaps.First().ID).Detach());
+
+ Assert.That(beatmap.BeatmapSet?.Files, Has.Count.GreaterThan(0));
+
+ var first = beatmaps.GetWorkingBeatmap(beatmap);
+ var second = beatmaps.GetWorkingBeatmap(beatmap, true);
+ Assert.That(first, Is.Not.SameAs(second));
+ });
+ }
+}
diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
index 9a8f29647d..604b87dc4c 100644
--- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
+++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
@@ -5,12 +5,15 @@
using System;
using System.IO;
+using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Extensions;
using osu.Framework.Platform;
using osu.Framework.Testing;
+using osu.Game.Collections;
+using osu.Game.Database;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Collections.IO
@@ -29,7 +32,11 @@ namespace osu.Game.Tests.Collections.IO
await importCollectionsFromStream(osu, new MemoryStream());
- Assert.That(osu.CollectionManager.Collections.Count, Is.Zero);
+ osu.Realm.Run(realm =>
+ {
+ var collections = realm.All().ToList();
+ Assert.That(collections.Count, Is.Zero);
+ });
}
finally
{
@@ -49,18 +56,22 @@ namespace osu.Game.Tests.Collections.IO
await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db"));
- Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
+ osu.Realm.Run(realm =>
+ {
+ var collections = realm.All().ToList();
+ Assert.That(collections.Count, Is.EqualTo(2));
- // Even with no beatmaps imported, collections are tracking the hashes and will continue to.
- // In the future this whole mechanism will be replaced with having the collections in realm,
- // but until that happens it makes rough sense that we want to track not-yet-imported beatmaps
- // and have them associate with collections if/when they become available.
+ // Even with no beatmaps imported, collections are tracking the hashes and will continue to.
+ // In the future this whole mechanism will be replaced with having the collections in realm,
+ // but until that happens it makes rough sense that we want to track not-yet-imported beatmaps
+ // and have them associate with collections if/when they become available.
- Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First"));
- Assert.That(osu.CollectionManager.Collections[0].BeatmapHashes.Count, Is.EqualTo(1));
+ Assert.That(collections[0].Name, Is.EqualTo("First"));
+ Assert.That(collections[0].BeatmapMD5Hashes.Count, Is.EqualTo(1));
- Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Second"));
- Assert.That(osu.CollectionManager.Collections[1].BeatmapHashes.Count, Is.EqualTo(12));
+ Assert.That(collections[1].Name, Is.EqualTo("Second"));
+ Assert.That(collections[1].BeatmapMD5Hashes.Count, Is.EqualTo(12));
+ });
}
finally
{
@@ -80,13 +91,18 @@ namespace osu.Game.Tests.Collections.IO
await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db"));
- Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
+ osu.Realm.Run(realm =>
+ {
+ var collections = realm.All().ToList();
- Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First"));
- Assert.That(osu.CollectionManager.Collections[0].BeatmapHashes.Count, Is.EqualTo(1));
+ Assert.That(collections.Count, Is.EqualTo(2));
- Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Second"));
- Assert.That(osu.CollectionManager.Collections[1].BeatmapHashes.Count, Is.EqualTo(12));
+ Assert.That(collections[0].Name, Is.EqualTo("First"));
+ Assert.That(collections[0].BeatmapMD5Hashes.Count, Is.EqualTo(1));
+
+ Assert.That(collections[1].Name, Is.EqualTo("Second"));
+ Assert.That(collections[1].BeatmapMD5Hashes.Count, Is.EqualTo(12));
+ });
}
finally
{
@@ -123,7 +139,11 @@ namespace osu.Game.Tests.Collections.IO
}
Assert.That(exceptionThrown, Is.False);
- Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(0));
+ osu.Realm.Run(realm =>
+ {
+ var collections = realm.All().ToList();
+ Assert.That(collections.Count, Is.EqualTo(0));
+ });
}
finally
{
@@ -148,12 +168,18 @@ namespace osu.Game.Tests.Collections.IO
await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db"));
- // Move first beatmap from second collection into the first.
- osu.CollectionManager.Collections[0].BeatmapHashes.Add(osu.CollectionManager.Collections[1].BeatmapHashes[0]);
- osu.CollectionManager.Collections[1].BeatmapHashes.RemoveAt(0);
+ // ReSharper disable once MethodHasAsyncOverload
+ osu.Realm.Write(realm =>
+ {
+ var collections = realm.All().ToList();
- // Rename the second collecction.
- osu.CollectionManager.Collections[1].Name.Value = "Another";
+ // Move first beatmap from second collection into the first.
+ collections[0].BeatmapMD5Hashes.Add(collections[1].BeatmapMD5Hashes[0]);
+ collections[1].BeatmapMD5Hashes.RemoveAt(0);
+
+ // Rename the second collecction.
+ collections[1].Name = "Another";
+ });
}
finally
{
@@ -168,13 +194,17 @@ namespace osu.Game.Tests.Collections.IO
{
var osu = LoadOsuIntoHost(host, true);
- Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
+ osu.Realm.Run(realm =>
+ {
+ var collections = realm.All().ToList();
+ Assert.That(collections.Count, Is.EqualTo(2));
- Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First"));
- Assert.That(osu.CollectionManager.Collections[0].BeatmapHashes.Count, Is.EqualTo(2));
+ Assert.That(collections[0].Name, Is.EqualTo("First"));
+ Assert.That(collections[0].BeatmapMD5Hashes.Count, Is.EqualTo(2));
- Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Another"));
- Assert.That(osu.CollectionManager.Collections[1].BeatmapHashes.Count, Is.EqualTo(11));
+ Assert.That(collections[1].Name, Is.EqualTo("Another"));
+ Assert.That(collections[1].BeatmapMD5Hashes.Count, Is.EqualTo(11));
+ });
}
finally
{
@@ -187,7 +217,7 @@ namespace osu.Game.Tests.Collections.IO
{
// intentionally spin this up on a separate task to avoid disposal deadlocks.
// see https://github.com/EventStore/EventStore/issues/1179
- await Task.Factory.StartNew(() => osu.CollectionManager.Import(stream).WaitSafely(), TaskCreationOptions.LongRunning);
+ await Task.Factory.StartNew(() => new LegacyCollectionImporter(osu.Realm).Import(stream).WaitSafely(), TaskCreationOptions.LongRunning);
}
}
}
diff --git a/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs b/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs
new file mode 100644
index 0000000000..4012e3f851
--- /dev/null
+++ b/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs
@@ -0,0 +1,132 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Beatmaps.IO;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Database
+{
+ [HeadlessTest]
+ public class BackgroundBeatmapProcessorTests : OsuTestScene, ILocalUserPlayInfo
+ {
+ public IBindable IsPlaying => isPlaying;
+
+ private readonly Bindable isPlaying = new Bindable();
+
+ private BeatmapSetInfo importedSet = null!;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuGameBase osu)
+ {
+ importedSet = BeatmapImportHelper.LoadQuickOszIntoOsu(osu).GetResultSafely();
+ }
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("Set not playing", () => isPlaying.Value = false);
+ }
+
+ [Test]
+ public void TestDifficultyProcessing()
+ {
+ AddAssert("Difficulty is initially set", () =>
+ {
+ return Realm.Run(r =>
+ {
+ var beatmapSetInfo = r.Find(importedSet.ID);
+ return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0);
+ });
+ });
+
+ AddStep("Reset difficulty", () =>
+ {
+ Realm.Write(r =>
+ {
+ var beatmapSetInfo = r.Find(importedSet.ID);
+ foreach (var b in beatmapSetInfo.Beatmaps)
+ b.StarRating = -1;
+ });
+ });
+
+ AddStep("Run background processor", () =>
+ {
+ Add(new TestBackgroundBeatmapProcessor());
+ });
+
+ AddUntilStep("wait for difficulties repopulated", () =>
+ {
+ return Realm.Run(r =>
+ {
+ var beatmapSetInfo = r.Find(importedSet.ID);
+ return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0);
+ });
+ });
+ }
+
+ [Test]
+ public void TestDifficultyProcessingWhilePlaying()
+ {
+ AddAssert("Difficulty is initially set", () =>
+ {
+ return Realm.Run(r =>
+ {
+ var beatmapSetInfo = r.Find(importedSet.ID);
+ return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0);
+ });
+ });
+
+ AddStep("Set playing", () => isPlaying.Value = true);
+
+ AddStep("Reset difficulty", () =>
+ {
+ Realm.Write(r =>
+ {
+ var beatmapSetInfo = r.Find(importedSet.ID);
+ foreach (var b in beatmapSetInfo.Beatmaps)
+ b.StarRating = -1;
+ });
+ });
+
+ AddStep("Run background processor", () =>
+ {
+ Add(new TestBackgroundBeatmapProcessor());
+ });
+
+ AddWaitStep("wait some", 500);
+
+ AddAssert("Difficulty still not populated", () =>
+ {
+ return Realm.Run(r =>
+ {
+ var beatmapSetInfo = r.Find(importedSet.ID);
+ return beatmapSetInfo.Beatmaps.All(b => b.StarRating == -1);
+ });
+ });
+
+ AddStep("Set not playing", () => isPlaying.Value = false);
+
+ AddUntilStep("wait for difficulties repopulated", () =>
+ {
+ return Realm.Run(r =>
+ {
+ var beatmapSetInfo = r.Find(importedSet.ID);
+ return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0);
+ });
+ });
+ }
+
+ public class TestBackgroundBeatmapProcessor : BackgroundBeatmapProcessor
+ {
+ protected override int TimeToSleepDuringGameplay => 10;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs
index 9ee88c0670..0546d3e912 100644
--- a/osu.Game.Tests/Database/BeatmapImporterTests.cs
+++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs
@@ -142,7 +142,6 @@ namespace osu.Game.Tests.Database
{
Task.Run(async () =>
{
- // ReSharper disable once AccessToDisposedClosure
var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
Assert.NotNull(beatmapSet);
@@ -311,6 +310,7 @@ namespace osu.Game.Tests.Database
}
finally
{
+ File.Delete(temp);
Directory.Delete(extractedFolder, true);
}
});
@@ -670,6 +670,61 @@ namespace osu.Game.Tests.Database
});
}
+ [Test]
+ public void TestImportThenReimportWithNewDifficulty()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ var importer = new BeatmapImporter(storage, realm);
+ using var store = new RealmRulesetStore(realm, storage);
+
+ string? pathOriginal = TestResources.GetTestBeatmapForImport();
+
+ string pathMissingOneBeatmap = pathOriginal.Replace(".osz", "_missing_difficulty.osz");
+
+ string extractedFolder = $"{pathOriginal}_extracted";
+ Directory.CreateDirectory(extractedFolder);
+
+ try
+ {
+ using (var zip = ZipArchive.Open(pathOriginal))
+ zip.WriteToDirectory(extractedFolder);
+
+ // remove one difficulty before first import
+ new FileInfo(Directory.GetFiles(extractedFolder, "*.osu").First()).Delete();
+
+ using (var zip = ZipArchive.Create())
+ {
+ zip.AddAllFromDirectory(extractedFolder);
+ zip.SaveTo(pathMissingOneBeatmap, new ZipWriterOptions(CompressionType.Deflate));
+ }
+
+ var firstImport = await importer.Import(new ImportTask(pathMissingOneBeatmap));
+ Assert.That(firstImport, Is.Not.Null);
+
+ Assert.That(realm.Realm.All().Where(s => !s.DeletePending), Has.Count.EqualTo(1));
+ Assert.That(realm.Realm.All().First(s => !s.DeletePending).Beatmaps, Has.Count.EqualTo(11));
+
+ // Second import matches first but contains one extra .osu file.
+ var secondImport = await importer.Import(new ImportTask(pathOriginal));
+ Assert.That(secondImport, Is.Not.Null);
+
+ Assert.That(realm.Realm.All(), Has.Count.EqualTo(23));
+ Assert.That(realm.Realm.All(), Has.Count.EqualTo(2));
+
+ Assert.That(realm.Realm.All().Where(s => !s.DeletePending), Has.Count.EqualTo(1));
+ Assert.That(realm.Realm.All().First(s => !s.DeletePending).Beatmaps, Has.Count.EqualTo(12));
+
+ // check the newly "imported" beatmap is not the original.
+ Assert.That(firstImport?.ID, Is.Not.EqualTo(secondImport?.ID));
+ }
+ finally
+ {
+ Directory.Delete(extractedFolder, true);
+ }
+ });
+ }
+
[Test]
public void TestImportThenReimportAfterMissingFiles()
{
@@ -742,7 +797,7 @@ namespace osu.Game.Tests.Database
await realm.Realm.WriteAsync(() =>
{
foreach (var b in imported.Beatmaps)
- b.OnlineID = -1;
+ b.ResetOnlineInfo();
});
deleteBeatmapSet(imported, realm.Realm);
diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs
new file mode 100644
index 0000000000..b94cff2a9a
--- /dev/null
+++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs
@@ -0,0 +1,600 @@
+// 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.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Linq.Expressions;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Game.Beatmaps;
+using osu.Game.Collections;
+using osu.Game.Database;
+using osu.Game.Models;
+using osu.Game.Overlays.Notifications;
+using osu.Game.Rulesets;
+using osu.Game.Scoring;
+using osu.Game.Tests.Resources;
+using Realms;
+using SharpCompress.Archives;
+using SharpCompress.Archives.Zip;
+using SharpCompress.Common;
+using SharpCompress.Writers.Zip;
+
+namespace osu.Game.Tests.Database
+{
+ ///
+ /// Tests the flow where a beatmap is already loaded and an update is applied.
+ ///
+ [TestFixture]
+ public class BeatmapImporterUpdateTests : RealmTest
+ {
+ private const int count_beatmaps = 12;
+
+ [Test]
+ public void TestNewDifficultyAdded()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ var importer = new BeatmapImporter(storage, realm);
+ using var rulesets = new RealmRulesetStore(realm, storage);
+
+ using var __ = getBeatmapArchive(out string pathOriginal);
+ using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory =>
+ {
+ // remove one difficulty before first import
+ directory.GetFiles("*.osu").First().Delete();
+ });
+
+ var importBeforeUpdate = await importer.Import(new ImportTask(pathMissingOneBeatmap));
+
+ Assert.That(importBeforeUpdate, Is.Not.Null);
+ Debug.Assert(importBeforeUpdate != null);
+
+ realm.Run(r => r.Refresh());
+
+ checkCount(realm, 1, s => !s.DeletePending);
+ Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps - 1));
+
+ // Second import matches first but contains one extra .osu file.
+ var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginal), importBeforeUpdate.Value);
+
+ Assert.That(importAfterUpdate, Is.Not.Null);
+ Debug.Assert(importAfterUpdate != null);
+
+ realm.Run(r => r.Refresh());
+
+ checkCount(realm, count_beatmaps);
+ checkCount(realm, count_beatmaps);
+ checkCount(realm, 1);
+
+ // check the newly "imported" beatmap is not the original.
+ Assert.That(importBeforeUpdate.ID, Is.Not.EqualTo(importAfterUpdate.ID));
+
+ // Previous beatmap set has no beatmaps so will be completely purged on the spot.
+ Assert.That(importBeforeUpdate.Value.IsValid, Is.False);
+ });
+ }
+
+ ///
+ /// Regression test covering https://github.com/ppy/osu/issues/19369 (import potentially duplicating if original has no ).
+ ///
+ [Test]
+ public void TestNewDifficultyAddedNoOnlineID()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ var importer = new BeatmapImporter(storage, realm);
+ using var rulesets = new RealmRulesetStore(realm, storage);
+
+ using var __ = getBeatmapArchive(out string pathOriginal);
+ using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory =>
+ {
+ // remove one difficulty before first import
+ directory.GetFiles("*.osu").First().Delete();
+ });
+
+ var importBeforeUpdate = await importer.Import(new ImportTask(pathMissingOneBeatmap));
+
+ Assert.That(importBeforeUpdate, Is.Not.Null);
+ Debug.Assert(importBeforeUpdate != null);
+
+ // This test is the same as TestNewDifficultyAdded except for this block.
+ importBeforeUpdate.PerformWrite(s =>
+ {
+ s.OnlineID = -1;
+ foreach (var beatmap in s.Beatmaps)
+ beatmap.ResetOnlineInfo();
+ });
+
+ realm.Run(r => r.Refresh());
+
+ checkCount(realm, 1, s => !s.DeletePending);
+ Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps - 1));
+
+ // Second import matches first but contains one extra .osu file.
+ var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginal), importBeforeUpdate.Value);
+
+ Assert.That(importAfterUpdate, Is.Not.Null);
+ Debug.Assert(importAfterUpdate != null);
+
+ realm.Run(r => r.Refresh());
+
+ checkCount(realm, count_beatmaps);
+ checkCount(realm, count_beatmaps);
+ checkCount(realm, 1);
+
+ // check the newly "imported" beatmap is not the original.
+ Assert.That(importBeforeUpdate.ID, Is.Not.EqualTo(importAfterUpdate.ID));
+
+ // Previous beatmap set has no beatmaps so will be completely purged on the spot.
+ Assert.That(importBeforeUpdate.Value.IsValid, Is.False);
+ });
+ }
+
+ [Test]
+ public void TestExistingDifficultyModified()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ var importer = new BeatmapImporter(storage, realm);
+ using var rulesets = new RealmRulesetStore(realm, storage);
+
+ using var __ = getBeatmapArchive(out string pathOriginal);
+ using var _ = getBeatmapArchiveWithModifications(out string pathModified, directory =>
+ {
+ // Modify one .osu file with different content.
+ var firstOsuFile = directory.GetFiles("*.osu").First();
+
+ string existingContent = File.ReadAllText(firstOsuFile.FullName);
+
+ File.WriteAllText(firstOsuFile.FullName, existingContent + "\n# I am new content");
+ });
+
+ var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
+
+ Assert.That(importBeforeUpdate, Is.Not.Null);
+ Debug.Assert(importBeforeUpdate != null);
+
+ realm.Run(r => r.Refresh());
+
+ checkCount(realm, 1, s => !s.DeletePending);
+ Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps));
+
+ // Second import matches first but contains one extra .osu file.
+ var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathModified), importBeforeUpdate.Value);
+
+ Assert.That(importAfterUpdate, Is.Not.Null);
+ Debug.Assert(importAfterUpdate != null);
+
+ // should only contain the modified beatmap (others purged).
+ Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(1));
+ Assert.That(importAfterUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps));
+
+ realm.Run(r => r.Refresh());
+
+ checkCount(realm, count_beatmaps + 1);
+ checkCount(realm, count_beatmaps + 1);
+
+ checkCount(realm, 1, s => !s.DeletePending);
+ checkCount(realm, 1, s => s.DeletePending);
+ });
+ }
+
+ [Test]
+ public void TestExistingDifficultyRemoved()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ var importer = new BeatmapImporter(storage, realm);
+ using var rulesets = new RealmRulesetStore(realm, storage);
+
+ using var __ = getBeatmapArchive(out string pathOriginal);
+ using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory =>
+ {
+ // remove one difficulty before first import
+ directory.GetFiles("*.osu").First().Delete();
+ });
+
+ var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
+
+ Assert.That(importBeforeUpdate, Is.Not.Null);
+ Debug.Assert(importBeforeUpdate != null);
+
+ Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps));
+ Assert.That(importBeforeUpdate.Value.Beatmaps.First().OnlineID, Is.GreaterThan(-1));
+
+ // Second import matches first but contains one extra .osu file.
+ var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathMissingOneBeatmap), importBeforeUpdate.Value);
+
+ Assert.That(importAfterUpdate, Is.Not.Null);
+ Debug.Assert(importAfterUpdate != null);
+
+ realm.Run(r => r.Refresh());
+
+ checkCount(realm, count_beatmaps);
+ checkCount(realm, count_beatmaps);
+ checkCount(realm, 2);
+
+ // previous set should contain the removed beatmap still.
+ Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(1));
+ Assert.That(importBeforeUpdate.Value.Beatmaps.First().OnlineID, Is.EqualTo(-1));
+
+ // Previous beatmap set has no beatmaps so will be completely purged on the spot.
+ Assert.That(importAfterUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps - 1));
+ });
+ }
+
+ [Test]
+ public void TestUpdatedImportContainsNothing()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ var importer = new BeatmapImporter(storage, realm);
+ using var rulesets = new RealmRulesetStore(realm, storage);
+
+ using var __ = getBeatmapArchive(out string pathOriginal);
+ using var _ = getBeatmapArchiveWithModifications(out string pathEmpty, directory =>
+ {
+ foreach (var file in directory.GetFiles())
+ file.Delete();
+ });
+
+ var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
+
+ Assert.That(importBeforeUpdate, Is.Not.Null);
+ Debug.Assert(importBeforeUpdate != null);
+
+ var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathEmpty), importBeforeUpdate.Value);
+ Assert.That(importAfterUpdate, Is.Null);
+
+ realm.Run(r => r.Refresh());
+
+ checkCount(realm, 1);
+ checkCount(realm, count_beatmaps);
+ checkCount(realm, count_beatmaps);
+
+ Assert.That(importBeforeUpdate.Value.IsValid, Is.True);
+ });
+ }
+
+ [Test]
+ public void TestNoChanges()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ var importer = new BeatmapImporter(storage, realm);
+ using var rulesets = new RealmRulesetStore(realm, storage);
+
+ using var __ = getBeatmapArchive(out string pathOriginal);
+ using var _ = getBeatmapArchive(out string pathOriginalSecond);
+
+ var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
+
+ Assert.That(importBeforeUpdate, Is.Not.Null);
+ Debug.Assert(importBeforeUpdate != null);
+
+ var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginalSecond), importBeforeUpdate.Value);
+
+ Assert.That(importAfterUpdate, Is.Not.Null);
+ Debug.Assert(importAfterUpdate != null);
+
+ realm.Run(r => r.Refresh());
+
+ checkCount(realm, 1);
+ checkCount(realm, count_beatmaps);
+ checkCount(realm, count_beatmaps);
+
+ Assert.That(importBeforeUpdate.Value.Beatmaps.First().OnlineID, Is.GreaterThan(-1));
+ Assert.That(importBeforeUpdate.ID, Is.EqualTo(importAfterUpdate.ID));
+ });
+ }
+
+ [Test]
+ public void TestScoreTransferredOnUnchanged()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ var importer = new BeatmapImporter(storage, realm);
+ using var rulesets = new RealmRulesetStore(realm, storage);
+ string removedFilename = null!;
+
+ using var __ = getBeatmapArchive(out string pathOriginal);
+ using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory =>
+ {
+ // arbitrary beatmap removal
+ var fileToRemove = directory.GetFiles("*.osu").First();
+
+ removedFilename = fileToRemove.Name;
+ fileToRemove.Delete();
+ });
+
+ var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
+
+ Assert.That(importBeforeUpdate, Is.Not.Null);
+ Debug.Assert(importBeforeUpdate != null);
+
+ string scoreTargetBeatmapHash = string.Empty;
+
+ importBeforeUpdate.PerformWrite(s =>
+ {
+ // make sure not to add scores to the same beatmap that is removed in the update.
+ var beatmapInfo = s.Beatmaps.First(b => b.File?.Filename != removedFilename);
+
+ scoreTargetBeatmapHash = beatmapInfo.Hash;
+ s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All().First(), new RealmUser()));
+ });
+
+ realm.Run(r => r.Refresh());
+
+ checkCount(realm, 1);
+
+ var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathMissingOneBeatmap), importBeforeUpdate.Value);
+
+ Assert.That(importAfterUpdate, Is.Not.Null);
+ Debug.Assert(importAfterUpdate != null);
+
+ realm.Run(r => r.Refresh());
+
+ checkCount(realm, count_beatmaps);
+ checkCount(realm, count_beatmaps);
+ checkCount(realm, 2);
+
+ // score is transferred across to the new set
+ checkCount(realm, 1);
+ Assert.That(importAfterUpdate.Value.Beatmaps.First(b => b.Hash == scoreTargetBeatmapHash).Scores, Has.Count.EqualTo(1));
+ });
+ }
+
+ [Test]
+ public void TestScoreLostOnModification()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ var importer = new BeatmapImporter(storage, realm);
+ using var rulesets = new RealmRulesetStore(realm, storage);
+
+ using var __ = getBeatmapArchive(out string pathOriginal);
+
+ var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
+
+ Assert.That(importBeforeUpdate, Is.Not.Null);
+ Debug.Assert(importBeforeUpdate != null);
+
+ string? scoreTargetFilename = string.Empty;
+
+ importBeforeUpdate.PerformWrite(s =>
+ {
+ var beatmapInfo = s.Beatmaps.Last();
+ scoreTargetFilename = beatmapInfo.File?.Filename;
+ s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All().First(), new RealmUser()));
+ });
+
+ realm.Run(r => r.Refresh());
+
+ checkCount(realm, 1);
+
+ using var _ = getBeatmapArchiveWithModifications(out string pathModified, directory =>
+ {
+ // Modify one .osu file with different content.
+ var firstOsuFile = directory.GetFiles(scoreTargetFilename).First();
+
+ string existingContent = File.ReadAllText(firstOsuFile.FullName);
+
+ File.WriteAllText(firstOsuFile.FullName, existingContent + "\n# I am new content");
+ });
+
+ var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathModified), importBeforeUpdate.Value);
+
+ Assert.That(importAfterUpdate, Is.Not.Null);
+ Debug.Assert(importAfterUpdate != null);
+
+ realm.Run(r => r.Refresh());
+
+ checkCount(realm, count_beatmaps + 1);
+ checkCount(realm, count_beatmaps + 1);
+ checkCount(realm, 2);
+
+ // score is not transferred due to modifications.
+ checkCount(realm, 1);
+ Assert.That(importBeforeUpdate.Value.Beatmaps.AsEnumerable().First(b => b.File?.Filename == scoreTargetFilename).Scores, Has.Count.EqualTo(1));
+ Assert.That(importAfterUpdate.Value.Beatmaps.AsEnumerable().First(b => b.File?.Filename == scoreTargetFilename).Scores, Has.Count.EqualTo(0));
+ });
+ }
+
+ [Test]
+ public void TestMetadataTransferred()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ var importer = new BeatmapImporter(storage, realm);
+ using var rulesets = new RealmRulesetStore(realm, storage);
+
+ using var __ = getBeatmapArchive(out string pathOriginal);
+ using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory =>
+ {
+ // arbitrary beatmap removal
+ directory.GetFiles("*.osu").First().Delete();
+ });
+
+ var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
+
+ Assert.That(importBeforeUpdate, Is.Not.Null);
+ Debug.Assert(importBeforeUpdate != null);
+
+ var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathMissingOneBeatmap), importBeforeUpdate.Value);
+
+ Assert.That(importAfterUpdate, Is.Not.Null);
+ Debug.Assert(importAfterUpdate != null);
+
+ Assert.That(importBeforeUpdate.ID, Is.Not.EqualTo(importAfterUpdate.ID));
+ Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(importAfterUpdate.Value.DateAdded));
+ });
+ }
+
+ ///
+ /// If all difficulties in the original beatmap set are in a collection, presume the user also wants new difficulties added.
+ ///
+ [TestCase(false)]
+ [TestCase(true)]
+ public void TestCollectionTransferNewBeatmap(bool allOriginalBeatmapsInCollection)
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ var importer = new BeatmapImporter(storage, realm);
+ using var rulesets = new RealmRulesetStore(realm, storage);
+
+ using var __ = getBeatmapArchive(out string pathOriginal);
+ using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory =>
+ {
+ // remove one difficulty before first import
+ directory.GetFiles("*.osu").First().Delete();
+ });
+
+ var importBeforeUpdate = await importer.Import(new ImportTask(pathMissingOneBeatmap));
+
+ Assert.That(importBeforeUpdate, Is.Not.Null);
+ Debug.Assert(importBeforeUpdate != null);
+
+ int beatmapsToAddToCollection = 0;
+
+ importBeforeUpdate.PerformWrite(s =>
+ {
+ var beatmapCollection = s.Realm.Add(new BeatmapCollection("test collection"));
+ beatmapsToAddToCollection = s.Beatmaps.Count - (allOriginalBeatmapsInCollection ? 0 : 1);
+
+ for (int i = 0; i < beatmapsToAddToCollection; i++)
+ beatmapCollection.BeatmapMD5Hashes.Add(s.Beatmaps[i].MD5Hash);
+ });
+
+ // Second import matches first but contains one extra .osu file.
+ var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginal), importBeforeUpdate.Value);
+
+ Assert.That(importAfterUpdate, Is.Not.Null);
+ Debug.Assert(importAfterUpdate != null);
+
+ importAfterUpdate.PerformRead(updated =>
+ {
+ updated.Realm.Refresh();
+
+ string[] hashes = updated.Realm.All().Single().BeatmapMD5Hashes.ToArray();
+
+ if (allOriginalBeatmapsInCollection)
+ {
+ Assert.That(updated.Beatmaps.Count, Is.EqualTo(beatmapsToAddToCollection + 1));
+ Assert.That(hashes, Has.Length.EqualTo(updated.Beatmaps.Count));
+ }
+ else
+ {
+ // Collection contains one less than the original beatmap, and two less after update (new difficulty included).
+ Assert.That(updated.Beatmaps.Count, Is.EqualTo(beatmapsToAddToCollection + 2));
+ Assert.That(hashes, Has.Length.EqualTo(beatmapsToAddToCollection));
+ }
+ });
+ });
+ }
+
+ ///
+ /// If a difficulty in the original beatmap set is modified, the updated version should remain in any collections it was in.
+ ///
+ [Test]
+ public void TestCollectionTransferModifiedBeatmap()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ var importer = new BeatmapImporter(storage, realm);
+ using var rulesets = new RealmRulesetStore(realm, storage);
+
+ using var __ = getBeatmapArchive(out string pathOriginal);
+ using var _ = getBeatmapArchiveWithModifications(out string pathModified, directory =>
+ {
+ // Modify one .osu file with different content.
+ var firstOsuFile = directory.GetFiles("*[Hard]*.osu").First();
+
+ string existingContent = File.ReadAllText(firstOsuFile.FullName);
+
+ File.WriteAllText(firstOsuFile.FullName, existingContent + "\n# I am new content");
+ });
+
+ var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
+
+ Assert.That(importBeforeUpdate, Is.Not.Null);
+ Debug.Assert(importBeforeUpdate != null);
+
+ string originalHash = string.Empty;
+
+ importBeforeUpdate.PerformWrite(s =>
+ {
+ var beatmapCollection = s.Realm.Add(new BeatmapCollection("test collection"));
+ originalHash = s.Beatmaps.Single(b => b.DifficultyName == "Hard").MD5Hash;
+
+ beatmapCollection.BeatmapMD5Hashes.Add(originalHash);
+ });
+
+ // Second import matches first but contains a modified .osu file.
+ var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathModified), importBeforeUpdate.Value);
+
+ Assert.That(importAfterUpdate, Is.Not.Null);
+ Debug.Assert(importAfterUpdate != null);
+
+ importAfterUpdate.PerformRead(updated =>
+ {
+ updated.Realm.Refresh();
+
+ string[] hashes = updated.Realm.All().Single().BeatmapMD5Hashes.ToArray();
+ string updatedHash = updated.Beatmaps.Single(b => b.DifficultyName == "Hard").MD5Hash;
+
+ Assert.That(hashes, Has.Length.EqualTo(1));
+ Assert.That(hashes.First(), Is.EqualTo(updatedHash));
+
+ Assert.That(updatedHash, Is.Not.EqualTo(originalHash));
+ });
+ });
+ }
+
+ private static void checkCount(RealmAccess realm, int expected, Expression>? condition = null) where T : RealmObject
+ {
+ var query = realm.Realm.All();
+
+ if (condition != null)
+ query = query.Where(condition);
+
+ Assert.That(query, Has.Count.EqualTo(expected));
+ }
+
+ private static IDisposable getBeatmapArchiveWithModifications(out string path, Action applyModifications)
+ {
+ var cleanup = getBeatmapArchive(out path);
+
+ string extractedFolder = $"{path}_extracted";
+ Directory.CreateDirectory(extractedFolder);
+
+ using (var zip = ZipArchive.Open(path))
+ zip.WriteToDirectory(extractedFolder);
+
+ applyModifications(new DirectoryInfo(extractedFolder));
+
+ File.Delete(path);
+
+ using (var zip = ZipArchive.Create())
+ {
+ zip.AddAllFromDirectory(extractedFolder);
+ zip.SaveTo(path, new ZipWriterOptions(CompressionType.Deflate));
+ }
+
+ Directory.Delete(extractedFolder, true);
+
+ return cleanup;
+ }
+
+ private static IDisposable getBeatmapArchive(out string path, bool quick = true)
+ {
+ string beatmapPath = TestResources.GetTestBeatmapForImport(quick);
+
+ path = beatmapPath;
+
+ return new InvokeOnDisposal(() => File.Delete(beatmapPath));
+ }
+ }
+}
diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs
index 3615cebe6a..d853e75db0 100644
--- a/osu.Game.Tests/Database/RealmLiveTests.cs
+++ b/osu.Game.Tests/Database/RealmLiveTests.cs
@@ -32,31 +32,29 @@ namespace osu.Game.Tests.Database
[Test]
public void TestAccessAfterStorageMigrate()
{
- RunTestWithRealm((realm, storage) =>
+ using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
{
- var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
-
- Live? liveBeatmap = null;
-
- realm.Run(r =>
+ RunTestWithRealm((realm, storage) =>
{
- r.Write(_ => r.Add(beatmap));
+ var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
- liveBeatmap = beatmap.ToLive(realm);
- });
+ Live? liveBeatmap = null;
+
+ realm.Run(r =>
+ {
+ r.Write(_ => r.Add(beatmap));
+
+ liveBeatmap = beatmap.ToLive(realm);
+ });
- using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
- {
migratedStorage.DeleteDirectory(string.Empty);
using (realm.BlockAllOperations("testing"))
- {
storage.Migrate(migratedStorage);
- }
Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden));
- }
- });
+ });
+ }
}
[Test]
@@ -341,14 +339,12 @@ namespace osu.Game.Tests.Database
liveBeatmap.PerformRead(resolved =>
{
// retrieval causes an implicit refresh. even changes that aren't related to the retrieval are fired at this point.
- // ReSharper disable once AccessToDisposedClosure
Assert.AreEqual(2, outerRealm.All().Count());
Assert.AreEqual(1, changesTriggered);
// can access properties without a crash.
Assert.IsFalse(resolved.Hidden);
- // ReSharper disable once AccessToDisposedClosure
outerRealm.Write(r =>
{
// can use with the main context.
diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs
index d6b3c1ff44..1b1878942b 100644
--- a/osu.Game.Tests/Database/RealmTest.cs
+++ b/osu.Game.Tests/Database/RealmTest.cs
@@ -4,11 +4,11 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
+using JetBrains.Annotations;
using NUnit.Framework;
using osu.Framework.Extensions;
using osu.Framework.Logging;
using osu.Framework.Platform;
-using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.IO;
@@ -20,22 +20,15 @@ namespace osu.Game.Tests.Database
[TestFixture]
public abstract class RealmTest
{
- private static readonly TemporaryNativeStorage storage;
-
- static RealmTest()
- {
- storage = new TemporaryNativeStorage("realm-test");
- storage.DeleteDirectory(string.Empty);
- }
-
- protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "")
+ protected void RunTestWithRealm([InstantHandle] Action testAction, [CallerMemberName] string caller = "")
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
{
host.Run(new RealmTestGame(() =>
{
- // ReSharper disable once AccessToDisposedClosure
- var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller));
+ var defaultStorage = host.Storage;
+
+ var testStorage = new OsuStorage(host, defaultStorage);
using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME))
{
@@ -58,7 +51,7 @@ namespace osu.Game.Tests.Database
{
host.Run(new RealmTestGame(async () =>
{
- var testStorage = storage.GetStorageForDirectory(caller);
+ var testStorage = host.Storage;
using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME))
{
@@ -116,7 +109,7 @@ namespace osu.Game.Tests.Database
private class RealmTestGame : Framework.Game
{
- public RealmTestGame(Func work)
+ public RealmTestGame([InstantHandle] Func work)
{
// ReSharper disable once AsyncVoidLambda
Scheduler.Add(async () =>
@@ -126,7 +119,7 @@ namespace osu.Game.Tests.Database
});
}
- public RealmTestGame(Action work)
+ public RealmTestGame([InstantHandle] Action work)
{
Scheduler.Add(() =>
{
diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs
index 0395ae9d99..5f403f9487 100644
--- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs
@@ -41,8 +41,6 @@ namespace osu.Game.Tests.Gameplay
AddStep("create container", () =>
{
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
- working.LoadTrack();
-
Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0);
});
@@ -58,8 +56,6 @@ namespace osu.Game.Tests.Gameplay
AddStep("create container", () =>
{
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
- working.LoadTrack();
-
Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0);
});
@@ -102,8 +98,6 @@ namespace osu.Game.Tests.Gameplay
AddStep("create container", () =>
{
working = new ClockBackedTestWorkingBeatmap(new OsuRuleset().RulesetInfo, new FramedClock(new ManualClock()), Audio);
- working.LoadTrack();
-
Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0);
gameplayClockContainer.Reset(startClock: !whileStopped);
diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs
index 9e1d786d87..4123412ab6 100644
--- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
@@ -15,6 +16,7 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Gameplay
@@ -91,6 +93,47 @@ namespace osu.Game.Tests.Gameplay
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0));
}
+ [Test]
+ public void TestFailScore()
+ {
+ var beatmap = new Beatmap
+ {
+ HitObjects =
+ {
+ new TestHitObject(),
+ new TestHitObject(HitResult.LargeTickHit),
+ new TestHitObject(HitResult.SmallTickHit),
+ new TestHitObject(HitResult.SmallBonus),
+ new TestHitObject(),
+ new TestHitObject(HitResult.LargeTickHit),
+ new TestHitObject(HitResult.SmallTickHit),
+ new TestHitObject(HitResult.LargeBonus),
+ }
+ };
+
+ var scoreProcessor = new ScoreProcessor(new OsuRuleset());
+ scoreProcessor.ApplyBeatmap(beatmap);
+
+ scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok });
+ scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.LargeTickHit });
+ scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].CreateJudgement()) { Type = HitResult.SmallTickMiss });
+ scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], beatmap.HitObjects[3].CreateJudgement()) { Type = HitResult.SmallBonus });
+
+ var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo };
+ scoreProcessor.FailScore(score);
+
+ Assert.That(score.Rank, Is.EqualTo(ScoreRank.F));
+ Assert.That(score.Passed, Is.False);
+ Assert.That(score.Statistics.Count(kvp => kvp.Value > 0), Is.EqualTo(7));
+ Assert.That(score.Statistics[HitResult.Ok], Is.EqualTo(1));
+ Assert.That(score.Statistics[HitResult.Miss], Is.EqualTo(1));
+ Assert.That(score.Statistics[HitResult.LargeTickHit], Is.EqualTo(1));
+ Assert.That(score.Statistics[HitResult.LargeTickMiss], Is.EqualTo(1));
+ Assert.That(score.Statistics[HitResult.SmallTickMiss], Is.EqualTo(2));
+ Assert.That(score.Statistics[HitResult.SmallBonus], Is.EqualTo(1));
+ Assert.That(score.Statistics[HitResult.IgnoreMiss], Is.EqualTo(1));
+ }
+
private class TestJudgement : Judgement
{
public override HitResult MaxResult { get; }
@@ -100,5 +143,17 @@ namespace osu.Game.Tests.Gameplay
MaxResult = maxResult;
}
}
+
+ private class TestHitObject : HitObject
+ {
+ private readonly HitResult maxResult;
+
+ public TestHitObject(HitResult maxResult = HitResult.Perfect)
+ {
+ this.maxResult = maxResult;
+ }
+
+ public override Judgement CreateJudgement() => new TestJudgement(maxResult);
+ }
}
}
diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
index a9c6bacc65..a432cc9648 100644
--- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
@@ -69,7 +69,6 @@ namespace osu.Game.Tests.Gameplay
AddStep("create container", () =>
{
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
- working.LoadTrack();
Add(gameplayContainer = new MasterGameplayClockContainer(working, 0)
{
@@ -96,7 +95,6 @@ namespace osu.Game.Tests.Gameplay
AddStep("create container", () =>
{
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
- working.LoadTrack();
const double start_time = 1000;
diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs
index 32b6dc649c..23ca31ee42 100644
--- a/osu.Game.Tests/ImportTest.cs
+++ b/osu.Game.Tests/ImportTest.cs
@@ -10,7 +10,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Platform;
-using osu.Game.Collections;
+using osu.Game.Database;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests
@@ -47,7 +47,7 @@ namespace osu.Game.Tests
public class TestOsuGameBase : OsuGameBase
{
- public CollectionManager CollectionManager { get; private set; }
+ public RealmAccess Realm => Dependencies.Get();
private readonly bool withBeatmap;
@@ -62,8 +62,6 @@ namespace osu.Game.Tests
// Beatmap must be imported before the collection manager is loaded.
if (withBeatmap)
BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely();
-
- AddInternal(CollectionManager = new CollectionManager(Storage));
}
}
}
diff --git a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs
index a6f68b2836..4101652c49 100644
--- a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs
+++ b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.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.
-#nullable disable
-
using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps;
@@ -17,7 +15,7 @@ namespace osu.Game.Tests.Mods
[TestFixture]
public class ModDifficultyAdjustTest
{
- private TestModDifficultyAdjust testMod;
+ private TestModDifficultyAdjust testMod = null!;
[SetUp]
public void Setup()
@@ -148,7 +146,7 @@ namespace osu.Game.Tests.Mods
yield return new TestModDifficultyAdjust();
}
- public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null)
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null)
{
throw new System.NotImplementedException();
}
diff --git a/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs b/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs
index e94ee40acd..cd6879cf01 100644
--- a/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs
+++ b/osu.Game.Tests/Mods/ModSettingsEqualityComparison.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.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Online.API;
using osu.Game.Rulesets.Osu;
diff --git a/osu.Game.Tests/Mods/ModSettingsTest.cs b/osu.Game.Tests/Mods/ModSettingsTest.cs
index 607b585d33..b9ea1f2567 100644
--- a/osu.Game.Tests/Mods/ModSettingsTest.cs
+++ b/osu.Game.Tests/Mods/ModSettingsTest.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.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs
index 22be1a3f01..3b391f6756 100644
--- a/osu.Game.Tests/Mods/ModUtilsTest.cs
+++ b/osu.Game.Tests/Mods/ModUtilsTest.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.
-#nullable disable
-
using System;
using System.Linq;
using Moq;
@@ -164,19 +162,19 @@ namespace osu.Game.Tests.Mods
new object[]
{
new Mod[] { new OsuModHidden(), new InvalidMultiplayerMod() },
- null
+ Array.Empty()
},
// invalid free mod is valid for local.
new object[]
{
new Mod[] { new OsuModHidden(), new InvalidMultiplayerFreeMod() },
- null
+ Array.Empty()
},
// valid pair.
new object[]
{
new Mod[] { new OsuModHidden(), new OsuModHardRock() },
- null
+ Array.Empty()
},
};
@@ -216,13 +214,13 @@ namespace osu.Game.Tests.Mods
new object[]
{
new Mod[] { new OsuModHidden(), new InvalidMultiplayerFreeMod() },
- null
+ Array.Empty()
},
// valid pair.
new object[]
{
new Mod[] { new OsuModHidden(), new OsuModHardRock() },
- null
+ Array.Empty()
},
};
@@ -256,19 +254,19 @@ namespace osu.Game.Tests.Mods
new object[]
{
new Mod[] { new OsuModHidden(), new OsuModApproachDifferent() },
- null,
+ Array.Empty(),
},
// incompatible pair with derived class is valid for free mods.
new object[]
{
new Mod[] { new OsuModDeflate(), new OsuModSpinIn() },
- null,
+ Array.Empty(),
},
// valid pair.
new object[]
{
new Mod[] { new OsuModHidden(), new OsuModHardRock() },
- null
+ Array.Empty()
},
};
@@ -277,12 +275,12 @@ namespace osu.Game.Tests.Mods
{
bool isValid = ModUtils.CheckValidForGameplay(inputMods, out var invalid);
- Assert.That(isValid, Is.EqualTo(expectedInvalid == null));
+ Assert.That(isValid, Is.EqualTo(expectedInvalid.Length == 0));
if (isValid)
Assert.IsNull(invalid);
else
- Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
+ Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
}
[TestCaseSource(nameof(invalid_multiplayer_mod_test_scenarios))]
@@ -290,12 +288,12 @@ namespace osu.Game.Tests.Mods
{
bool isValid = ModUtils.CheckValidRequiredModsForMultiplayer(inputMods, out var invalid);
- Assert.That(isValid, Is.EqualTo(expectedInvalid == null));
+ Assert.That(isValid, Is.EqualTo(expectedInvalid.Length == 0));
if (isValid)
Assert.IsNull(invalid);
else
- Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
+ Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
}
[TestCaseSource(nameof(invalid_free_mod_test_scenarios))]
@@ -303,12 +301,12 @@ namespace osu.Game.Tests.Mods
{
bool isValid = ModUtils.CheckValidFreeModsForMultiplayer(inputMods, out var invalid);
- Assert.That(isValid, Is.EqualTo(expectedInvalid == null));
+ Assert.That(isValid, Is.EqualTo(expectedInvalid.Length == 0));
if (isValid)
Assert.IsNull(invalid);
else
- Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
+ Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
}
public abstract class CustomMod1 : Mod, IModCompatibilitySpecification
diff --git a/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs b/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs
index 3c69adcb59..b8a3828a64 100644
--- a/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs
+++ b/osu.Game.Tests/Mods/MultiModIncompatibilityTest.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.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -29,10 +27,10 @@ namespace osu.Game.Tests.Mods
[TestCase(typeof(ManiaRuleset))]
public void TestAllMultiModsFromRulesetAreIncompatible(Type rulesetType)
{
- var ruleset = (Ruleset)Activator.CreateInstance(rulesetType);
+ var ruleset = Activator.CreateInstance(rulesetType) as Ruleset;
Assert.That(ruleset, Is.Not.Null);
- var allMultiMods = getMultiMods(ruleset);
+ var allMultiMods = getMultiMods(ruleset!);
Assert.Multiple(() =>
{
diff --git a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs
index f608d020d4..dd105787fa 100644
--- a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs
+++ b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.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.
-#nullable disable
-
using System.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
diff --git a/osu.Game.Tests/Mods/TestCustomisableModRuleset.cs b/osu.Game.Tests/Mods/TestCustomisableModRuleset.cs
index 08007503c6..9e3354935a 100644
--- a/osu.Game.Tests/Mods/TestCustomisableModRuleset.cs
+++ b/osu.Game.Tests/Mods/TestCustomisableModRuleset.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.
-#nullable disable
-
using System;
using System.Collections.Generic;
using osu.Framework.Bindables;
@@ -33,7 +31,7 @@ namespace osu.Game.Tests.Mods
return Array.Empty();
}
- public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException();
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) => throw new NotImplementedException();
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException();
diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs
index 185f85513b..67dbcf0ccf 100644
--- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs
+++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs
@@ -12,7 +12,6 @@ using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
-using osu.Game.Online.Solo;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
@@ -110,30 +109,30 @@ namespace osu.Game.Tests.Online
}
[Test]
- public void TestDeserialiseSubmittableScoreWithEmptyMods()
+ public void TestDeserialiseSoloScoreWithEmptyMods()
{
- var score = new SubmittableScore(new ScoreInfo
+ var score = SoloScoreInfo.ForSubmission(new ScoreInfo
{
User = new APIUser(),
Ruleset = new OsuRuleset().RulesetInfo,
});
- var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score));
+ var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score));
Assert.That(deserialised?.Mods.Length, Is.Zero);
}
[Test]
- public void TestDeserialiseSubmittableScoreWithCustomModSetting()
+ public void TestDeserialiseSoloScoreWithCustomModSetting()
{
- var score = new SubmittableScore(new ScoreInfo
+ var score = SoloScoreInfo.ForSubmission(new ScoreInfo
{
Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } },
User = new APIUser(),
Ruleset = new OsuRuleset().RulesetInfo,
});
- var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score));
+ var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score));
Assert.That((deserialised?.Mods[0])?.Settings["speed_change"], Is.EqualTo(2));
}
diff --git a/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs
index d0176da0e9..e7a6e9a543 100644
--- a/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs
+++ b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs
@@ -80,7 +80,7 @@ namespace osu.Game.Tests.Online
{
AddStep("download beatmap", () => beatmaps.Download(test_db_model));
- AddStep("cancel download from request", () => beatmaps.GetExistingDownload(test_db_model).Cancel());
+ AddStep("cancel download from request", () => beatmaps.GetExistingDownload(test_db_model)!.Cancel());
AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_db_model) == null);
AddAssert("is notification cancelled", () => recentNotification.State == ProgressNotificationState.Cancelled);
diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
index fcf69bf6f2..3f20f843a7 100644
--- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
+++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
@@ -126,10 +126,10 @@ namespace osu.Game.Tests.Online
AddStep("start downloading", () => beatmapDownloader.Download(testBeatmapSet));
addAvailabilityCheckStep("state downloading 0%", () => BeatmapAvailability.Downloading(0.0f));
- AddStep("set progress 40%", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet)).SetProgress(0.4f));
+ AddStep("set progress 40%", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.SetProgress(0.4f));
addAvailabilityCheckStep("state downloading 40%", () => BeatmapAvailability.Downloading(0.4f));
- AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet)).TriggerSuccess(testBeatmapFile));
+ AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.TriggerSuccess(testBeatmapFile));
addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing);
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
@@ -208,7 +208,7 @@ namespace osu.Game.Tests.Online
public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources,
GameHost host = null, WorkingBeatmap defaultBeatmap = null)
- : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap)
+ : base(storage, realm, api, audioManager, resources, host, defaultBeatmap)
{
}
@@ -246,7 +246,7 @@ namespace osu.Game.Tests.Online
=> new TestDownloadRequest(set);
}
- private class TestDownloadRequest : ArchiveDownloadRequest
+ internal class TestDownloadRequest : ArchiveDownloadRequest
{
public new void SetProgress(float progress) => base.SetProgress(progress);
public new void TriggerSuccess(string filename) => base.TriggerSuccess(filename);
diff --git a/osu.Game.Tests/Online/TestSubmittableScoreJsonSerialization.cs b/osu.Game.Tests/Online/TestSoloScoreInfoJsonSerialization.cs
similarity index 79%
rename from osu.Game.Tests/Online/TestSubmittableScoreJsonSerialization.cs
rename to osu.Game.Tests/Online/TestSoloScoreInfoJsonSerialization.cs
index f0f6727393..8ff0b67b5b 100644
--- a/osu.Game.Tests/Online/TestSubmittableScoreJsonSerialization.cs
+++ b/osu.Game.Tests/Online/TestSoloScoreInfoJsonSerialization.cs
@@ -6,7 +6,7 @@
using Newtonsoft.Json;
using NUnit.Framework;
using osu.Game.IO.Serialization;
-using osu.Game.Online.Solo;
+using osu.Game.Online.API.Requests.Responses;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Online
@@ -15,12 +15,12 @@ namespace osu.Game.Tests.Online
/// Basic testing to ensure our attribute-based naming is correctly working.
///
[TestFixture]
- public class TestSubmittableScoreJsonSerialization
+ public class TestSoloScoreInfoJsonSerialization
{
[Test]
public void TestScoreSerialisationViaExtensionMethod()
{
- var score = new SubmittableScore(TestResources.CreateTestScoreInfo());
+ var score = SoloScoreInfo.ForSubmission(TestResources.CreateTestScoreInfo());
string serialised = score.Serialize();
@@ -31,7 +31,7 @@ namespace osu.Game.Tests.Online
[Test]
public void TestScoreSerialisationWithoutSettings()
{
- var score = new SubmittableScore(TestResources.CreateTestScoreInfo());
+ var score = SoloScoreInfo.ForSubmission(TestResources.CreateTestScoreInfo());
string serialised = JsonConvert.SerializeObject(score);
diff --git a/osu.Game.Tests/Resources/Archives/modified-classic-20220723.osk b/osu.Game.Tests/Resources/Archives/modified-classic-20220723.osk
new file mode 100644
index 0000000000..8e7a1b42df
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/modified-classic-20220723.osk differ
diff --git a/osu.Game.Tests/Resources/Archives/modified-classic-20220801.osk b/osu.Game.Tests/Resources/Archives/modified-classic-20220801.osk
new file mode 100644
index 0000000000..9236e1d77f
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/modified-classic-20220801.osk differ
diff --git a/osu.Game.Tests/Resources/Archives/modified-default-20220723.osk b/osu.Game.Tests/Resources/Archives/modified-default-20220723.osk
new file mode 100644
index 0000000000..7547162165
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/modified-default-20220723.osk differ
diff --git a/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs
index 2622db464f..4601737558 100644
--- a/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs
+++ b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.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.
-#nullable disable
-
using NUnit.Framework;
using osu.Framework.Audio.Track;
using osu.Framework.Timing;
@@ -19,8 +17,8 @@ namespace osu.Game.Tests.Rulesets.Mods
private const double start_time = 1000;
private const double duration = 9000;
- private TrackVirtual track;
- private OsuPlayfield playfield;
+ private TrackVirtual track = null!;
+ private OsuPlayfield playfield = null!;
[SetUp]
public void SetUp()
diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs
new file mode 100644
index 0000000000..53639deac3
--- /dev/null
+++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.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 System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.OpenGL.Textures;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.IO.Stores;
+using osu.Game.Audio;
+using osu.Game.IO;
+using osu.Game.IO.Archives;
+using osu.Game.Screens.Play.HUD;
+using osu.Game.Screens.Play.HUD.HitErrorMeters;
+using osu.Game.Skinning;
+using osu.Game.Tests.Resources;
+
+namespace osu.Game.Tests.Skins
+{
+ ///
+ /// Test that the main components (which are serialised based on namespace/class name)
+ /// remain compatible with any changes.
+ ///
+ ///
+ /// If this test breaks, check any naming or class structure changes.
+ /// Migration rules may need to be added to .
+ ///
+ [TestFixture]
+ public class SkinDeserialisationTest
+ {
+ private static readonly string[] available_skins =
+ {
+ // Covers song progress before namespace changes, and most other components.
+ "Archives/modified-default-20220723.osk",
+ "Archives/modified-classic-20220723.osk",
+ // Covers legacy song progress, UR counter, colour hit error metre.
+ "Archives/modified-classic-20220801.osk"
+ };
+
+ ///
+ /// If this test fails, new test resources should be added to include new components.
+ ///
+ [Test]
+ public void TestSkinnableComponentsCoveredByDeserialisationTests()
+ {
+ HashSet instantiatedTypes = new HashSet();
+
+ foreach (string oskFile in available_skins)
+ {
+ using (var stream = TestResources.OpenResource(oskFile))
+ using (var storage = new ZipArchiveReader(stream))
+ {
+ var skin = new TestSkin(new SkinInfo(), null, storage);
+
+ foreach (var target in skin.DrawableComponentInfo)
+ {
+ foreach (var info in target.Value)
+ instantiatedTypes.Add(info.Type);
+ }
+ }
+ }
+
+ var editableTypes = SkinnableInfo.GetAllAvailableDrawables().Where(t => (Activator.CreateInstance(t) as ISkinnableDrawable)?.IsEditable == true);
+
+ Assert.That(instantiatedTypes, Is.EquivalentTo(editableTypes));
+ }
+
+ [Test]
+ public void TestDeserialiseModifiedDefault()
+ {
+ using (var stream = TestResources.OpenResource("Archives/modified-default-20220723.osk"))
+ using (var storage = new ZipArchiveReader(stream))
+ {
+ var skin = new TestSkin(new SkinInfo(), null, storage);
+
+ Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2));
+ Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents], Has.Length.EqualTo(9));
+ }
+ }
+
+ [Test]
+ public void TestDeserialiseModifiedClassic()
+ {
+ using (var stream = TestResources.OpenResource("Archives/modified-classic-20220723.osk"))
+ using (var storage = new ZipArchiveReader(stream))
+ {
+ var skin = new TestSkin(new SkinInfo(), null, storage);
+
+ Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2));
+ Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents], Has.Length.EqualTo(6));
+ Assert.That(skin.DrawableComponentInfo[SkinnableTarget.SongSelect], Has.Length.EqualTo(1));
+
+ var skinnableInfo = skin.DrawableComponentInfo[SkinnableTarget.SongSelect].First();
+
+ Assert.That(skinnableInfo.Type, Is.EqualTo(typeof(SkinnableSprite)));
+ Assert.That(skinnableInfo.Settings.First().Key, Is.EqualTo("sprite_name"));
+ Assert.That(skinnableInfo.Settings.First().Value, Is.EqualTo("ppy_logo-2.png"));
+ }
+
+ using (var stream = TestResources.OpenResource("Archives/modified-classic-20220801.osk"))
+ using (var storage = new ZipArchiveReader(stream))
+ {
+ var skin = new TestSkin(new SkinInfo(), null, storage);
+ Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents], Has.Length.EqualTo(8));
+ Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter)));
+ Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter)));
+ Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(LegacySongProgress)));
+ }
+ }
+
+ private class TestSkin : Skin
+ {
+ public TestSkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? storage = null, string configurationFilename = "skin.ini")
+ : base(skin, resources, storage, configurationFilename)
+ {
+ }
+
+ public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
+
+ public override IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
+
+ public override ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
+ }
+ }
+}
diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
index f4cea2c8cc..e82a5b57d9 100644
--- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
+++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
@@ -32,7 +32,6 @@ namespace osu.Game.Tests.Skins
imported?.PerformRead(s =>
{
beatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps[0]);
- beatmap.LoadTrack();
});
}
@@ -40,6 +39,10 @@ namespace osu.Game.Tests.Skins
public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo("sample")) != null);
[Test]
- public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => !(beatmap.Track is TrackVirtual));
+ public void TestRetrieveOggTrack() => AddAssert("track is non-null", () =>
+ {
+ using (var track = beatmap.LoadTrack())
+ return track is not TrackVirtual;
+ });
}
}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
index aaccea09d4..5aadd6f56a 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Background
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new OsuConfigManager(LocalStorage));
Dependencies.Cache(Realm);
diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs
index 3f30fa367c..4f7d3a4403 100644
--- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs
+++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.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.
-#nullable disable
-
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -27,38 +25,32 @@ namespace osu.Game.Tests.Visual.Collections
{
protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
- private DialogOverlay dialogOverlay;
- private CollectionManager manager;
-
- private RulesetStore rulesets;
- private BeatmapManager beatmapManager;
-
- private ManageCollectionsDialog dialog;
+ private DialogOverlay dialogOverlay = null!;
+ private BeatmapManager beatmapManager = null!;
+ private ManageCollectionsDialog dialog = null!;
[BackgroundDependencyLoader]
private void load(GameHost host)
{
- Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
- Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(new RealmRulesetStore(Realm));
+ Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
base.Content.AddRange(new Drawable[]
{
- manager = new CollectionManager(LocalStorage),
Content,
dialogOverlay = new DialogOverlay(),
});
- Dependencies.Cache(manager);
Dependencies.CacheAs(dialogOverlay);
}
[SetUp]
public void SetUp() => Schedule(() =>
{
- manager.Collections.Clear();
+ Realm.Write(r => r.RemoveAll());
Child = dialog = new ManageCollectionsDialog();
});
@@ -78,17 +70,17 @@ namespace osu.Game.Tests.Visual.Collections
[Test]
public void TestLastItemIsPlaceholder()
{
- AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType().Last().Model));
+ AddAssert("last item is placeholder", () => !dialog.ChildrenOfType().Last().Model.IsManaged);
}
[Test]
public void TestAddCollectionExternal()
{
- AddStep("add collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "First collection" } }));
+ AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "First collection"))));
assertCollectionCount(1);
assertCollectionName(0, "First collection");
- AddStep("add another collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "Second collection" } }));
+ AddStep("add another collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "Second collection"))));
assertCollectionCount(2);
assertCollectionName(1, "Second collection");
}
@@ -108,7 +100,7 @@ namespace osu.Game.Tests.Visual.Collections
[Test]
public void TestAddCollectionViaPlaceholder()
{
- DrawableCollectionListItem placeholderItem = null;
+ DrawableCollectionListItem placeholderItem = null!;
AddStep("focus placeholder", () =>
{
@@ -116,24 +108,37 @@ namespace osu.Game.Tests.Visual.Collections
InputManager.Click(MouseButton.Left);
});
- // Done directly via the collection since InputManager methods cannot add text to textbox...
- AddStep("change collection name", () => placeholderItem.Model.Name.Value = "a");
- assertCollectionCount(1);
- AddAssert("collection now exists", () => manager.Collections.Contains(placeholderItem.Model));
+ assertCollectionCount(0);
- AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType().Last().Model));
+ AddStep("change collection name", () =>
+ {
+ placeholderItem.ChildrenOfType().First().Text = "test text";
+ InputManager.Key(Key.Enter);
+ });
+
+ assertCollectionCount(1);
+
+ AddAssert("last item is placeholder", () => !dialog.ChildrenOfType().Last().Model.IsManaged);
}
[Test]
public void TestRemoveCollectionExternal()
{
- AddStep("add two collections", () => manager.Collections.AddRange(new[]
- {
- new BeatmapCollection { Name = { Value = "1" } },
- new BeatmapCollection { Name = { Value = "2" } },
- }));
+ BeatmapCollection first = null!;
- AddStep("remove first collection", () => manager.Collections.RemoveAt(0));
+ AddStep("add two collections", () =>
+ {
+ Realm.Write(r =>
+ {
+ r.Add(new[]
+ {
+ first = new BeatmapCollection(name: "1"),
+ new BeatmapCollection(name: "2"),
+ });
+ });
+ });
+
+ AddStep("remove first collection", () => Realm.Write(r => r.Remove(first)));
assertCollectionCount(1);
assertCollectionName(0, "2");
}
@@ -143,7 +148,7 @@ namespace osu.Game.Tests.Visual.Collections
{
AddStep("add dropdown", () =>
{
- Add(new CollectionFilterDropdown
+ Add(new CollectionDropdown
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
@@ -151,21 +156,27 @@ namespace osu.Game.Tests.Visual.Collections
Width = 0.4f,
});
});
- AddStep("add two collections with same name", () => manager.Collections.AddRange(new[]
+ AddStep("add two collections with same name", () => Realm.Write(r => r.Add(new[]
{
- new BeatmapCollection { Name = { Value = "1" } },
- new BeatmapCollection { Name = { Value = "1" }, BeatmapHashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } },
- }));
+ new BeatmapCollection(name: "1"),
+ new BeatmapCollection(name: "1")
+ {
+ BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash }
+ },
+ })));
}
[Test]
public void TestRemoveCollectionViaButton()
{
- AddStep("add two collections", () => manager.Collections.AddRange(new[]
+ AddStep("add two collections", () => Realm.Write(r => r.Add(new[]
{
- new BeatmapCollection { Name = { Value = "1" } },
- new BeatmapCollection { Name = { Value = "2" }, BeatmapHashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } },
- }));
+ new BeatmapCollection(name: "1"),
+ new BeatmapCollection(name: "2")
+ {
+ BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash }
+ },
+ })));
assertCollectionCount(2);
@@ -198,10 +209,13 @@ namespace osu.Game.Tests.Visual.Collections
[Test]
public void TestCollectionNotRemovedWhenDialogCancelled()
{
- AddStep("add two collections", () => manager.Collections.AddRange(new[]
+ AddStep("add collection", () => Realm.Write(r => r.Add(new[]
{
- new BeatmapCollection { Name = { Value = "1" }, BeatmapHashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } },
- }));
+ new BeatmapCollection(name: "1")
+ {
+ BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash }
+ },
+ })));
assertCollectionCount(1);
@@ -224,34 +238,67 @@ namespace osu.Game.Tests.Visual.Collections
[Test]
public void TestCollectionRenamedExternal()
{
- AddStep("add two collections", () => manager.Collections.AddRange(new[]
+ BeatmapCollection first = null!;
+
+ AddStep("add two collections", () =>
{
- new BeatmapCollection { Name = { Value = "1" } },
- new BeatmapCollection { Name = { Value = "2" } },
- }));
+ Realm.Write(r =>
+ {
+ r.Add(new[]
+ {
+ first = new BeatmapCollection(name: "1"),
+ new BeatmapCollection(name: "2"),
+ });
+ });
+ });
- AddStep("change first collection name", () => manager.Collections[0].Name.Value = "First");
+ assertCollectionName(0, "1");
+ assertCollectionName(1, "2");
- assertCollectionName(0, "First");
+ AddStep("change first collection name", () => Realm.Write(_ => first.Name = "First"));
+
+ // Item will have moved due to alphabetical sorting.
+ assertCollectionName(0, "2");
+ assertCollectionName(1, "First");
}
[Test]
public void TestCollectionRenamedOnTextChange()
{
- AddStep("add two collections", () => manager.Collections.AddRange(new[]
+ BeatmapCollection first = null!;
+ DrawableCollectionListItem firstItem = null!;
+
+ AddStep("add two collections", () =>
{
- new BeatmapCollection { Name = { Value = "1" } },
- new BeatmapCollection { Name = { Value = "2" } },
- }));
+ Realm.Write(r =>
+ {
+ r.Add(new[]
+ {
+ first = new BeatmapCollection(name: "1"),
+ new BeatmapCollection(name: "2"),
+ });
+ });
+ });
assertCollectionCount(2);
- AddStep("change first collection name", () => dialog.ChildrenOfType().First().Text = "First");
- AddAssert("collection has new name", () => manager.Collections[0].Name.Value == "First");
+ AddStep("focus first collection", () =>
+ {
+ InputManager.MoveMouseTo(firstItem = dialog.ChildrenOfType().First());
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddStep("change first collection name", () =>
+ {
+ firstItem.ChildrenOfType().First().Text = "First";
+ InputManager.Key(Key.Enter);
+ });
+
+ AddUntilStep("collection has new name", () => first.Name == "First");
}
private void assertCollectionCount(int count)
- => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count(i => i.IsCreated.Value) == count);
+ => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count() == count + 1); // +1 for placeholder
private void assertCollectionName(int index, string name)
=> AddUntilStep($"item {index + 1} has correct name", () => dialog.ChildrenOfType().ElementAt(index).ChildrenOfType().First().Text == name);
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs
index 2cada1989e..630d048867 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs
@@ -5,7 +5,6 @@
using NUnit.Framework;
using osu.Framework.Graphics;
-using osu.Framework.Utils;
namespace osu.Game.Tests.Visual.Editing
{
@@ -14,30 +13,22 @@ namespace osu.Game.Tests.Visual.Editing
public override Drawable CreateTestComponent() => Empty();
[Test]
- [FlakyTest]
- /*
- * Fail rate around 0.3%
- *
- * TearDown : osu.Framework.Testing.Drawables.Steps.AssertButton+TracedException : range halved
- * --TearDown
- * at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal()
- * at osu.Framework.Threading.Scheduler.Update()
- * at osu.Framework.Graphics.Drawable.UpdateSubTree()
- */
public void TestVisibleRangeUpdatesOnZoomChange()
{
double initialVisibleRange = 0;
+ AddUntilStep("wait for load", () => MusicController.TrackLoaded);
+
AddStep("reset zoom", () => TimelineArea.Timeline.Zoom = 100);
AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
AddStep("scale zoom", () => TimelineArea.Timeline.Zoom = 200);
- AddAssert("range halved", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange / 2, 1));
+ AddStep("range halved", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange / 2).Within(1)));
AddStep("descale zoom", () => TimelineArea.Timeline.Zoom = 50);
- AddAssert("range doubled", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange * 2, 1));
+ AddStep("range doubled", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange * 2).Within(1)));
AddStep("restore zoom", () => TimelineArea.Timeline.Zoom = 100);
- AddAssert("range restored", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange, 1));
+ AddStep("range restored", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange).Within(1)));
}
[Test]
@@ -45,6 +36,8 @@ namespace osu.Game.Tests.Visual.Editing
{
double initialVisibleRange = 0;
+ AddUntilStep("wait for load", () => MusicController.TrackLoaded);
+
AddStep("reset timeline size", () => TimelineArea.Timeline.Width = 1);
AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs
index 9dc403814b..ce418f33f0 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Editing
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(30)
},
- scrollContainer = new ZoomableScrollContainer
+ scrollContainer = new ZoomableScrollContainer(1, 60, 1)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -80,21 +80,6 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
}
- [Test]
- public void TestZoomRangeUpdate()
- {
- AddStep("set zoom to 2", () => scrollContainer.Zoom = 2);
- AddStep("set min zoom to 5", () => scrollContainer.MinZoom = 5);
- AddAssert("zoom = 5", () => scrollContainer.Zoom == 5);
-
- AddStep("set max zoom to 10", () => scrollContainer.MaxZoom = 10);
- AddAssert("zoom = 5", () => scrollContainer.Zoom == 5);
-
- AddStep("set min zoom to 20", () => scrollContainer.MinZoom = 20);
- AddStep("set max zoom to 40", () => scrollContainer.MaxZoom = 40);
- AddAssert("zoom = 20", () => scrollContainer.Zoom == 20);
- }
-
[Test]
public void TestZoom0()
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
index 47c8dc0f8d..f2fe55d719 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
@@ -31,20 +31,20 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void AddCheckSteps()
{
- // It doesn't matter which ruleset is used - this beatmap is only used for reference.
- var beatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ // we only want this beatmap for time reference.
+ var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2));
- seekTo(beatmap.Beatmap.Breaks[0].StartTime);
+ seekTo(referenceBeatmap.Breaks[0].StartTime);
AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting);
AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType().Single().AccuracyDisplay.Current.Value == 1);
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
- seekTo(beatmap.Beatmap.HitObjects[^1].GetEndTime());
+ seekTo(referenceBeatmap.HitObjects[^1].GetEndTime());
AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true);
AddAssert("score has combo", () => getResultsScreen().Score.Combo > 100);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
index a79ba0ae5d..334d8f1452 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
@@ -263,27 +263,30 @@ namespace osu.Game.Tests.Visual.Gameplay
return beatmap;
}
- private void createTest(IBeatmap beatmap, Action overrideAction = null) => AddStep("create test", () =>
+ private void createTest(IBeatmap beatmap, Action overrideAction = null)
{
- var ruleset = new TestScrollingRuleset();
-
- drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo));
- drawableRuleset.FrameStablePlayback = false;
-
- overrideAction?.Invoke(drawableRuleset);
-
- Child = new Container
+ AddStep("create test", () =>
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Y,
- Height = 0.75f,
- Width = 400,
- Masking = true,
- Clock = new FramedClock(testClock),
- Child = drawableRuleset
- };
- });
+ var ruleset = new TestScrollingRuleset();
+
+ drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo));
+ drawableRuleset.FrameStablePlayback = false;
+
+ overrideAction?.Invoke(drawableRuleset);
+
+ Child = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Y,
+ Height = 0.75f,
+ Width = 400,
+ Masking = true,
+ Clock = new FramedClock(testClock),
+ Child = drawableRuleset
+ };
+ });
+ }
#region Ruleset
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
index dd0f965914..fb97f94dbb 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
@@ -159,6 +159,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType().Single().Alpha == 0);
+ AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType().All(c => c.ComponentsLoaded));
AddStep("bind on update", () =>
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
index 5bd0a29308..71cc1f7b23 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public TestScenePause()
{
- base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both });
+ base.Content.Add(content = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both });
}
[SetUpSteps]
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
index 56588e4d4e..05474e3d39 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
@@ -308,17 +308,18 @@ namespace osu.Game.Tests.Visual.Gameplay
}
}
- [TestCase(false, 1.0, false)] // not charging, above cutoff --> no warning
- [TestCase(true, 0.1, false)] // charging, below cutoff --> no warning
- [TestCase(false, 0.25, true)] // not charging, at cutoff --> warning
- public void TestLowBatteryNotification(bool isCharging, double chargeLevel, bool shouldWarn)
+ [TestCase(true, 1.0, false)] // on battery, above cutoff --> no warning
+ [TestCase(false, 0.1, false)] // not on battery, below cutoff --> no warning
+ [TestCase(true, 0.25, true)] // on battery, at cutoff --> warning
+ [TestCase(true, null, false)] // on battery, level unknown --> no warning
+ public void TestLowBatteryNotification(bool onBattery, double? chargeLevel, bool shouldWarn)
{
AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce).Value = false);
// set charge status and level
AddStep("load player", () => resetPlayer(false, () =>
{
- batteryInfo.SetCharging(isCharging);
+ batteryInfo.SetOnBattery(onBattery);
batteryInfo.SetChargeLevel(chargeLevel);
}));
AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready);
@@ -408,19 +409,19 @@ namespace osu.Game.Tests.Visual.Gameplay
///
private class LocalBatteryInfo : BatteryInfo
{
- private bool isCharging = true;
- private double chargeLevel = 1;
+ private bool onBattery;
+ private double? chargeLevel;
- public override bool IsCharging => isCharging;
+ public override bool OnBattery => onBattery;
- public override double ChargeLevel => chargeLevel;
+ public override double? ChargeLevel => chargeLevel;
- public void SetCharging(bool value)
+ public void SetOnBattery(bool value)
{
- isCharging = value;
+ onBattery = value;
}
- public void SetChargeLevel(double value)
+ public void SetChargeLevel(double? value)
{
chargeLevel = value;
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs
index 6491987abe..ddb585a73c 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
- Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, Scheduler, API));
Dependencies.Cache(Realm);
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
index 96efca6b65..d1bdfb1dfa 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
@@ -239,7 +239,7 @@ namespace osu.Game.Tests.Visual.Gameplay
createPlayerTest(false, r =>
{
var beatmap = createTestBeatmap(r);
- beatmap.BeatmapInfo.OnlineID = -1;
+ beatmap.BeatmapInfo.ResetOnlineInfo();
return beatmap;
});
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs
index 1fa4885b7a..618ffbcb0e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs
@@ -158,21 +158,24 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("clean up", () => drawableRuleset.NewResult -= onNewResult);
}
- private void createTest(IBeatmap beatmap, int poolSize, Func createClock = null) => AddStep("create test", () =>
+ private void createTest(IBeatmap beatmap, int poolSize, Func createClock = null)
{
- var ruleset = new TestPoolingRuleset();
-
- drawableRuleset = (TestDrawablePoolingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo));
- drawableRuleset.FrameStablePlayback = true;
- drawableRuleset.PoolSize = poolSize;
-
- Child = new Container
+ AddStep("create test", () =>
{
- RelativeSizeAxes = Axes.Both,
- Clock = createClock?.Invoke() ?? new FramedOffsetClock(Clock, false) { Offset = -Clock.CurrentTime },
- Child = drawableRuleset
- };
- });
+ var ruleset = new TestPoolingRuleset();
+
+ drawableRuleset = (TestDrawablePoolingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo));
+ drawableRuleset.FrameStablePlayback = true;
+ drawableRuleset.PoolSize = poolSize;
+
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Clock = createClock?.Invoke() ?? new FramedOffsetClock(Clock, false) { Offset = -Clock.CurrentTime },
+ Child = drawableRuleset
+ };
+ });
+ }
#region Ruleset
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
index c259d5f0a8..9d70d1ef33 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
@@ -7,7 +7,6 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online;
-using osu.Game.Online.API.Requests.Responses;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
@@ -15,7 +14,6 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
-using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
@@ -30,9 +28,6 @@ namespace osu.Game.Tests.Visual.Gameplay
{
private const long online_score_id = 2553163309;
- [Resolved]
- private RulesetStore rulesets { get; set; }
-
private TestReplayDownloadButton downloadButton;
[Resolved]
@@ -211,21 +206,18 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value);
}
- private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true)
+ private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true) => new ScoreInfo
{
- return new APIScore
+ OnlineID = hasOnlineId ? online_score_id : 0,
+ Ruleset = new OsuRuleset().RulesetInfo,
+ BeatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(),
+ Hash = replayAvailable ? "online" : string.Empty,
+ User = new APIUser
{
- OnlineID = hasOnlineId ? online_score_id : 0,
- RulesetID = 0,
- Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(),
- HasReplay = replayAvailable,
- User = new APIUser
- {
- Id = 39828,
- Username = @"WubWoofWolf",
- }
- }.CreateScoreInfo(rulesets, beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First());
- }
+ Id = 39828,
+ Username = @"WubWoofWolf",
+ }
+ };
private class TestReplayDownloadButton : ReplayDownloadButton
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
index 54b2e66f2f..b3401c916b 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
@@ -3,10 +3,10 @@
#nullable disable
+using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -40,8 +40,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private TestReplayRecorder recorder;
- [Cached]
- private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
+ private GameplayState gameplayState;
[SetUpSteps]
public void SetUpSteps()
@@ -52,81 +51,15 @@ namespace osu.Game.Tests.Visual.Gameplay
{
replay = new Replay();
- Add(new GridContainer
+ gameplayState = TestGameplayState.Create(new OsuRuleset());
+ gameplayState.Score.Replay = replay;
+
+ Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
- Content = new[]
- {
- new Drawable[]
- {
- recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
- {
- Recorder = recorder = new TestReplayRecorder(new Score
- {
- Replay = replay,
- ScoreInfo =
- {
- BeatmapInfo = gameplayState.Beatmap.BeatmapInfo,
- Ruleset = new OsuRuleset().RulesetInfo,
- }
- })
- {
- ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
- },
- Child = new Container
- {
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- new Box
- {
- Colour = Color4.Brown,
- RelativeSizeAxes = Axes.Both,
- },
- new OsuSpriteText
- {
- Text = "Recording",
- Scale = new Vector2(3),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- },
- new TestInputConsumer()
- }
- },
- }
- },
- new Drawable[]
- {
- playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
- {
- ReplayInputHandler = new TestFramedReplayInputHandler(replay)
- {
- GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
- },
- Child = new Container
- {
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- new Box
- {
- Colour = Color4.DarkBlue,
- RelativeSizeAxes = Axes.Both,
- },
- new OsuSpriteText
- {
- Text = "Playback",
- Scale = new Vector2(3),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- },
- new TestInputConsumer()
- }
- },
- }
- }
- }
- });
+ CachedDependencies = new (Type, object)[] { (typeof(GameplayState), gameplayState) },
+ Child = createContent(),
+ };
});
}
@@ -203,6 +136,74 @@ namespace osu.Game.Tests.Visual.Gameplay
recorder = null;
}
+ private Drawable createContent() => new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
+ {
+ Recorder = recorder = new TestReplayRecorder(gameplayState.Score)
+ {
+ ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
+ },
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Brown,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Text = "Recording",
+ Scale = new Vector2(3),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new TestInputConsumer()
+ }
+ },
+ }
+ },
+ new Drawable[]
+ {
+ playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
+ {
+ ReplayInputHandler = new TestFramedReplayInputHandler(replay)
+ {
+ GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
+ },
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.DarkBlue,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Text = "Playback",
+ Scale = new Vector2(3),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new TestInputConsumer()
+ }
+ },
+ }
+ }
+ }
+ };
+
public class TestFramedReplayInputHandler : FramedReplayInputHandler
{
public TestFramedReplayInputHandler(Replay replay)
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
index f319290441..bd274dfef5 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
@@ -14,6 +14,7 @@ using osu.Game.Overlays.Settings;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
+using osu.Game.Skinning;
using osu.Game.Skinning.Editor;
using osuTK.Input;
@@ -33,6 +34,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{
base.SetUpSteps();
+ AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded));
+
AddStep("reload skin editor", () =>
{
skinEditor?.Expire();
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
index eacab6d34f..ef56f456ea 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
@@ -10,6 +10,7 @@ using osu.Framework.Testing;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD;
+using osu.Game.Skinning;
namespace osu.Game.Tests.Visual.Gameplay
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
index e1fc65404d..5c73db15df 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
@@ -33,7 +33,6 @@ namespace osu.Game.Tests.Visual.Gameplay
increment = skip_time;
var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
- working.LoadTrack();
Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0)
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
index 07efb25b46..9eb71b9cf7 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
@@ -1,161 +1,76 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
-using osu.Framework.Utils;
-using osu.Framework.Timing;
-using osu.Game.Graphics;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.HUD;
+using osu.Game.Skinning;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
- public class TestSceneSongProgress : OsuTestScene
+ public class TestSceneSongProgress : SkinnableHUDComponentTestScene
{
- private SongProgress progress;
- private TestSongProgressGraph graph;
- private readonly Container progressContainer;
+ private GameplayClockContainer gameplayClockContainer = null!;
- private readonly StopwatchClock clock;
- private readonly FramedClock framedClock;
+ private const double skip_target_time = -2000;
- [Cached]
- private readonly GameplayClock gameplayClock;
-
- public TestSceneSongProgress()
+ [BackgroundDependencyLoader]
+ private void load()
{
- clock = new StopwatchClock();
- gameplayClock = new GameplayClock(framedClock = new FramedClock(clock));
+ Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
- Add(progressContainer = new Container
- {
- RelativeSizeAxes = Axes.X,
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
- Height = 100,
- Y = -100,
- Child = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = OsuColour.Gray(1),
- }
- });
+ Add(gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time));
+
+ Dependencies.CacheAs(gameplayClockContainer.GameplayClock);
}
[SetUpSteps]
public void SetupSteps()
{
- AddStep("add new song progress", () =>
- {
- if (progress != null)
- {
- progress.Expire();
- progress = null;
- }
-
- progressContainer.Add(progress = new SongProgress
- {
- RelativeSizeAxes = Axes.X,
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- });
- });
-
- AddStep("add new big graph", () =>
- {
- if (graph != null)
- {
- graph.Expire();
- graph = null;
- }
-
- Add(graph = new TestSongProgressGraph
- {
- RelativeSizeAxes = Axes.X,
- Height = 200,
- Anchor = Anchor.TopLeft,
- Origin = Anchor.TopLeft,
- });
- });
-
- AddStep("reset clock", clock.Reset);
- }
-
- [Test]
- public void TestGraphRecreation()
- {
- AddAssert("ensure not created", () => graph.CreationCount == 0);
- AddStep("display values", displayRandomValues);
- AddUntilStep("wait for creation count", () => graph.CreationCount == 1);
- AddRepeatStep("new values", displayRandomValues, 5);
- AddWaitStep("wait some", 5);
- AddAssert("ensure recreation debounced", () => graph.CreationCount == 2);
+ AddStep("reset clock", () => gameplayClockContainer.Reset());
+ AddStep("set hit objects", setHitObjects);
}
[Test]
public void TestDisplay()
{
- AddStep("display max values", displayMaxValues);
- AddUntilStep("wait for graph", () => graph.CreationCount == 1);
- AddStep("start", clock.Start);
- AddStep("allow seeking", () => progress.AllowSeeking.Value = true);
- AddStep("hide graph", () => progress.ShowGraph.Value = false);
- AddStep("disallow seeking", () => progress.AllowSeeking.Value = false);
- AddStep("allow seeking", () => progress.AllowSeeking.Value = true);
- AddStep("show graph", () => progress.ShowGraph.Value = true);
- AddStep("stop", clock.Stop);
+ AddStep("seek to intro", () => gameplayClockContainer.Seek(skip_target_time));
+ AddStep("start", gameplayClockContainer.Start);
+ AddStep("stop", gameplayClockContainer.Stop);
}
- private void displayRandomValues()
+ [Test]
+ public void TestToggleSeeking()
{
- var objects = new List();
- for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000)
- objects.Add(new HitObject { StartTime = i });
+ DefaultSongProgress getDefaultProgress() => this.ChildrenOfType().Single();
- replaceObjects(objects);
+ AddStep("allow seeking", () => getDefaultProgress().AllowSeeking.Value = true);
+ AddStep("hide graph", () => getDefaultProgress().ShowGraph.Value = false);
+ AddStep("disallow seeking", () => getDefaultProgress().AllowSeeking.Value = false);
+ AddStep("allow seeking", () => getDefaultProgress().AllowSeeking.Value = true);
+ AddStep("show graph", () => getDefaultProgress().ShowGraph.Value = true);
}
- private void displayMaxValues()
+ private void setHitObjects()
{
var objects = new List();
for (double i = 0; i < 5000; i++)
objects.Add(new HitObject { StartTime = i });
- replaceObjects(objects);
+ this.ChildrenOfType().ForEach(progress => progress.Objects = objects);
}
- private void replaceObjects(List objects)
- {
- progress.Objects = objects;
- graph.Objects = objects;
+ protected override Drawable CreateDefaultImplementation() => new DefaultSongProgress();
- progress.RequestSeek = pos => clock.Seek(pos);
- }
-
- protected override void Update()
- {
- base.Update();
- framedClock.ProcessFrame();
- }
-
- private class TestSongProgressGraph : SongProgressGraph
- {
- public int CreationCount { get; private set; }
-
- protected override void RecreateGraph()
- {
- base.RecreateGraph();
- CreationCount++;
- }
- }
+ protected override Drawable CreateLegacyImplementation() => new LegacySongProgress();
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgressGraph.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgressGraph.cs
new file mode 100644
index 0000000000..2fa3c0c7ec
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgressGraph.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.
+
+#nullable disable
+
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Testing;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Screens.Play.HUD;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ [TestFixture]
+ public class TestSceneSongProgressGraph : OsuTestScene
+ {
+ private TestSongProgressGraph graph;
+
+ [SetUpSteps]
+ public void SetupSteps()
+ {
+ AddStep("add new big graph", () =>
+ {
+ if (graph != null)
+ {
+ graph.Expire();
+ graph = null;
+ }
+
+ Add(graph = new TestSongProgressGraph
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 200,
+ Anchor = Anchor.TopLeft,
+ Origin = Anchor.TopLeft,
+ });
+ });
+ }
+
+ [Test]
+ public void TestGraphRecreation()
+ {
+ AddAssert("ensure not created", () => graph.CreationCount == 0);
+ AddStep("display values", displayRandomValues);
+ AddUntilStep("wait for creation count", () => graph.CreationCount == 1);
+ AddRepeatStep("new values", displayRandomValues, 5);
+ AddWaitStep("wait some", 5);
+ AddAssert("ensure recreation debounced", () => graph.CreationCount == 2);
+ }
+
+ private void displayRandomValues()
+ {
+ var objects = new List();
+ for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000)
+ objects.Add(new HitObject { StartTime = i });
+
+ graph.Objects = objects;
+ }
+
+ private class TestSongProgressGraph : SongProgressGraph
+ {
+ public int CreationCount { get; private set; }
+
+ protected override void RecreateGraph()
+ {
+ base.RecreateGraph();
+ CreationCount++;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs
index c55e98c1a8..9ad8ac086c 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs
@@ -5,6 +5,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Screens;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Spectator;
@@ -43,6 +44,21 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchedUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID);
}
+ [Test]
+ public void TestRestart()
+ {
+ AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
+
+ AddStep("exit player", () => Player.Exit());
+ AddStep("reload player", LoadPlayer);
+ AddUntilStep("wait for player load", () => Player.IsLoaded && Player.Alpha == 1);
+
+ AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
+
+ AddWaitStep("wait", 5);
+ AddUntilStep("spectator client still sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
+ }
+
public override void TearDownSteps()
{
base.TearDownSteps();
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
index 5fad661e9b..9c41c70a0e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
@@ -27,7 +27,6 @@ using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
-using osu.Game.Screens.Play;
using osu.Game.Tests.Gameplay;
using osu.Game.Tests.Mods;
using osu.Game.Tests.Visual.Spectator;
@@ -41,16 +40,12 @@ namespace osu.Game.Tests.Visual.Gameplay
private TestRulesetInputManager playbackManager;
private TestRulesetInputManager recordingManager;
- private Replay replay;
-
+ private Score recordingScore;
+ private Replay playbackReplay;
private TestSpectatorClient spectatorClient;
-
private ManualClock manualClock;
-
private TestReplayRecorder recorder;
-
private OsuSpriteText latencyDisplay;
-
private TestFramedReplayInputHandler replayHandler;
[SetUpSteps]
@@ -58,7 +53,16 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("Setup containers", () =>
{
- replay = new Replay();
+ recordingScore = new Score
+ {
+ ScoreInfo =
+ {
+ BeatmapInfo = new BeatmapInfo(),
+ Ruleset = new OsuRuleset().RulesetInfo,
+ }
+ };
+
+ playbackReplay = new Replay();
manualClock = new ManualClock();
Child = new DependencyProvidingContainer
@@ -67,7 +71,6 @@ namespace osu.Game.Tests.Visual.Gameplay
CachedDependencies = new[]
{
(typeof(SpectatorClient), (object)(spectatorClient = new TestSpectatorClient())),
- (typeof(GameplayState), TestGameplayState.Create(new OsuRuleset()))
},
Children = new Drawable[]
{
@@ -81,7 +84,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
- Recorder = recorder = new TestReplayRecorder
+ Recorder = recorder = new TestReplayRecorder(recordingScore)
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
},
@@ -112,7 +115,7 @@ namespace osu.Game.Tests.Visual.Gameplay
playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
Clock = new FramedClock(manualClock),
- ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay)
+ ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(playbackReplay)
{
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
},
@@ -144,6 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}
};
+ spectatorClient.BeginPlaying(TestGameplayState.Create(new OsuRuleset()), recordingScore);
spectatorClient.OnNewFrames += onNewFrames;
});
}
@@ -151,15 +155,15 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestBasic()
{
- AddUntilStep("received frames", () => replay.Frames.Count > 50);
+ AddUntilStep("received frames", () => playbackReplay.Frames.Count > 50);
AddStep("stop sending frames", () => recorder.Expire());
- AddUntilStep("wait for all frames received", () => replay.Frames.Count == recorder.SentFrames.Count);
+ AddUntilStep("wait for all frames received", () => playbackReplay.Frames.Count == recorder.SentFrames.Count);
}
[Test]
public void TestWithSendFailure()
{
- AddUntilStep("received frames", () => replay.Frames.Count > 50);
+ AddUntilStep("received frames", () => playbackReplay.Frames.Count > 50);
int framesReceivedSoFar = 0;
int frameSendAttemptsSoFar = 0;
@@ -172,21 +176,21 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for next send attempt", () =>
{
- framesReceivedSoFar = replay.Frames.Count;
+ framesReceivedSoFar = playbackReplay.Frames.Count;
return spectatorClient.FrameSendAttempts > frameSendAttemptsSoFar + 1;
});
AddUntilStep("wait for more send attempts", () => spectatorClient.FrameSendAttempts > frameSendAttemptsSoFar + 10);
- AddAssert("frames did not increase", () => framesReceivedSoFar == replay.Frames.Count);
+ AddAssert("frames did not increase", () => framesReceivedSoFar == playbackReplay.Frames.Count);
AddStep("stop failing sends", () => spectatorClient.ShouldFailSendingFrames = false);
- AddUntilStep("wait for next frames", () => framesReceivedSoFar < replay.Frames.Count);
+ AddUntilStep("wait for next frames", () => framesReceivedSoFar < playbackReplay.Frames.Count);
AddStep("stop sending frames", () => recorder.Expire());
- AddUntilStep("wait for all frames received", () => replay.Frames.Count == recorder.SentFrames.Count);
- AddAssert("ensure frames were received in the correct sequence", () => replay.Frames.Select(f => f.Time).SequenceEqual(recorder.SentFrames.Select(f => f.Time)));
+ AddUntilStep("wait for all frames received", () => playbackReplay.Frames.Count == recorder.SentFrames.Count);
+ AddAssert("ensure frames were received in the correct sequence", () => playbackReplay.Frames.Select(f => f.Time).SequenceEqual(recorder.SentFrames.Select(f => f.Time)));
}
private void onNewFrames(int userId, FrameDataBundle frames)
@@ -195,10 +199,10 @@ namespace osu.Game.Tests.Visual.Gameplay
{
var frame = new TestReplayFrame();
frame.FromLegacy(legacyFrame, null);
- replay.Frames.Add(frame);
+ playbackReplay.Frames.Add(frame);
}
- Logger.Log($"Received {frames.Frames.Count} new frames (total {replay.Frames.Count} of {recorder.SentFrames.Count})");
+ Logger.Log($"Received {frames.Frames.Count} new frames (total {playbackReplay.Frames.Count} of {recorder.SentFrames.Count})");
}
private double latency = SpectatorClient.TIME_BETWEEN_SENDS;
@@ -219,7 +223,7 @@ namespace osu.Game.Tests.Visual.Gameplay
if (!replayHandler.HasFrames)
return;
- var lastFrame = replay.Frames.LastOrDefault();
+ var lastFrame = playbackReplay.Frames.LastOrDefault();
// this isn't perfect as we basically can't be aware of the rate-of-send here (the streamer is not sending data when not being moved).
// in gameplay playback, the case where NextFrame is null would pause gameplay and handle this correctly; it's strictly a test limitation / best effort implementation.
@@ -360,15 +364,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public List SentFrames = new List();
- public TestReplayRecorder()
- : base(new Score
- {
- ScoreInfo =
- {
- BeatmapInfo = new BeatmapInfo(),
- Ruleset = new OsuRuleset().RulesetInfo,
- }
- })
+ public TestReplayRecorder(Score score)
+ : base(score)
{
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs
index 2b461cf6f6..ca4d926866 100644
--- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs
@@ -35,7 +35,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
protected IScreen CurrentSubScreen => multiplayerComponents.MultiplayerScreen.CurrentSubScreen;
private BeatmapManager beatmaps;
- private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
private TestMultiplayerComponents multiplayerComponents;
@@ -45,8 +44,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
- Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(new RealmRulesetStore(Realm));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs
index 0a59e0e858..b26481387d 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs
@@ -19,29 +19,33 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
private DrawableRoomParticipantsList list;
- [SetUp]
- public new void Setup() => Schedule(() =>
+ public override void SetUpSteps()
{
- SelectedRoom.Value = new Room
- {
- Name = { Value = "test room" },
- Host =
- {
- Value = new APIUser
- {
- Id = 2,
- Username = "peppy",
- }
- }
- };
+ base.SetUpSteps();
- Child = list = new DrawableRoomParticipantsList
+ AddStep("create list", () =>
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- NumberOfCircles = 4
- };
- });
+ SelectedRoom.Value = new Room
+ {
+ Name = { Value = "test room" },
+ Host =
+ {
+ Value = new APIUser
+ {
+ Id = 2,
+ Username = "peppy",
+ }
+ }
+ };
+
+ Child = list = new DrawableRoomParticipantsList
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ NumberOfCircles = 4
+ };
+ });
+ }
[Test]
public void TestCircleCountNearLimit()
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
index 1797c82fb9..73d1222156 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
@@ -38,13 +38,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
private TestPlaylist playlist;
private BeatmapManager manager;
- private RulesetStore rulesets;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(new RealmRulesetStore(Realm));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
index 82e7bf8969..3d6d4f0a90 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
@@ -25,23 +25,27 @@ namespace osu.Game.Tests.Visual.Multiplayer
private RoomsContainer container;
- [SetUp]
- public new void Setup() => Schedule(() =>
+ public override void SetUpSteps()
{
- Child = new PopoverContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Width = 0.5f,
+ base.SetUpSteps();
- Child = container = new RoomsContainer
+ AddStep("create container", () =>
+ {
+ Child = new PopoverContainer
{
- SelectedRoom = { BindTarget = SelectedRoom }
- }
- };
- });
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Width = 0.5f,
+
+ Child = container = new RoomsContainer
+ {
+ SelectedRoom = { BindTarget = SelectedRoom }
+ }
+ };
+ });
+ }
[Test]
public void TestBasicListChanges()
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs
index 8cdcdfdfdf..b113352117 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs
@@ -3,7 +3,6 @@
#nullable disable
-using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
@@ -18,19 +17,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchBeatmapDetailArea : OnlinePlayTestScene
{
- [SetUp]
- public new void Setup() => Schedule(() =>
+ public override void SetUpSteps()
{
- SelectedRoom.Value = new Room();
+ base.SetUpSteps();
- Child = new MatchBeatmapDetailArea
+ AddStep("create area", () =>
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(500),
- CreateNewItem = createNewItem
- };
- });
+ SelectedRoom.Value = new Room();
+
+ Child = new MatchBeatmapDetailArea
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(500),
+ CreateNewItem = createNewItem
+ };
+ });
+ }
private void createNewItem()
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
index 506d7541a7..d2468ae005 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
@@ -4,8 +4,6 @@
#nullable disable
using System.Collections.Generic;
-using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
@@ -19,59 +17,62 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchLeaderboard : OnlinePlayTestScene
{
- [BackgroundDependencyLoader]
- private void load()
+ public override void SetUpSteps()
{
- ((DummyAPIAccess)API).HandleRequest = r =>
+ base.SetUpSteps();
+
+ AddStep("setup API", () =>
{
- switch (r)
+ ((DummyAPIAccess)API).HandleRequest = r =>
{
- case GetRoomLeaderboardRequest leaderboardRequest:
- leaderboardRequest.TriggerSuccess(new APILeaderboard
- {
- Leaderboard = new List
+ switch (r)
+ {
+ case GetRoomLeaderboardRequest leaderboardRequest:
+ leaderboardRequest.TriggerSuccess(new APILeaderboard
{
- new APIUserScoreAggregate
+ Leaderboard = new List
{
- UserID = 2,
- User = new APIUser { Id = 2, Username = "peppy" },
- TotalScore = 995533,
- RoomID = 3,
- CompletedBeatmaps = 1,
- TotalAttempts = 6,
- Accuracy = 0.9851
- },
- new APIUserScoreAggregate
- {
- UserID = 1040328,
- User = new APIUser { Id = 1040328, Username = "smoogipoo" },
- TotalScore = 981100,
- RoomID = 3,
- CompletedBeatmaps = 1,
- TotalAttempts = 9,
- Accuracy = 0.937
+ new APIUserScoreAggregate
+ {
+ UserID = 2,
+ User = new APIUser { Id = 2, Username = "peppy" },
+ TotalScore = 995533,
+ RoomID = 3,
+ CompletedBeatmaps = 1,
+ TotalAttempts = 6,
+ Accuracy = 0.9851
+ },
+ new APIUserScoreAggregate
+ {
+ UserID = 1040328,
+ User = new APIUser { Id = 1040328, Username = "smoogipoo" },
+ TotalScore = 981100,
+ RoomID = 3,
+ CompletedBeatmaps = 1,
+ TotalAttempts = 9,
+ Accuracy = 0.937
+ }
}
- }
- });
- return true;
- }
+ });
+ return true;
+ }
- return false;
- };
- }
+ return false;
+ };
+ });
- [SetUp]
- public new void Setup() => Schedule(() =>
- {
- SelectedRoom.Value = new Room { RoomID = { Value = 3 } };
-
- Child = new MatchLeaderboard
+ AddStep("create leaderboard", () =>
{
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- Size = new Vector2(550f, 450f),
- Scope = MatchLeaderboardScope.Overall,
- };
- });
+ SelectedRoom.Value = new Room { RoomID = { Value = 3 } };
+
+ Child = new MatchLeaderboard
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ Size = new Vector2(550f, 450f),
+ Scope = MatchLeaderboardScope.Overall,
+ };
+ });
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
index 80c356ec67..9e6941738a 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
@@ -22,8 +22,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
private MultiSpectatorLeaderboard leaderboard;
[SetUpSteps]
- public new void SetUpSteps()
+ public override void SetUpSteps()
{
+ base.SetUpSteps();
+
AddStep("reset", () =>
{
leaderboard?.RemoveAndDisposeImmediately();
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
index 7df68392cf..d626426e6d 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
@@ -56,8 +56,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
importedBeatmapId = importedBeatmap.OnlineID;
}
- [SetUp]
- public new void Setup() => Schedule(() => playingUsers.Clear());
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+
+ AddStep("clear playing users", () => playingUsers.Clear());
+ }
[Test]
public void TestDelayedStart()
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index a2793acba7..269867be73 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -24,7 +24,6 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
-using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mods;
@@ -50,7 +49,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
public class TestSceneMultiplayer : ScreenTestScene
{
private BeatmapManager beatmaps = null!;
- private RulesetStore rulesets = null!;
private BeatmapSetInfo importedSet = null!;
private TestMultiplayerComponents multiplayerComponents = null!;
@@ -64,8 +62,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
- Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(new RealmRulesetStore(Realm));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
}
@@ -402,16 +400,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestPlayStartsWithCorrectBeatmapWhileAtSongSelect()
{
- createRoom(() => new Room
+ PlaylistItem? item = null;
+ createRoom(() =>
{
- Name = { Value = "Test Room" },
- Playlist =
+ item = new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
- new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
- {
- RulesetID = new OsuRuleset().RulesetInfo.OnlineID
- }
- }
+ RulesetID = new OsuRuleset().RulesetInfo.OnlineID
+ };
+ return new Room
+ {
+ Name = { Value = "Test Room" },
+ Playlist = { item }
+ };
});
pressReadyButton();
@@ -419,7 +419,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("Enter song select", () =>
{
var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen;
- ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId);
+ ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(item);
});
AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true);
@@ -440,16 +440,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestPlayStartsWithCorrectRulesetWhileAtSongSelect()
{
- createRoom(() => new Room
+ PlaylistItem? item = null;
+ createRoom(() =>
{
- Name = { Value = "Test Room" },
- Playlist =
+ item = new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
- new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
- {
- RulesetID = new OsuRuleset().RulesetInfo.OnlineID
- }
- }
+ RulesetID = new OsuRuleset().RulesetInfo.OnlineID
+ };
+ return new Room
+ {
+ Name = { Value = "Test Room" },
+ Playlist = { item }
+ };
});
pressReadyButton();
@@ -457,7 +459,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("Enter song select", () =>
{
var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen;
- ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId);
+ ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(item);
});
AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true);
@@ -478,16 +480,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestPlayStartsWithCorrectModsWhileAtSongSelect()
{
- createRoom(() => new Room
+ PlaylistItem? item = null;
+ createRoom(() =>
{
- Name = { Value = "Test Room" },
- Playlist =
+ item = new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
- new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
- {
- RulesetID = new OsuRuleset().RulesetInfo.OnlineID
- }
- }
+ RulesetID = new OsuRuleset().RulesetInfo.OnlineID
+ };
+ return new Room
+ {
+ Name = { Value = "Test Room" },
+ Playlist = { item }
+ };
});
pressReadyButton();
@@ -495,7 +499,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("Enter song select", () =>
{
var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen;
- ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId);
+ ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(item);
});
AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true);
@@ -627,7 +631,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("invoke on back button", () => multiplayerComponents.OnBackButton());
- AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden);
+ AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().UserModsSelectOverlay.State.Value == Visibility.Hidden);
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs
index a98030e1e3..83e7ef6a81 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs
@@ -3,7 +3,6 @@
#nullable disable
-using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
@@ -13,23 +12,27 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiplayerMatchFooter : MultiplayerTestScene
{
- [SetUp]
- public new void Setup() => Schedule(() =>
+ public override void SetUpSteps()
{
- Child = new PopoverContainer
+ base.SetUpSteps();
+
+ AddStep("create footer", () =>
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Child = new Container
+ Child = new PopoverContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.X,
- Height = 50,
- Child = new MultiplayerMatchFooter()
- }
- };
- });
+ RelativeSizeAxes = Axes.Both,
+ Child = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.X,
+ Height = 50,
+ Child = new MultiplayerMatchFooter()
+ }
+ };
+ });
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
index ab4f9c37b2..2281235f25 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray()));
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
index 5d6a6c8104..9fc42dc68b 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
@@ -4,6 +4,7 @@
#nullable disable
using System.Linq;
+using JetBrains.Annotations;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@@ -17,6 +18,8 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
+using osu.Game.Overlays;
+using osu.Game.Overlays.Dialog;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
@@ -24,6 +27,7 @@ using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Taiko;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Menu;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Screens.OnlinePlay.Multiplayer;
@@ -40,7 +44,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
private MultiplayerMatchSubScreen screen;
private BeatmapManager beatmaps;
- private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
public TestSceneMultiplayerMatchSubScreen()
@@ -51,8 +54,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
- Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(new RealmRulesetStore(Realm));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
@@ -60,16 +63,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
}
- [SetUp]
- public new void Setup() => Schedule(() =>
- {
- SelectedRoom.Value = new Room { Name = { Value = "Test Room" } };
- });
-
[SetUpSteps]
public void SetupSteps()
{
- AddStep("load match", () => LoadScreen(screen = new MultiplayerMatchSubScreen(SelectedRoom.Value)));
+ AddStep("load match", () =>
+ {
+ SelectedRoom.Value = new Room { Name = { Value = "Test Room" } };
+ LoadScreen(screen = new TestMultiplayerMatchSubScreen(SelectedRoom.Value));
+ });
+
AddUntilStep("wait for load", () => screen.IsCurrentScreen());
}
@@ -283,5 +285,29 @@ namespace osu.Game.Tests.Visual.Multiplayer
return lastItem.IsSelectedItem;
});
}
+
+ private class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen
+ {
+ [Resolved(canBeNull: true)]
+ [CanBeNull]
+ private IDialogOverlay dialogOverlay { get; set; }
+
+ public TestMultiplayerMatchSubScreen(Room room)
+ : base(room)
+ {
+ }
+
+ public override bool OnExiting(ScreenExitEvent e)
+ {
+ // For testing purposes allow the screen to exit without confirming on second attempt.
+ if (!ExitConfirmed && dialogOverlay?.CurrentDialog is ConfirmDiscardChangesDialog confirmDialog)
+ {
+ confirmDialog.PerformAction();
+ return true;
+ }
+
+ return base.OnExiting(e);
+ }
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs
index 5ee385810b..8dbad4e330 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs
@@ -31,33 +31,33 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
private MultiplayerPlaylist list;
private BeatmapManager beatmaps;
- private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
private BeatmapInfo importedBeatmap;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
- Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(new RealmRulesetStore(Realm));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
}
- [SetUp]
- public new void Setup() => Schedule(() =>
- {
- Child = list = new MultiplayerPlaylist
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Size = new Vector2(0.4f, 0.8f)
- };
- });
-
[SetUpSteps]
- public new void SetUpSteps()
+ public override void SetUpSteps()
{
+ base.SetUpSteps();
+
+ AddStep("create list", () =>
+ {
+ Child = list = new MultiplayerPlaylist
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.4f, 0.8f)
+ };
+ });
+
AddStep("import beatmap", () =>
{
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
index e709a955b3..f31261dc1f 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
@@ -29,15 +29,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
private MultiplayerQueueList playlist;
private BeatmapManager beatmaps;
- private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
private BeatmapInfo importedBeatmap;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
- Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(new RealmRulesetStore(Realm));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs
index 91c87548c7..9b4cb722f3 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs
@@ -35,55 +35,58 @@ namespace osu.Game.Tests.Visual.Multiplayer
private BeatmapSetInfo importedSet;
private BeatmapManager beatmaps;
- private RulesetStore rulesets;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
- Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(new RealmRulesetStore(Realm));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
}
- [SetUp]
- public new void Setup() => Schedule(() =>
+ public override void SetUpSteps()
{
- AvailabilityTracker.SelectedItem.BindTo(selectedItem);
+ base.SetUpSteps();
- importedSet = beatmaps.GetAllUsableBeatmapSets().First();
- Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
- selectedItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo)
+ AddStep("create button", () =>
{
- RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID,
- };
+ AvailabilityTracker.SelectedItem.BindTo(selectedItem);
- Child = new PopoverContainer
- {
- RelativeSizeAxes = Axes.Both,
- Child = new FillFlowContainer
+ importedSet = beatmaps.GetAllUsableBeatmapSets().First();
+ Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
+ selectedItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo)
{
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Vertical,
- Children = new Drawable[]
+ RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID,
+ };
+
+ Child = new PopoverContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new FillFlowContainer
{
- spectateButton = new MultiplayerSpectateButton
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(200, 50),
- },
- startControl = new MatchStartControl
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(200, 50),
+ spectateButton = new MultiplayerSpectateButton
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(200, 50),
+ },
+ startControl = new MatchStartControl
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(200, 50),
+ }
}
}
- }
- };
- });
+ };
+ });
+ }
[TestCase(MultiplayerRoomState.Open)]
[TestCase(MultiplayerRoomState.WaitingForLoad)]
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
index 88afe1ce7c..2eddf1a17e 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
@@ -28,15 +28,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
private BeatmapManager manager;
- private RulesetStore rulesets;
-
private TestPlaylistsSongSelect songSelect;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(new RealmRulesetStore(Realm));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
var beatmapSet = TestResources.CreateTestBeatmapSetInfo();
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs
index 321e0c2c89..5bccabcf2f 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs
@@ -14,17 +14,21 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneStarRatingRangeDisplay : OnlinePlayTestScene
{
- [SetUp]
- public new void Setup() => Schedule(() =>
+ public override void SetUpSteps()
{
- SelectedRoom.Value = new Room();
+ base.SetUpSteps();
- Child = new StarRatingRangeDisplay
+ AddStep("create display", () =>
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre
- };
- });
+ SelectedRoom.Value = new Room();
+
+ Child = new StarRatingRangeDisplay
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre
+ };
+ });
+ }
[Test]
public void TestRange([Values(0, 2, 3, 4, 6, 7)] double min, [Values(0, 2, 3, 4, 6, 7)] double max)
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
index d80537a2e5..ef2a431b8f 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
@@ -30,7 +30,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
public class TestSceneTeamVersus : ScreenTestScene
{
private BeatmapManager beatmaps;
- private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
private TestMultiplayerComponents multiplayerComponents;
@@ -40,8 +39,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
- Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(new RealmRulesetStore(Realm));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
}
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
index a61352f954..8fce43f9b0 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
@@ -26,6 +26,7 @@ using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Menu;
using osu.Game.Screens.OnlinePlay.Lounge;
+using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
@@ -45,6 +46,57 @@ namespace osu.Game.Tests.Visual.Navigation
private Vector2 optionsButtonPosition => Game.ToScreenSpace(new Vector2(click_padding, click_padding));
+ [TestCase(false)]
+ [TestCase(true)]
+ public void TestConfirmationRequiredToDiscardPlaylist(bool withPlaylistItemAdded)
+ {
+ Screens.OnlinePlay.Playlists.Playlists playlistScreen = null;
+
+ AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType().SingleOrDefault() != null);
+
+ PushAndConfirm(() => playlistScreen = new Screens.OnlinePlay.Playlists.Playlists());
+
+ AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
+
+ AddStep("open create screen", () =>
+ {
+ InputManager.MoveMouseTo(playlistScreen.ChildrenOfType().Single());
+ InputManager.Click(MouseButton.Left);
+ });
+
+ if (withPlaylistItemAdded)
+ {
+ AddUntilStep("wait for settings displayed",
+ () => (playlistScreen.CurrentSubScreen as PlaylistsRoomSubScreen)?.ChildrenOfType().SingleOrDefault()?.State.Value == Visibility.Visible);
+
+ AddStep("edit playlist", () => InputManager.Key(Key.Enter));
+
+ AddUntilStep("wait for song select", () => (playlistScreen.CurrentSubScreen as PlaylistsSongSelect)?.BeatmapSetsLoaded == true);
+
+ AddUntilStep("wait for selection", () => !Game.Beatmap.IsDefault);
+
+ AddStep("add item", () => InputManager.Key(Key.Enter));
+
+ AddUntilStep("wait for return to playlist screen", () => playlistScreen.CurrentSubScreen is PlaylistsRoomSubScreen);
+
+ pushEscape();
+ AddAssert("confirmation dialog shown", () => Game.ChildrenOfType().Single().CurrentDialog is not null);
+
+ AddStep("confirm exit", () => InputManager.Key(Key.Enter));
+
+ AddAssert("dialog dismissed", () => Game.ChildrenOfType().Single().CurrentDialog == null);
+
+ exitViaEscapeAndConfirm();
+ }
+ else
+ {
+ pushEscape();
+ AddAssert("confirmation dialog not shown", () => Game.ChildrenOfType().Single().CurrentDialog == null);
+
+ exitViaEscapeAndConfirm();
+ }
+ }
+
[Test]
public void TestExitSongSelectWithEscape()
{
@@ -74,14 +126,14 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("set filter again", () => songSelect.ChildrenOfType().Single().Current.Value = "test");
AddStep("open collections dropdown", () =>
{
- InputManager.MoveMouseTo(songSelect.ChildrenOfType().Single());
+ InputManager.MoveMouseTo(songSelect.ChildrenOfType().Single());
InputManager.Click(MouseButton.Left);
});
AddStep("press back once", () => InputManager.Click(MouseButton.Button1));
AddAssert("still at song select", () => Game.ScreenStack.CurrentScreen == songSelect);
AddAssert("collections dropdown closed", () => songSelect
- .ChildrenOfType().Single()
+ .ChildrenOfType().Single()
.ChildrenOfType.DropdownMenu>().Single().State == MenuState.Closed);
AddStep("press back a second time", () => InputManager.Click(MouseButton.Button1));
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
index 416e8aebcc..bb4823fb1d 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
@@ -16,6 +16,10 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapSet.Scores;
+using osu.Game.Resources.Localisation.Web;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Screens.Select.Details;
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
namespace osu.Game.Tests.Visual.Online
@@ -34,6 +38,9 @@ namespace osu.Game.Tests.Visual.Online
[Resolved]
private IRulesetStore rulesets { get; set; }
+ [SetUp]
+ public void SetUp() => Schedule(() => SelectedMods.Value = Array.Empty());
+
[Test]
public void TestLoading()
{
@@ -205,6 +212,21 @@ namespace osu.Game.Tests.Visual.Online
});
}
+ [Test]
+ public void TestSelectedModsDontAffectStatistics()
+ {
+ AddStep("show map", () => overlay.ShowBeatmapSet(getBeatmapSet()));
+ AddAssert("AR displayed as 0", () => overlay.ChildrenOfType().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value == (0, null));
+ AddStep("set AR10 diff adjust", () => SelectedMods.Value = new[]
+ {
+ new OsuModDifficultyAdjust
+ {
+ ApproachRate = { Value = 10 }
+ }
+ });
+ AddAssert("AR still displayed as 0", () => overlay.ChildrenOfType().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value == (0, null));
+ }
+
[Test]
public void TestHide()
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
index 4c39dc34d5..c5ac3dd442 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
@@ -108,6 +108,7 @@ namespace osu.Game.Tests.Visual.Online
Version = "2018.712.0",
DisplayVersion = "2018.712.0",
UpdateStream = streams[OsuGameBase.CLIENT_STREAM_NAME],
+ CreatedAt = new DateTime(2018, 7, 12),
ChangelogEntries = new List
{
new APIChangelogEntry
@@ -171,6 +172,7 @@ namespace osu.Game.Tests.Visual.Online
{
Version = "2019.920.0",
DisplayVersion = "2019.920.0",
+ CreatedAt = new DateTime(2019, 9, 20),
UpdateStream = new APIUpdateStream
{
Name = "Test",
diff --git a/osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs b/osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs
index fdcde0f2a5..4185d56833 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs
@@ -3,6 +3,8 @@
#nullable disable
+using osu.Framework.Graphics;
+using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
using osuTK;
@@ -12,9 +14,15 @@ namespace osu.Game.Tests.Visual.Online
{
public TestSceneExternalLinkButton()
{
- Child = new ExternalLinkButton("https://osu.ppy.sh/home")
+ Child = new OsuContextMenuContainer
{
- Size = new Vector2(50)
+ RelativeSizeAxes = Axes.Both,
+ Child = new ExternalLinkButton("https://osu.ppy.sh/home")
+ {
+ Size = new Vector2(50),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
};
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs
index beca3a8700..cfa9f77634 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs
@@ -141,6 +141,19 @@ namespace osu.Game.Tests.Visual.Online
AddUntilStep("best score not displayed", () => scoresContainer.ChildrenOfType().Count() == 1);
}
+ [Test]
+ public void TestUnprocessedPP()
+ {
+ AddStep("Load scores with unprocessed PP", () =>
+ {
+ var allScores = createScores();
+ allScores.Scores[0].PP = null;
+ allScores.UserScore = createUserBest();
+ allScores.UserScore.Score.PP = null;
+ scoresContainer.Scores = allScores;
+ });
+ }
+
private int onlineID = 1;
private APIScoresCollection createScores()
@@ -255,18 +268,25 @@ namespace osu.Game.Tests.Visual.Online
};
const int initial_great_count = 2000;
+ const int initial_tick_count = 100;
int greatCount = initial_great_count;
+ int tickCount = initial_tick_count;
foreach (var s in scores.Scores)
{
s.Statistics = new Dictionary
{
- { HitResult.Great, greatCount -= 100 },
+ { HitResult.Great, greatCount },
+ { HitResult.LargeTickHit, tickCount },
{ HitResult.Ok, RNG.Next(100) },
{ HitResult.Meh, RNG.Next(100) },
- { HitResult.Miss, initial_great_count - greatCount }
+ { HitResult.Miss, initial_great_count - greatCount },
+ { HitResult.LargeTickMiss, initial_tick_count - tickCount },
};
+
+ greatCount -= 100;
+ tickCount -= RNG.Next(1, 5);
}
return scores;
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs
index 0eb6ec3c04..4bbb72c862 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs
@@ -7,6 +7,7 @@ using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
@@ -99,6 +100,23 @@ namespace osu.Game.Tests.Visual.Online
Accuracy = 0.55879
};
+ var unprocessedPPScore = new SoloScoreInfo
+ {
+ Rank = ScoreRank.B,
+ Beatmap = new APIBeatmap
+ {
+ BeatmapSet = new APIBeatmapSet
+ {
+ Title = "C18H27NO3(extend)",
+ Artist = "Team Grimoire",
+ },
+ DifficultyName = "[4K] Cataclysmic Hypernova",
+ Status = BeatmapOnlineStatus.Ranked,
+ },
+ EndedAt = DateTimeOffset.Now,
+ Accuracy = 0.55879
+ };
+
Add(new FillFlowContainer
{
Anchor = Anchor.Centre,
@@ -112,6 +130,7 @@ namespace osu.Game.Tests.Visual.Online
new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(firstScore)),
new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(secondScore)),
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore)),
+ new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(unprocessedPPScore)),
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(firstScore, 0.97)),
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(secondScore, 0.85)),
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(thirdScore, 0.66)),
diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs
index 8889cb3e37..558bff2f3c 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs
@@ -3,6 +3,7 @@
#nullable disable
+using System;
using System.Net;
using NUnit.Framework;
using osu.Game.Online.API;
@@ -25,24 +26,40 @@ namespace osu.Game.Tests.Visual.Online
public void TestMainPage()
{
setUpWikiResponse(responseMainPage);
- AddStep("Show Main Page", () => wiki.Show());
+ AddStep("Show main page", () => wiki.Show());
}
[Test]
public void TestArticlePage()
{
setUpWikiResponse(responseArticlePage);
- AddStep("Show Article Page", () => wiki.ShowPage("Article_styling_criteria/Formatting"));
+ AddStep("Show article page", () => wiki.ShowPage("Article_styling_criteria/Formatting"));
+ }
+
+ [Test]
+ public void TestRedirection()
+ {
+ const string redirection_path = "Redirection_path_for_article";
+
+ setUpWikiResponse(responseArticlePage, redirection_path);
+ AddStep("Show article page", () => wiki.ShowPage(redirection_path));
+
+ AddUntilStep("Current page is article", () => wiki.Header.Current.Value == "Formatting");
+
+ setUpWikiResponse(responseArticleParentPage);
+ AddStep("Show parent page", () => wiki.Header.ShowParentPage?.Invoke());
+
+ AddUntilStep("Current page is parent", () => wiki.Header.Current.Value == "Article styling criteria");
}
[Test]
public void TestErrorPage()
{
- setUpWikiResponse(null, true);
+ setUpWikiResponse(responseArticlePage);
AddStep("Show Error Page", () => wiki.ShowPage("Error"));
}
- private void setUpWikiResponse(APIWikiPage r, bool isFailed = false)
+ private void setUpWikiResponse(APIWikiPage r, string redirectionPath = null)
=> AddStep("set up response", () =>
{
dummyAPI.HandleRequest = request =>
@@ -50,10 +67,13 @@ namespace osu.Game.Tests.Visual.Online
if (!(request is GetWikiRequest getWikiRequest))
return false;
- if (isFailed)
- getWikiRequest.TriggerFailure(new WebException());
- else
+ if (getWikiRequest.Path.Equals(r.Path, StringComparison.OrdinalIgnoreCase) ||
+ getWikiRequest.Path.Equals(redirectionPath, StringComparison.OrdinalIgnoreCase))
+ {
getWikiRequest.TriggerSuccess(r);
+ }
+ else
+ getWikiRequest.TriggerFailure(new WebException());
return true;
};
@@ -82,5 +102,17 @@ namespace osu.Game.Tests.Visual.Online
Markdown =
"# Formatting\n\n*For the writing standards, see: [Article style criteria/Writing](../Writing)*\n\n*Notice: This article uses [RFC 2119](https://tools.ietf.org/html/rfc2119 \"IETF Tools\") to describe requirement levels.*\n\n## Locales\n\nListed below are the properly-supported locales for the wiki:\n\n| File Name | Locale Name | Native Script |\n| :-- | :-- | :-- |\n| `en.md` | English | English |\n| `ar.md` | Arabic | اَلْعَرَبِيَّةُ |\n| `be.md` | Belarusian | Беларуская мова |\n| `bg.md` | Bulgarian | Български |\n| `cs.md` | Czech | Česky |\n| `da.md` | Danish | Dansk |\n| `de.md` | German | Deutsch |\n| `gr.md` | Greek | Ελληνικά |\n| `es.md` | Spanish | Español |\n| `fi.md` | Finnish | Suomi |\n| `fr.md` | French | Français |\n| `hu.md` | Hungarian | Magyar |\n| `id.md` | Indonesian | Bahasa Indonesia |\n| `it.md` | Italian | Italiano |\n| `ja.md` | Japanese | 日本語 |\n| `ko.md` | Korean | 한국어 |\n| `nl.md` | Dutch | Nederlands |\n| `no.md` | Norwegian | Norsk |\n| `pl.md` | Polish | Polski |\n| `pt.md` | Portuguese | Português |\n| `pt-br.md` | Brazilian Portuguese | Português (Brasil) |\n| `ro.md` | Romanian | Română |\n| `ru.md` | Russian | Русский |\n| `sk.md` | Slovak | Slovenčina |\n| `sv.md` | Swedish | Svenska |\n| `th.md` | Thai | ไทย |\n| `tr.md` | Turkish | Türkçe |\n| `uk.md` | Ukrainian | Українська мова |\n| `vi.md` | Vietnamese | Tiếng Việt |\n| `zh.md` | Chinese (Simplified) | 简体中文 |\n| `zh-tw.md` | Traditional Chinese (Taiwan) | 繁體中文(台灣) |\n\n*Note: The website will give readers their selected language's version of an article. If it is not available, the English version will be given.*\n\n### Content parity\n\nTranslations are subject to strict content parity with their English article, in the sense that they must have the same message, regardless of grammar and syntax. Any changes to the translations' meanings must be accompanied by equivalent changes to the English article.\n\nThere are some cases where the content is allowed to differ:\n\n- Articles originally written in a language other than English (in this case, English should act as the translation)\n- Explanations of English words that are common terms in the osu! community\n- External links\n- Tags\n- Subcommunity-specific explanations\n\n## Front matter\n\nFront matter must be placed at the very top of the file. It is written in [YAML](https://en.wikipedia.org/wiki/YAML#Example \"YAML Wikipedia article\") and describes additional information about the article. This must be surrounded by three hyphens (`---`) on the lines above and below it, and an empty line must follow it before the title heading.\n\n### Articles that need help\n\n*Note: Avoid translating English articles with this tag. In addition to this, this tag should be added when the translation needs its own clean up.*\n\nThe `needs_cleanup` tag may be added to articles that need rewriting or formatting help. It is also acceptable to open an issue on GitHub for this purpose. This tag must be written as shown below:\n\n```yaml\nneeds_cleanup: true\n```\n\nWhen adding this tag to an article, [comments](#comments) should also be added to explain what needs to be done to remove the tag.\n\n### Outdated articles\n\n*Note: Avoid translating English articles with this tag. If the English article has this tag, the translation must also have this tag.*\n\nTranslated articles that are outdated must use the `outdated` tag when the English variant is updated. English articles may also become outdated when the content they contain is misleading or no longer relevant. This tag must be written as shown below:\n\n```yaml\noutdated: true\n```\n\nWhen adding this tag to an article, [comments](#comments) should also be added to explain what needs to be updated to remove the tag.\n\n### Tagging articles\n\nTags help the website's search engine query articles better. Tags should be written in the same language as the article and include the original list of tags. Tags should use lowercase letters where applicable.\n\nFor example, an article called \"Beatmap discussion\" may include the following tags:\n\n```yaml\ntags:\n - beatmap discussions\n - modding V2\n - MV2\n```\n\n### Translations without reviews\n\n*Note: Wiki maintainers will determine and apply this mark prior to merging.*\n\nSometimes, translations are added to the wiki without review from other native speakers of the language. In this case, the `no_native_review` mark is added to let future translators know that it may need to be checked again. This tag must be written as shown below:\n\n```yaml\nno_native_review: true\n```\n\n## Article naming\n\n*See also: [Folder names](#folder-names) and [Titles](#titles)*\n\nArticle titles should be singular and use sentence case. See [Wikipedia's naming conventions article](https://en.wikipedia.org/wiki/Wikipedia:Naming_conventions_(plurals) \"Wikipedia\") for more details.\n\nArticle titles should match the folder name it is in (spaces may replace underscores (`_`) where appropriate). If the folder name changes, the article title should be changed to match it and vice versa.\n\n---\n\nContest and tournament articles are an exception. The folder name must use abbreviations, acronyms, or initialisms. The article's title must be the full name of the contest or tournament.\n\n## Folder and file structure\n\n### Folder names\n\n*See also: [Article naming](#article-naming)*\n\nFolder names must be in English and use sentence case.\n\nFolder names must only use these characters:\n\n- uppercase and lowercase letters\n- numbers\n- underscores (`_`)\n- hyphens (`-`)\n- exclamation marks (`!`)\n\n### Article file names\n\nThe file name of an article can be found in the `File Name` column of the [locales section](#locales). The location of a translated article must be placed in the same folder as the English article.\n\n### Index articles\n\nAn index article must be created if the folder is intended to only hold other articles. Index articles must contain a list of articles that are inside its own folder. They may also contain other information, such as a lead paragraph or descriptions of the linked articles.\n\n### Disambiguation articles\n\n[Disambiguation](/wiki/Disambiguation) articles must be placed in the `/wiki/Disambiguation` folder. The main page must be updated to include the disambiguation article. Refer to [Disambiguation/Mod](/wiki/Disambiguation/Mod) as an example.\n\nRedirects must be updated to have the ambiguous keyword(s) redirect to the disambiguation article.\n\nArticles linked from a disambiguation article must have a [For other uses](#for-other-uses) hatnote.\n\n## HTML\n\nHTML must not be used, with exception for [comments](#comments). The structure of the article must be redone if HTML is used.\n\n### Comments\n\nHTML comments should be used for marking to-dos, but may also be used to annotate text. They should be on their own line, but can be placed inline in a paragraph. If placed inline, the start of the comment must not have a space.\n\nBad example:\n\n```markdown\nHTML comments should be used for marking to-dos or annotate text.\n```\n\nGood example:\n\n```markdown\nHTML comments should be used for marking to-dos or annotate text.\n```\n\n## Editing\n\n### End of line sequence\n\n*Caution: Uploading Markdown files using `CRLF` (carriage return and line feed) via GitHub will result in those files using `CRLF`. To prevent this, set the line ending to `LF` (line feed) before uploading.*\n\nMarkdown files must be checked in using the `LF` end of line sequence.\n\n### Escaping\n\nMarkdown syntax should be escaped as needed. However, article titles are parsed as plain text and so must not be escaped.\n\n### Paragraphs\n\nEach paragraph must be followed by one empty line.\n\n### Line breaks\n\nLine breaks must use a backslash (`\\`).\n\nLine breaks must be used sparingly.\n\n## Hatnote\n\n*Not to be confused with [Notice](#notice).*\n\nHatnotes are short notes placed at the top of an article or section to help readers navigate to related articles or inform them about related topics.\n\nHatnotes must be italicised and be placed immediately after the heading. If multiple hatnotes are used, they must be on the same paragraph separated with a line break.\n\n### Main page\n\n*Main page* hatnotes direct the reader to the main article of a topic. When this hatnote is used, it implies that the section it is on is a summary of what the linked page is about. This hatnote should have only one link. These must be formatted as follows:\n\n```markdown\n*Main page: {article}*\n\n*Main pages: {article} and {article}*\n```\n\n### See also\n\n*See also* hatnotes suggest to readers other points of interest from a given article or section. These must be formatted as follows:\n\n```markdown\n*See also: {article}*\n\n*See also: {article} and {article}*\n```\n\n### For see\n\n*For see* hatnotes are similar to *see also* hatnotes, but are generally more descriptive and direct. This hatnote may use more than one link if necessary. These must be formatted as follows:\n\n```markdown\n*For {description}, see: {article}`*\n\n*For {description}, see: {article} and {article}`*\n```\n\n### Not to be confused with\n\n*Not to be confused with* hatnotes help distinguish ambiguous or misunderstood article titles or sections. This hatnote may use more than one link if necessary. These must be formatted as follows:\n\n```markdown\n*Not to be confused with {article}.*\n\n*Not to be confused with {article} or {article}.*\n```\n\n### For other uses\n\n*For other uses* hatnotes are similar to *not to be confused with* hatnotes, but links directly to the [disambiguation article](#disambiguation-articles). This hatnote must only link to the disambiguation article. These must be formatted as follows:\n\n```markdown\n*For other uses, see {disambiguation article}.*\n```\n\n## Notice\n\n*Not to be confused with [Hatnote](#hatnote).*\n\nA notice should be placed where appropriate in a section, but must start off the paragraph and use italics. Notices may contain bolding where appropriate, but should be kept to a minimum. Notices must be written as complete sentences. Thus, unlike most [hatnotes](#hatnotes), must use a full stop (`.`) or an exclamation mark (`!`) if appropriate. Anything within the same paragraph of a notice must also be italicised. These must be formatted as follows:\n\n```markdown\n*Note: {note}.*\n\n*Notice: {notice}.*\n\n*Caution: {caution}.*\n\n*Warning: {warning}.*\n```\n\n- `Note` should be used for factual or trivial details.\n- `Notice` should be used for reminders or to draw attention to something that the reader should be made aware of.\n- `Caution` should be used to warn the reader to avoid unintended consequences.\n- `Warning` should be used to warn the reader that action may be taken against them.\n\n## Emphasising\n\n### Bold\n\nBold must use double asterisks (`**`).\n\nLead paragraphs may bold the first occurrence of the article's title.\n\n### Italics\n\nItalics must use single asterisks (`*`).\n\nNames of work or video games should be italicised. osu!—the game—is exempt from this.\n\nThe first occurrence of an abbreviation, acronym, or initialism may be italicised.\n\nItalics may also be used to provide emphasis or help with readability.\n\n## Headings\n\nAll headings must use sentence case.\n\nHeadings must use the [ATX (hash) style](https://github.github.com/gfm/#atx-headings \"GitHub\") and must have an empty line before and after the heading. The title heading is an exception when it is on the first line. If this is the case, there only needs to be an empty line after the title heading.\n\nHeadings must not exceed a heading level of 5 and must not be used to style or format text.\n\n### Titles\n\n*See also: [Article naming](#article-naming)*\n\n*Caution: Titles are parsed as plain text; they must not be escaped.*\n\nThe first heading in all articles must be a level 1 heading, being the article's title. All headings afterwards must be [section headings](#sections). Titles must not contain formatting, links, or images.\n\nThe title heading must be on the first line, unless [front matter](#front-matter) is being used. If that is the case, the title heading must go after it and have an empty line before the title heading.\n\n### Sections\n\nSection headings must use levels 2 to 5. The section heading proceeding the [title heading](#titles) must be a level 2 heading. Unlike titles, section headings may have small image icons.\n\nSection headings must not skip a heading level (i.e. do not go from a level 2 heading to a level 4 heading) and must not contain formatting or links.\n\n*Notice: On the website, heading levels 4 and 5 will not appear in the table of contents. They cannot be linked to directly either.*\n\n## Lists\n\nLists should not go over 4 levels of indentation and should not have an empty line in between each item.\n\nFor nested lists, bullets or numbers must align with the item content of their parent lists.\n\nThe following example was done incorrectly (take note of the spacing before the bullet):\n\n```markdown\n1. Fly a kite\n - Don't fly a kite if it's raining\n```\n\nThe following example was done correctly:\n\n```markdown\n1. Fly a kite\n - Don't fly a kite if it's raining\n```\n\n### Bulleted\n\nBulleted lists must use a hyphen (`-`). These must then be followed by one space. (Example shown below.)\n\n```markdown\n- osu!\n - Hit circle\n - Combo number\n - Approach circle\n - Slider\n - Hit circles\n - Slider body\n - Slider ticks\n - Spinner\n- osu!taiko\n```\n\n### Numbered\n\nThe numbers in a numbered list must be incremented to represent their step.\n\n```markdown\n1. Download the osu! installer.\n2. Run the installer.\n 1. To change the installation location, click the text underneath the progression bar.\n 2. The installer will prompt for a new location, choose the installation folder.\n3. osu! will start up once installation is complete.\n4. Sign in.\n```\n\n### Mixed\n\nCombining both bulleted and numbered lists should be done sparingly.\n\n```markdown\n1. Download a skin from the forums.\n2. Load the skin file into osu!.\n - If the file is a `.zip`, unzip it and move the contents into the `Skins/` folder (found in your osu! installation folder).\n - If the file is a `.osk`, open it on your desktop or drag-and-drop it into the game client.\n3. Open osu!, if it is not opened, and select the skin in the options.\n - This may have been completed if you opened the `.osk` file or drag-and-dropped it into the game client.\n```\n\n## Code\n\nThe markup for code is a grave mark (`` ` ``). To put grave marks in code, use double grave marks instead. If the grave mark is at the start or end, pad it with one space. (Example shown below.)\n\n```markdown\n`` ` ``\n`` `Space` ``\n```\n\n### Keyboard keys\n\n*Notice: When denoting the letter itself, and not the keyboard key, use quotation marks instead.*\n\nWhen representing keyboard keys, use capital letters for single characters and title case for modifiers. Use the plus symbol (`+`) (without code) to represent key combinations. (Example shown below.)\n\n```markdown\npippi is spelt with a lowercase \"p\" like peppy.\n\nPress `Ctrl` + `O` to open the open dialog.\n```\n\nWhen representing a space or the spacebar, use `` `Space` ``.\n\n### Button and menu text\n\nWhen copying the text from a menu or button, the letter casing should be copied as it appears. (Example shown below.)\n\n```markdown\nThe `osu!direct` button is visible in the main menu on the right side, if you have an active osu!supporter tag.\n```\n\n### Folder and directory names\n\nWhen copying the name of a folder or directory, the letter casing should be copied as it appears, but prefer lowercased paths when possible. Directory paths must not be absolute (i.e. do not start the directory name from the drive letter or from the root folder). (Example shown below.)\n\n```markdown\nosu! is installed in the `AppData/Local` folder by default, unless specified otherwise during installation.\n```\n\n### Keywords and commands\n\nWhen copying a keyword or command, the letter casing should be copied as it appears or how someone normally would type it. If applicable, prefer lowercase letters. (Example shown below.)\n\n```markdown\nAs of now, the `Name` and `Author` commands in the skin configuration file (`skin.ini`) do nothing.\n```\n\n### File names\n\nWhen copying the name of a file, the letter casing should be copied as it appears. If applicable, prefer lowercase letters. (Example shown below.)\n\n```markdown\nTo play osu!, double click the `osu!.exe` icon.\n```\n\n### File extensions\n\n*Notice: File formats (not to be confused with file extensions) must be written in capital letters without the prefixed fullstop (`.`).*\n\nFile extensions must be prefixed with a fullstop (`.`) and be followed by the file extension in lowercase letters. (Example shown below.)\n\n```markdown\nThe JPG (or JPEG) file format has the `.jpg` (or `.jpeg`) extension.\n```\n\n### Chat channels\n\nWhen copying the name of a chat channel, start it with a hash (`#`), followed by the channel name in lowercase letters. (Example shown below.)\n\n```markdown\n`#lobby` is where you can advertise your multi room.\n```\n\n## Preformatted text (code blocks)\n\n*Notice: Syntax highlighting for preformatted text is not implemented on the website yet.*\n\nPreformatted text (also known as code blocks) must be fenced using three grave marks. They should set the language identifier for syntax highlighting.\n\n## Links\n\nThere are two types of links: inline and reference. Inline has two styles.\n\nThe following is an example of both inline styles:\n\n```markdown\n[Game Modifiers](/wiki/Game_Modifiers)\n\n\n```\n\nThe following is an example of the reference style:\n\n```markdown\n[Game Modifiers][game mods link]\n\n[game mods link]: /wiki/Game_Modifiers\n```\n\n---\n\nLinks must use the inline style if they are only referenced once. The inline angle brackets style should be avoided. References to reference links must be placed at the bottom of the article.\n\n### Internal links\n\n*Note: Internal links refer to links that stay inside the `https://osu.ppy.sh/` domain.*\n\n#### Wiki links\n\nAll links that point to an wiki article should start with `/wiki/` followed by the path to get to the article you are targeting. Relative links may also be used. Some examples include the following:\n\n```markdown\n[FAQ](/wiki/FAQ)\n[pippi](/wiki/Mascots#-pippi)\n[Beatmaps](../)\n[Pattern](./Pattern)\n```\n\nWiki links must not use redirects and must not have a trailing forward slash (`/`).\n\nBad examples include the following:\n\n```markdown\n[Article styling criteria](/wiki/ASC)\n[Developers](/wiki/Developers/)\n[Developers](/wiki/Developers/#game-client-developers)\n```\n\nGood examples include the following:\n\n```markdown\n[Article styling criteria](/wiki/Article_styling_criteria)\n[Developers](/wiki/Developers)\n[Developers](/wiki/Developers#game-client-developers)\n```\n\n##### Sub-article links\n\nWiki links that point to a sub-article should include the parent article's folder name in its link text. See the following example:\n\n```markdown\n*See also: [Beatmap Editor/Design](/wiki/Beatmap_Editor/Design)*\n```\n\n##### Section links\n\n*Notice: On the website, heading levels 4 and 5 are not given the id attribute. This means that they can not be linked to directly.*\n\nWiki links that point to a section of an article may use the section sign symbol (`§`). See the following example:\n\n```markdown\n*For timing rules, see: [Ranking Criteria § Timing](/wiki/Ranking_Criteria#timing)*\n```\n\n#### Other osu! links\n\nThe URL from the address bar of your web browser should be copied as it is when linking to other osu! web pages. The `https://osu.ppy.sh` part of the URL must be kept.\n\n##### User profiles\n\nAll usernames must be linked on first occurrence. Other occurrences are optional, but must be consistent throughout the entire article for all usernames. If it is difficult to determine the user's id, it may be skipped over.\n\nWhen linking to a user profile, the user's id number must be used. Use the new website (`https://osu.ppy.sh/users/{username})`) to get the user's id.\n\nThe link text of the user link should be the user's current name.\n\n##### Difficulties\n\nWhenever linking to a single difficulty, use this format as the link text:\n\n```\n{artist} - {title} ({creator}) [{difficuty_name}]\n```\n\nThe link must actually link to that difficulty. Beatmap difficulty URLs must be formatted as follows:\n\n```\nhttps://osu.ppy.sh/beatmapsets/{BeatmapSetID}#{mode}/{BeatmapID}\n```\n\nThe difficulty name may be left outside of the link text, but doing so must be consistent throughout the entire article.\n\n##### Beatmaps\n\nWhenever linking to a beatmap, use this format as the link text:\n\n```\n{artist} - {title} ({creator})\n```\n\nAll beatmap URLs must be formatted as follows:\n\n```\nhttps://osu.ppy.sh/beatmapsets/{BeatmapSetID}\n```\n\n### External links\n\n*Notice: External links refers to links that go outside the `https://osu.ppy.sh/` domain.*\n\nThe `https` protocol must be used, unless the site does not support it. External links must be a clean and direct link to a reputable source. The link text should be the title of the page it is linking to. The URL from the address bar of your web browser should be copied as it is when linking to other external pages.\n\nThere are no visual differences between external and osu! web links. Due to this, the website name should be included in the title text. See the following example:\n\n```markdown\n*For more information about music theory, see: [Music theory](https://en.wikipedia.org/wiki/Music_theory \"Wikipedia\")*\n```\n\n## Images\n\nThere are two types of image links: inline and reference. Examples:\n\n**Inline style:**\n\n```markdown\n\n```\n\n**Reference style:**\n\n```markdown\n![][flag_AU]\n\n[flag_AU]: /wiki/shared/flag/AU.gif\n```\n\nImages should use the inline linking style. References to reference links must be placed at the bottom of the article.\n\nImages must be placed in a folder named `img`, located in the article's folder. Images that are used in multiple articles should be stored in the `/wiki/shared/` folder.\n\n### Image caching\n\nImages on the website are cached for up to 60 days. The cached image is matched with the image link's URL.\n\nWhen updating an image, either change the image's name or append a query string to the URL. In both cases, all translations linking to the updated image should also be updated.\n\n### Formats and quality\n\nImages should use the JPG format at quality 8 (80 or 80%, depending on the program). If the image contains transparency or has text that must be readable, use the PNG format instead. If the image contains an animation, the GIF format can be used; however, this should be used sparingly as these may take longer to load or can be bigger then the [max file size](#file-size).\n\n### File size\n\nImages must be under 1 megabyte, otherwise they will fail to load. Downscaling and using JPG at 80% is almost always under the size limit.\n\nAll images should be optimised as much as possible. Use [jpeg-archive](https://github.com/danielgtaylor/jpeg-archive \"GitHub\") to compress JPEG images. For consistency, use the following command for jpeg-archive:\n\n```sh\njpeg-recompress -am smallfry