diff --git a/.editorconfig b/.editorconfig
index b5333ad8e7..8cdb92d11c 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -176,8 +176,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
#Style - C# 8 features
csharp_prefer_static_local_function = true:warning
csharp_prefer_simple_using_statement = true:silent
-csharp_style_prefer_index_operator = false:none
-csharp_style_prefer_range_operator = false:none
+csharp_style_prefer_index_operator = true:warning
+csharp_style_prefer_range_operator = true:warning
csharp_style_prefer_switch_expression = false:none
#Supressing roslyn built-in analyzers
diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset
new file mode 100644
index 0000000000..d497365f87
--- /dev/null
+++ b/CodeAnalysis/osu.ruleset
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index c0d740bac1..27a0bd0d48 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -18,7 +18,11 @@
+
+
+ $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset
+
true
$(NoWarn);CS1591
diff --git a/build/InspectCode.cake b/build/InspectCode.cake
index bd3fdf5f93..06c56dce87 100644
--- a/build/InspectCode.cake
+++ b/build/InspectCode.cake
@@ -1,5 +1,5 @@
#addin "nuget:?package=CodeFileSanity&version=0.0.33"
-#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.2.1"
+#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.3.0"
#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
diff --git a/global.json b/global.json
index 43bb34912a..6858d4044d 100644
--- a/global.json
+++ b/global.json
@@ -1,4 +1,9 @@
{
+ "sdk": {
+ "allowPrerelease": false,
+ "rollForward": "minor",
+ "version": "3.1.100"
+ },
"msbuild-sdks": {
"Microsoft.Build.Traversal": "2.0.24"
}
diff --git a/osu.Android.props b/osu.Android.props
index 239e1c2d31..abb3cc8244 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -53,7 +53,7 @@
-
+
diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs
new file mode 100644
index 0000000000..b53ca6161b
--- /dev/null
+++ b/osu.Desktop/DiscordRichPresence.cs
@@ -0,0 +1,120 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using DiscordRPC;
+using DiscordRPC.Message;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Logging;
+using osu.Game.Online.API;
+using osu.Game.Rulesets;
+using osu.Game.Users;
+using LogLevel = osu.Framework.Logging.LogLevel;
+using User = osu.Game.Users.User;
+
+namespace osu.Desktop
+{
+ internal class DiscordRichPresence : Component
+ {
+ private const string client_id = "367827983903490050";
+
+ private DiscordRpcClient client;
+
+ [Resolved]
+ private IBindable ruleset { get; set; }
+
+ private Bindable user;
+
+ private readonly IBindable status = new Bindable();
+ private readonly IBindable activity = new Bindable();
+
+ private readonly RichPresence presence = new RichPresence
+ {
+ Assets = new Assets { LargeImageKey = "osu_logo_lazer", }
+ };
+
+ [BackgroundDependencyLoader]
+ private void load(IAPIProvider provider)
+ {
+ client = new DiscordRpcClient(client_id)
+ {
+ SkipIdenticalPresence = false // handles better on discord IPC loss, see updateStatus call in onReady.
+ };
+
+ client.OnReady += onReady;
+ client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network, LogLevel.Error);
+ client.OnConnectionFailed += (_, e) => Logger.Log($"An connection occurred with Discord RPC Client: {e.Type}", LoggingTarget.Network, LogLevel.Error);
+
+ (user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u =>
+ {
+ status.UnbindBindings();
+ status.BindTo(u.NewValue.Status);
+
+ activity.UnbindBindings();
+ activity.BindTo(u.NewValue.Activity);
+ }, true);
+
+ ruleset.BindValueChanged(_ => updateStatus());
+ status.BindValueChanged(_ => updateStatus());
+ activity.BindValueChanged(_ => updateStatus());
+
+ client.Initialize();
+ }
+
+ private void onReady(object _, ReadyMessage __)
+ {
+ Logger.Log("Discord RPC Client ready.", LoggingTarget.Network, LogLevel.Debug);
+ updateStatus();
+ }
+
+ private void updateStatus()
+ {
+ if (status.Value is UserStatusOffline)
+ {
+ client.ClearPresence();
+ return;
+ }
+
+ if (status.Value is UserStatusOnline && activity.Value != null)
+ {
+ presence.State = activity.Value.Status;
+ presence.Details = getDetails(activity.Value);
+ }
+ else
+ {
+ presence.State = "Idle";
+ presence.Details = string.Empty;
+ }
+
+ // update user information
+ presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.Ranks.Global > 0 ? $" (rank #{user.Value.Statistics.Ranks.Global:N0})" : string.Empty);
+
+ // update ruleset
+ presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom";
+ presence.Assets.SmallImageText = ruleset.Value.Name;
+
+ client.SetPresence(presence);
+ }
+
+ private string getDetails(UserActivity activity)
+ {
+ switch (activity)
+ {
+ case UserActivity.SoloGame solo:
+ return solo.Beatmap.ToString();
+
+ case UserActivity.Editing edit:
+ return edit.Beatmap.ToString();
+ }
+
+ return string.Empty;
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ client.Dispose();
+ base.Dispose(isDisposing);
+ }
+ }
+}
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 66e7bb381c..f70cc24159 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -60,6 +60,8 @@ namespace osu.Desktop
else
Add(new SimpleUpdateManager());
}
+
+ LoadComponentAsync(new DiscordRichPresence(), Add);
}
protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen)
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 60cada3ae7..61299cc23f 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -29,6 +29,7 @@
+
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 506fa23fa9..065771bc4a 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -16,14 +16,20 @@ using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Difficulty;
+using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
+using System;
namespace osu.Game.Rulesets.Catch
{
public class CatchRuleset : Ruleset
{
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchRuleset(this, beatmap, mods);
+
+ public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new CatchScoreProcessor(beatmap);
+
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
@@ -51,7 +57,9 @@ namespace osu.Game.Rulesets.Catch
else if (mods.HasFlag(LegacyMods.SuddenDeath))
yield return new CatchModSuddenDeath();
- if (mods.HasFlag(LegacyMods.Autoplay))
+ if (mods.HasFlag(LegacyMods.Cinema))
+ yield return new CatchModCinema();
+ else if (mods.HasFlag(LegacyMods.Autoplay))
yield return new CatchModAutoplay();
if (mods.HasFlag(LegacyMods.Easy))
@@ -101,7 +109,7 @@ namespace osu.Game.Rulesets.Catch
case ModType.Automation:
return new Mod[]
{
- new MultiMod(new CatchModAutoplay(), new ModCinema()),
+ new MultiMod(new CatchModAutoplay(), new CatchModCinema()),
new CatchModRelax(),
};
@@ -112,7 +120,7 @@ namespace osu.Game.Rulesets.Catch
};
default:
- return new Mod[] { };
+ return Array.Empty();
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs
new file mode 100644
index 0000000000..3bc1ee5bf5
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs
@@ -0,0 +1,21 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Replays;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Scoring;
+using osu.Game.Users;
+
+namespace osu.Game.Rulesets.Catch.Mods
+{
+ public class CatchModCinema : ModCinema
+ {
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
+ {
+ ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } },
+ Replay = new CatchAutoGenerator(beatmap).Generate(),
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index d5d99640af..a4ed966abb 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Catch.Objects
if (value != null)
{
- path.ControlPoints.AddRange(value.ControlPoints);
+ path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position.Value, c.Type.Value)));
path.ExpectedDistance.Value = value.ExpectedDistance.Value;
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
index 278ff97195..fdd820b891 100644
--- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
@@ -10,10 +10,8 @@ using osu.Game.Replays;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.Replays;
-using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
@@ -32,8 +30,6 @@ namespace osu.Game.Rulesets.Catch.UI
TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
}
- public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(Beatmap);
-
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation);
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 9069c09ae4..6e4491de94 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
prevNoteTimes.RemoveAt(0);
prevNoteTimes.Add(newNoteTime);
- density = (prevNoteTimes[prevNoteTimes.Count - 1] - prevNoteTimes[0]) / prevNoteTimes.Count;
+ density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count;
}
private double lastTime;
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index a96c79b40b..8b53ce01f6 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -25,6 +25,8 @@ using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Difficulty;
using osu.Game.Rulesets.Mania.Edit;
+using osu.Game.Rulesets.Mania.Scoring;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mania
@@ -32,7 +34,11 @@ namespace osu.Game.Rulesets.Mania
public class ManiaRuleset : Ruleset
{
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableManiaRuleset(this, beatmap, mods);
+
+ public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new ManiaScoreProcessor(beatmap);
+
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
+
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);
public const string SHORT_NAME = "mania";
@@ -51,7 +57,9 @@ namespace osu.Game.Rulesets.Mania
else if (mods.HasFlag(LegacyMods.SuddenDeath))
yield return new ManiaModSuddenDeath();
- if (mods.HasFlag(LegacyMods.Autoplay))
+ if (mods.HasFlag(LegacyMods.Cinema))
+ yield return new ManiaModCinema();
+ else if (mods.HasFlag(LegacyMods.Autoplay))
yield return new ManiaModAutoplay();
if (mods.HasFlag(LegacyMods.Easy))
@@ -148,7 +156,7 @@ namespace osu.Game.Rulesets.Mania
case ModType.Automation:
return new Mod[]
{
- new MultiMod(new ManiaModAutoplay(), new ModCinema()),
+ new MultiMod(new ManiaModAutoplay(), new ManiaModCinema()),
};
case ModType.Fun:
@@ -158,7 +166,7 @@ namespace osu.Game.Rulesets.Mania
};
default:
- return new Mod[] { };
+ return Array.Empty();
}
}
@@ -268,7 +276,7 @@ namespace osu.Game.Rulesets.Mania
return stage1Bindings.Concat(stage2Bindings);
}
- return new KeyBinding[0];
+ return Array.Empty();
}
public override string GetVariantName(int variant)
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs
new file mode 100644
index 0000000000..02c1fc1b79
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs
@@ -0,0 +1,22 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Replays;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Scoring;
+using osu.Game.Users;
+
+namespace osu.Game.Rulesets.Mania.Mods
+{
+ public class ManiaModCinema : ModCinema
+ {
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
+ {
+ ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } },
+ Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
index cf1970c28b..2c497541a8 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
@@ -14,11 +14,9 @@ using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays;
-using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
@@ -67,8 +65,6 @@ namespace osu.Game.Rulesets.Mania.UI
protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages);
- public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(Beatmap);
-
public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns;
protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/approachcircle.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/approachcircle.png
new file mode 100644
index 0000000000..ff8b02ce80
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/approachcircle.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-0.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-0.png
new file mode 100644
index 0000000000..2af0569bcb
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-0.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-1.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-1.png
new file mode 100644
index 0000000000..e8b674d62a
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-1.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-2.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-2.png
new file mode 100644
index 0000000000..dbf7bc73bc
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-2.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-3.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-3.png
new file mode 100644
index 0000000000..43990c46e7
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-3.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-4.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-4.png
new file mode 100644
index 0000000000..4564f6d8bf
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-4.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-5.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-5.png
new file mode 100644
index 0000000000..dcef35eb59
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-5.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-6.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-6.png
new file mode 100644
index 0000000000..bfc0a01be5
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-6.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-7.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-7.png
new file mode 100644
index 0000000000..b9079ad5d5
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-7.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-8.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-8.png
new file mode 100644
index 0000000000..3a3d61b947
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-8.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-9.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-9.png
new file mode 100644
index 0000000000..3e703cd1cf
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-9.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit0.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit0.png
new file mode 100644
index 0000000000..3c3ebbfd0b
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit0.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit100.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit100.png
new file mode 100644
index 0000000000..9ecc302910
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit100.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit300.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit300.png
new file mode 100644
index 0000000000..24945f7d92
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit300.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit50.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit50.png
new file mode 100644
index 0000000000..d3f7eec5ee
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit50.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircle.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircle.png
new file mode 100644
index 0000000000..a5a3545abf
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircle.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircleoverlay.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircleoverlay.png
new file mode 100644
index 0000000000..b4062b8c62
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircleoverlay.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini
new file mode 100644
index 0000000000..5369de24e9
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini
@@ -0,0 +1,2 @@
+[General]
+Version: 1.0
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
index 38aac50df6..2fad3bd04f 100644
--- a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
+++ b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
@@ -19,9 +19,10 @@ namespace osu.Game.Rulesets.Osu.Tests
private Skin metricsSkin;
private Skin defaultSkin;
private Skin specialSkin;
+ private Skin oldSkin;
protected SkinnableTestScene()
- : base(2, 2)
+ : base(2, 3)
{
}
@@ -33,6 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests
metricsSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true);
defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
specialSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/special_skin"), audio, true);
+ oldSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/old_skin"), audio, true);
}
public void SetContents(Func creationFunction)
@@ -41,6 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Cell(1).Child = createProvider(metricsSkin, creationFunction);
Cell(2).Child = createProvider(defaultSkin, creationFunction);
Cell(3).Child = createProvider(specialSkin, creationFunction);
+ Cell(4).Child = createProvider(oldSkin, creationFunction);
}
private Drawable createProvider(Skin skin, Func creationFunction)
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
index 5f75cbabec..b6fc9821a4 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
@@ -286,11 +286,11 @@ namespace osu.Game.Rulesets.Osu.Tests
private bool assertGreatJudge() => judgementResults.Last().Type == HitResult.Great;
- private bool assertHeadMissTailTracked() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss;
+ private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss;
- private bool assertMidSliderJudgements() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great;
+ private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.Great;
- private bool assertMidSliderJudgementFail() => judgementResults[judgementResults.Count - 2].Type == HitResult.Miss;
+ private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.Miss;
private ScoreAccessibleReplayPlayer currentPlayer;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
index d0ce0c33c2..02ce77e707 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
@@ -70,6 +70,21 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("is rotation absolute almost same", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, estimatedRotation, 100));
}
+ [Test]
+ public void TestSpinPerMinuteOnRewind()
+ {
+ double estimatedSpm = 0;
+
+ addSeekStep(2500);
+ AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpmCounter.SpinsPerMinute);
+
+ addSeekStep(5000);
+ AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0));
+
+ addSeekStep(2500);
+ AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0));
+ }
+
private void addSeekStep(double time)
{
AddStep($"seek to {time}", () => track.Seek(time));
@@ -84,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new Spinner
{
Position = new Vector2(256, 192),
- EndTime = 5000,
+ EndTime = 6000,
},
// placeholder object to avoid hitting the results screen
new HitObject
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
index 9b820261ab..2497e428fc 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
@@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected override bool OnDoubleClick(DoubleClickEvent e)
{
// Todo: This should all not occur on double click, but rather if the previous control point is hovered.
- segmentStart = HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1];
+ segmentStart = HitObject.Path.ControlPoints[^1];
segmentStart.Type.Value = PathType.Linear;
currentSegmentLength = 1;
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs
index bde86a2890..ff3be97427 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using JetBrains.Annotations;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
@@ -10,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
{
public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null)
- : base(hitObject.StackedPosition, hitObject.StartTime, nextHitObject?.StartTime)
+ : base(hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime)
{
Masking = true;
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 675b09fc6d..a2c1a5f5f4 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -92,7 +92,24 @@ namespace osu.Game.Rulesets.Osu.Edit
return null;
OsuHitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex];
- OsuHitObject targetObject = sourceIndex + targetOffset < EditorBeatmap.HitObjects.Count ? EditorBeatmap.HitObjects[sourceIndex + targetOffset] : null;
+
+ int targetIndex = sourceIndex + targetOffset;
+ OsuHitObject targetObject = null;
+
+ // Keep advancing the target object while its start time falls before the end time of the source object
+ while (true)
+ {
+ if (targetIndex >= EditorBeatmap.HitObjects.Count)
+ break;
+
+ if (EditorBeatmap.HitObjects[targetIndex].StartTime >= sourceObject.GetEndTime())
+ {
+ targetObject = EditorBeatmap.HitObjects[targetIndex];
+ break;
+ }
+
+ targetIndex++;
+ }
return new OsuDistanceSnapGrid(sourceObject, targetObject);
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs
new file mode 100644
index 0000000000..5d9a524577
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs
@@ -0,0 +1,25 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Replays;
+using osu.Game.Scoring;
+using osu.Game.Users;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ public class OsuModCinema : ModCinema
+ {
+ public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray();
+
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
+ {
+ ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } },
+ Replay = new OsuAutoGenerator(beatmap).Generate()
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
index 7e20feba02..cf1ce517e8 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
@@ -11,6 +11,7 @@ using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -54,13 +55,8 @@ namespace osu.Game.Rulesets.Osu.Mods
break;
case DrawableSlider slider:
- slider.AccentColour.BindValueChanged(_ =>
- {
- //will trigger on skin change.
- slider.Body.AccentColour = slider.AccentColour.Value.Opacity(0);
- slider.Body.BorderColour = slider.AccentColour.Value;
- }, true);
-
+ slider.Body.OnSkinChanged += () => applySliderState(slider);
+ applySliderState(slider);
break;
case DrawableSpinner spinner:
@@ -69,5 +65,11 @@ namespace osu.Game.Rulesets.Osu.Mods
break;
}
}
+
+ private void applySliderState(DrawableSlider slider)
+ {
+ ((PlaySliderBody)slider.Body.Drawable).AccentColour = slider.AccentColour.Value.Opacity(0);
+ ((PlaySliderBody)slider.Body.Drawable).BorderColour = slider.AccentColour.Value;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
index 71cb9a9691..b81d94a673 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Scoring;
using osuTK;
using osu.Game.Skinning;
@@ -98,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public void UpdateSnakingPosition(Vector2 start, Vector2 end)
{
bool isRepeatAtEnd = repeatPoint.RepeatIndex % 2 == 0;
- List curve = drawableSlider.Body.CurrentCurve;
+ List curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
Position = isRepeatAtEnd ? end : start;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 1e0402d492..cd3c572ba0 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -11,7 +11,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Scoring;
using osuTK.Graphics;
@@ -24,8 +23,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public DrawableSliderHead HeadCircle => headContainer.Child;
public DrawableSliderTail TailCircle => tailContainer.Child;
- public readonly SnakingSliderBody Body;
public readonly SliderBall Ball;
+ public readonly SkinnableDrawable Body;
+
+ private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody;
private readonly Container headContainer;
private readonly Container tailContainer;
@@ -37,10 +38,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly IBindable positionBindable = new Bindable();
private readonly IBindable stackHeightBindable = new Bindable();
private readonly IBindable scaleBindable = new Bindable();
- private readonly IBindable pathVersion = new Bindable();
-
- [Resolved(CanBeNull = true)]
- private OsuRulesetConfigManager config { get; set; }
public DrawableSlider(Slider s)
: base(s)
@@ -51,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
InternalChildren = new Drawable[]
{
- Body = new SnakingSliderBody(s),
+ Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling),
tickContainer = new Container { RelativeSizeAxes = Axes.Both },
repeatContainer = new Container { RelativeSizeAxes = Axes.Both },
Ball = new SliderBall(s, this)
@@ -70,28 +67,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
[BackgroundDependencyLoader]
private void load()
{
- config?.BindWith(OsuRulesetSetting.SnakingInSliders, Body.SnakingIn);
- config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut);
-
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
- scaleBindable.BindValueChanged(scale =>
- {
- updatePathRadius();
- Ball.Scale = new Vector2(scale.NewValue);
- });
+ scaleBindable.BindValueChanged(scale => Ball.Scale = new Vector2(scale.NewValue));
positionBindable.BindTo(HitObject.PositionBindable);
stackHeightBindable.BindTo(HitObject.StackHeightBindable);
scaleBindable.BindTo(HitObject.ScaleBindable);
- pathVersion.BindTo(slider.Path.Version);
-
- pathVersion.BindValueChanged(_ => Body.Refresh());
AccentColour.BindValueChanged(colour =>
{
- Body.AccentColour = colour.NewValue;
-
foreach (var drawableHitObject in NestedHitObjects)
drawableHitObject.AccentColour.Value = colour.NewValue;
}, true);
@@ -169,16 +154,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
Ball.UpdateProgress(completionProgress);
- Body.UpdateProgress(completionProgress);
+ sliderBody?.UpdateProgress(completionProgress);
foreach (DrawableHitObject hitObject in NestedHitObjects)
{
- if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0));
+ if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(sliderBody?.SnakedStart ?? 0), slider.Path.PositionAt(sliderBody?.SnakedEnd ?? 0));
if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking;
}
- Size = Body.Size;
- OriginPosition = Body.PathOffset;
+ Size = sliderBody?.Size ?? Vector2.Zero;
+ OriginPosition = sliderBody?.PathOffset ?? Vector2.Zero;
if (DrawSize != Vector2.Zero)
{
@@ -192,28 +177,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override void OnKilled()
{
base.OnKilled();
- Body.RecyclePath();
+ sliderBody?.RecyclePath();
}
- private float sliderPathRadius;
-
protected override void ApplySkin(ISkinSource skin, bool allowFallback)
{
base.ApplySkin(skin, allowFallback);
- Body.BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE;
- sliderPathRadius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
- updatePathRadius();
-
- Body.AccentColour = skin.GetConfig(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value;
- Body.BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
-
bool allowBallTint = skin.GetConfig(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White;
}
- private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius;
-
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (userTriggered || Time.Current < slider.EndTime)
@@ -264,6 +238,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public Drawable ProxiedLayer => HeadCircle.ProxiedLayer;
- public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos);
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
+
+ private class DefaultSliderBody : PlaySliderBody
+ {
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index 1261d3d19a..de11ab6419 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public readonly SpinnerDisc Disc;
public readonly SpinnerTicks Ticks;
- private readonly SpinnerSpmCounter spmCounter;
+ public readonly SpinnerSpmCounter SpmCounter;
private readonly Container mainContainer;
@@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
},
}
},
- spmCounter = new SpinnerSpmCounter
+ SpmCounter = new SpinnerSpmCounter
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -177,8 +177,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void Update()
{
Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false;
- if (!spmCounter.IsPresent && Disc.Tracking)
- spmCounter.FadeIn(HitObject.TimeFadeIn);
+ if (!SpmCounter.IsPresent && Disc.Tracking)
+ SpmCounter.FadeIn(HitObject.TimeFadeIn);
base.Update();
}
@@ -189,7 +189,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
circle.Rotation = Disc.Rotation;
Ticks.Rotation = Disc.Rotation;
- spmCounter.SetRotation(Disc.RotationAbsolute);
+ SpmCounter.SetRotation(Disc.RotationAbsolute);
float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight;
Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DrawableSliderPath.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DrawableSliderPath.cs
new file mode 100644
index 0000000000..c31d6beb01
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DrawableSliderPath.cs
@@ -0,0 +1,70 @@
+// 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.Lines;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
+{
+ public abstract class DrawableSliderPath : SmoothPath
+ {
+ protected const float BORDER_PORTION = 0.128f;
+ protected const float GRADIENT_PORTION = 1 - BORDER_PORTION;
+
+ private const float border_max_size = 8f;
+ private const float border_min_size = 0f;
+
+ private Color4 borderColour = Color4.White;
+
+ public Color4 BorderColour
+ {
+ get => borderColour;
+ set
+ {
+ if (borderColour == value)
+ return;
+
+ borderColour = value;
+
+ InvalidateTexture();
+ }
+ }
+
+ private Color4 accentColour = Color4.White;
+
+ public Color4 AccentColour
+ {
+ get => accentColour;
+ set
+ {
+ if (accentColour == value)
+ return;
+
+ accentColour = value;
+
+ InvalidateTexture();
+ }
+ }
+
+ private float borderSize = 1;
+
+ public float BorderSize
+ {
+ get => borderSize;
+ set
+ {
+ if (borderSize == value)
+ return;
+
+ if (value < border_min_size || value > border_max_size)
+ return;
+
+ borderSize = value;
+
+ InvalidateTexture();
+ }
+ }
+
+ protected float CalculatedBorderPortion => BorderSize * BORDER_PORTION;
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.cs
new file mode 100644
index 0000000000..cedf2f6e09
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.cs
@@ -0,0 +1,52 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Configuration;
+using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Skinning;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
+{
+ public abstract class PlaySliderBody : SnakingSliderBody
+ {
+ private IBindable scaleBindable;
+ private IBindable pathVersion;
+ private IBindable accentColour;
+
+ [Resolved]
+ private DrawableHitObject drawableObject { get; set; }
+
+ [Resolved(CanBeNull = true)]
+ private OsuRulesetConfigManager config { get; set; }
+
+ private Slider slider;
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ slider = (Slider)drawableObject.HitObject;
+
+ scaleBindable = slider.ScaleBindable.GetBoundCopy();
+ scaleBindable.BindValueChanged(scale => PathRadius = OsuHitObject.OBJECT_RADIUS * scale.NewValue, true);
+
+ pathVersion = slider.Path.Version.GetBoundCopy();
+ pathVersion.BindValueChanged(_ => Refresh());
+
+ accentColour = drawableObject.AccentColour.GetBoundCopy();
+ accentColour.BindValueChanged(accent => updateAccentColour(skin, accent.NewValue), true);
+
+ config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn);
+ config?.BindWith(OsuRulesetSetting.SnakingOutSliders, SnakingOut);
+
+ BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? 1;
+ BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
+ }
+
+ private void updateAccentColour(ISkinSource skin, Color4 defaultAccentColour)
+ => AccentColour = skin.GetConfig(OsuSkinColour.SliderTrackOverride)?.Value ?? defaultAccentColour;
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
index 24a437c20e..8758a4a066 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines;
using osuTK;
@@ -12,9 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public abstract class SliderBody : CompositeDrawable
{
- public const float DEFAULT_BORDER_SIZE = 1;
-
- private SliderPath path;
+ private DrawableSliderPath path;
protected Path Path => path;
@@ -80,19 +79,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}
///
- /// Initialises a new , releasing all resources retained by the old one.
+ /// Initialises a new , releasing all resources retained by the old one.
///
public virtual void RecyclePath()
{
- InternalChild = path = new SliderPath
+ InternalChild = path = CreateSliderPath().With(p =>
{
- Position = path?.Position ?? Vector2.Zero,
- PathRadius = path?.PathRadius ?? 10,
- AccentColour = path?.AccentColour ?? Color4.White,
- BorderColour = path?.BorderColour ?? Color4.White,
- BorderSize = path?.BorderSize ?? DEFAULT_BORDER_SIZE,
- Vertices = path?.Vertices ?? Array.Empty()
- };
+ p.Position = path?.Position ?? Vector2.Zero;
+ p.PathRadius = path?.PathRadius ?? 10;
+ p.AccentColour = path?.AccentColour ?? Color4.White;
+ p.BorderColour = path?.BorderColour ?? Color4.White;
+ p.BorderSize = path?.BorderSize ?? 1;
+ p.Vertices = path?.Vertices ?? Array.Empty();
+ });
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos);
@@ -103,77 +102,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
/// The vertices
protected void SetVertices(IReadOnlyList vertices) => path.Vertices = vertices;
- private class SliderPath : SmoothPath
+ protected virtual DrawableSliderPath CreateSliderPath() => new DefaultDrawableSliderPath();
+
+ private class DefaultDrawableSliderPath : DrawableSliderPath
{
- private const float border_max_size = 8f;
- private const float border_min_size = 0f;
-
- private const float border_portion = 0.128f;
- private const float gradient_portion = 1 - border_portion;
-
private const float opacity_at_centre = 0.3f;
private const float opacity_at_edge = 0.8f;
- private Color4 borderColour = Color4.White;
-
- public Color4 BorderColour
- {
- get => borderColour;
- set
- {
- if (borderColour == value)
- return;
-
- borderColour = value;
-
- InvalidateTexture();
- }
- }
-
- private Color4 accentColour = Color4.White;
-
- public Color4 AccentColour
- {
- get => accentColour;
- set
- {
- if (accentColour == value)
- return;
-
- accentColour = value;
-
- InvalidateTexture();
- }
- }
-
- private float borderSize = DEFAULT_BORDER_SIZE;
-
- public float BorderSize
- {
- get => borderSize;
- set
- {
- if (borderSize == value)
- return;
-
- if (value < border_min_size || value > border_max_size)
- return;
-
- borderSize = value;
-
- InvalidateTexture();
- }
- }
-
- private float calculatedBorderPortion => BorderSize * border_portion;
-
protected override Color4 ColourAt(float position)
{
- if (calculatedBorderPortion != 0f && position <= calculatedBorderPortion)
+ if (CalculatedBorderPortion != 0f && position <= CalculatedBorderPortion)
return BorderColour;
- position -= calculatedBorderPortion;
- return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / gradient_portion) * AccentColour.A);
+ position -= CalculatedBorderPortion;
+ return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / GRADIENT_PORTION) * AccentColour.A);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
index f2150280b3..8a8668d6af 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osuTK;
@@ -50,16 +51,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
///
private Vector2 snakedPathOffset;
- private readonly Slider slider;
-
- public SnakingSliderBody(Slider slider)
- {
- this.slider = slider;
- }
+ private Slider slider;
[BackgroundDependencyLoader]
- private void load()
+ private void load(DrawableHitObject drawableObject)
{
+ slider = (Slider)drawableObject.HitObject;
+
Refresh();
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs
index b1d90c49f6..97a7b98c5b 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.MathUtils;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@@ -62,6 +63,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public void SetRotation(float currentRotation)
{
+ // Never calculate SPM by same time of record to avoid 0 / 0 = NaN or X / 0 = Infinity result.
+ if (Precision.AlmostEquals(0, Time.Elapsed))
+ return;
+
// If we've gone back in time, it's fine to work with a fresh set of records for now
if (records.Count > 0 && Time.Current < records.Last().Time)
records.Clear();
@@ -71,6 +76,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
var record = records.Peek();
while (records.Count > 0 && Time.Current - records.Peek().Time > spm_count_duration)
record = records.Dequeue();
+
SpinsPerMinute = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360;
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 34e5a7f3cd..fe65ab78d1 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Objects
if (value != null)
{
- path.ControlPoints.AddRange(value.ControlPoints);
+ path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position.Value, c.Type.Value)));
path.ExpectedDistance.Value = value.ExpectedDistance.Value;
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 2f43909332..835ae2564c 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -23,16 +23,23 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Difficulty;
+using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Skinning;
+using System;
namespace osu.Game.Rulesets.Osu
{
public class OsuRuleset : Ruleset
{
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableOsuRuleset(this, beatmap, mods);
+
+ public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new OsuScoreProcessor(beatmap);
+
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
+
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
public const string SHORT_NAME = "osu";
@@ -60,7 +67,9 @@ namespace osu.Game.Rulesets.Osu
if (mods.HasFlag(LegacyMods.Autopilot))
yield return new OsuModAutopilot();
- if (mods.HasFlag(LegacyMods.Autoplay))
+ if (mods.HasFlag(LegacyMods.Cinema))
+ yield return new OsuModCinema();
+ else if (mods.HasFlag(LegacyMods.Autoplay))
yield return new OsuModAutoplay();
if (mods.HasFlag(LegacyMods.Easy))
@@ -126,7 +135,7 @@ namespace osu.Game.Rulesets.Osu
case ModType.Automation:
return new Mod[]
{
- new MultiMod(new OsuModAutoplay(), new ModCinema()),
+ new MultiMod(new OsuModAutoplay(), new OsuModCinema()),
new OsuModRelax(),
new OsuModAutopilot(),
};
@@ -149,7 +158,7 @@ namespace osu.Game.Rulesets.Osu
};
default:
- return new Mod[] { };
+ return Array.Empty();
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
index 8dd48eace0..4ea4220faf 100644
--- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
@@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu
ReverseArrow,
HitCircleText,
SliderFollowCircle,
- SliderBall
+ SliderBall,
+ SliderBody,
}
}
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
index bd59e8a03f..2686ba4fd2 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
@@ -156,9 +156,9 @@ namespace osu.Game.Rulesets.Osu.Replays
// TODO: Shouldn't the spinner always spin in the same direction?
if (h is Spinner)
{
- calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[Frames.Count - 1]).Position, out startPosition, out spinnerDirection);
+ calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[^1]).Position, out startPosition, out spinnerDirection);
- Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[Frames.Count - 1]).Position;
+ Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[^1]).Position;
if (spinCentreOffset.Length > SPIN_RADIUS)
{
@@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Replays
private void moveToHitObject(OsuHitObject h, Vector2 targetPos, Easing easing)
{
- OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[Frames.Count - 1];
+ OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[^1];
// Wait until Auto could "see and react" to the next note.
double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime);
@@ -363,7 +363,7 @@ namespace osu.Game.Rulesets.Osu.Replays
}
// We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed!
- if (Frames[Frames.Count - 1].Time <= endFrame.Time)
+ if (Frames[^1].Time <= endFrame.Time)
AddFrameToReplay(endFrame);
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs
new file mode 100644
index 0000000000..d41135ca69
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs
@@ -0,0 +1,56 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.MathUtils;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Skinning
+{
+ public class LegacySliderBody : PlaySliderBody
+ {
+ protected override DrawableSliderPath CreateSliderPath() => new LegacyDrawableSliderPath();
+
+ private class LegacyDrawableSliderPath : DrawableSliderPath
+ {
+ private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS);
+
+ public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, base.AccentColour.A * 0.70f);
+
+ protected override Color4 ColourAt(float position)
+ {
+ float realBorderPortion = shadow_portion + CalculatedBorderPortion;
+ float realGradientPortion = 1 - realBorderPortion;
+
+ if (position <= shadow_portion)
+ return new Color4(0f, 0f, 0f, 0.25f * position / shadow_portion);
+
+ if (position <= realBorderPortion)
+ return BorderColour;
+
+ position -= realBorderPortion;
+
+ Color4 outerColour = AccentColour.Darken(0.1f);
+ Color4 innerColour = lighten(AccentColour, 0.5f);
+
+ return Interpolation.ValueAt(position / realGradientPortion, outerColour, innerColour, 0, 1);
+ }
+
+ ///
+ /// Lightens a colour in a way more friendly to dark or strong colours.
+ ///
+ private static Color4 lighten(Color4 color, float amount)
+ {
+ amount *= 0.5f;
+ return new Color4(
+ Math.Min(1, color.R * (1 + 0.5f * amount) + 1 * amount),
+ Math.Min(1, color.G * (1 + 0.5f * amount) + 1 * amount),
+ Math.Min(1, color.B * (1 + 0.5f * amount) + 1 * amount),
+ color.A);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
index f5b7d9166f..266b619334 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
/// Their hittable area is 128px, but the actual circle portion is 118px.
/// We must account for some gameplay elements such as slider bodies, where this padding is not present.
///
- private const float legacy_circle_radius = 64 - 5;
+ public const float LEGACY_CIRCLE_RADIUS = 64 - 5;
public OsuLegacySkinTransformer(ISkinSource source)
{
@@ -73,6 +73,12 @@ namespace osu.Game.Rulesets.Osu.Skinning
return null;
+ case OsuSkinComponents.SliderBody:
+ if (hasHitCircle.Value)
+ return new LegacySliderBody();
+
+ return null;
+
case OsuSkinComponents.HitCircle:
if (hasHitCircle.Value)
return new LegacyMainCirclePiece();
@@ -124,7 +130,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
case OsuSkinConfiguration.SliderPathRadius:
if (hasHitCircle.Value)
- return SkinUtils.As(new BindableFloat(legacy_circle_radius));
+ return SkinUtils.As(new BindableFloat(LEGACY_CIRCLE_RADIUS));
break;
}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 80291c002e..4e86662ec6 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private void addPart(Vector2 screenSpacePosition)
{
parts[currentIndex].Position = screenSpacePosition;
- parts[currentIndex].Time = time;
+ parts[currentIndex].Time = time + 1;
++parts[currentIndex].InvalidationID;
currentIndex = (currentIndex + 1) % max_sprites;
@@ -201,7 +201,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private readonly TrailPart[] parts = new TrailPart[max_sprites];
private Vector2 size;
- private readonly TrailBatch vertexBatch = new TrailBatch(max_sprites, 1);
+ private readonly QuadBatch vertexBatch = new QuadBatch(max_sprites, 1);
public TrailDrawNode(CursorTrail source)
: base(source)
@@ -227,23 +227,52 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
shader.Bind();
shader.GetUniform("g_FadeClock").UpdateValue(ref time);
- for (int i = 0; i < parts.Length; ++i)
+ texture.TextureGL.Bind();
+
+ RectangleF textureRect = texture.GetTextureRect();
+
+ foreach (var part in parts)
{
- if (parts[i].InvalidationID == -1)
+ if (part.InvalidationID == -1)
continue;
- vertexBatch.DrawTime = parts[i].Time;
+ if (time - part.Time >= 1)
+ continue;
- Vector2 pos = parts[i].Position;
+ vertexBatch.Add(new TexturedTrailVertex
+ {
+ Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y + size.Y / 2),
+ TexturePosition = textureRect.BottomLeft,
+ Colour = DrawColourInfo.Colour.BottomLeft.Linear,
+ Time = part.Time
+ });
- DrawQuad(
- texture,
- new Quad(pos.X - size.X / 2, pos.Y - size.Y / 2, size.X, size.Y),
- DrawColourInfo.Colour,
- null,
- vertexBatch.AddAction);
+ vertexBatch.Add(new TexturedTrailVertex
+ {
+ Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y + size.Y / 2),
+ TexturePosition = textureRect.BottomRight,
+ Colour = DrawColourInfo.Colour.BottomRight.Linear,
+ Time = part.Time
+ });
+
+ vertexBatch.Add(new TexturedTrailVertex
+ {
+ Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y - size.Y / 2),
+ TexturePosition = textureRect.TopRight,
+ Colour = DrawColourInfo.Colour.TopRight.Linear,
+ Time = part.Time
+ });
+
+ vertexBatch.Add(new TexturedTrailVertex
+ {
+ Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y - size.Y / 2),
+ TexturePosition = textureRect.TopLeft,
+ Colour = DrawColourInfo.Colour.TopLeft.Linear,
+ Time = part.Time
+ });
}
+ vertexBatch.Draw();
shader.Unbind();
}
@@ -253,25 +282,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Dispose();
}
-
- // Todo: This shouldn't exist, but is currently used to reduce allocations by caching variable-capturing closures.
- private class TrailBatch : QuadBatch
- {
- public new readonly Action AddAction;
- public float DrawTime;
-
- public TrailBatch(int size, int maxBuffers)
- : base(size, maxBuffers)
- {
- AddAction = v => Add(new TexturedTrailVertex
- {
- Position = v.Position,
- TexturePosition = v.TexturePosition,
- Time = DrawTime + 1,
- Colour = v.Colour,
- });
- }
- }
}
[StructLayout(LayoutKind.Sequential)]
diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
index 039d38e4fd..a37ef8d9a0 100644
--- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
@@ -14,8 +14,6 @@ using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Replays;
-using osu.Game.Rulesets.Osu.Scoring;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osuTK;
@@ -33,8 +31,6 @@ namespace osu.Game.Rulesets.Osu.UI
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor
- public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(Beatmap);
-
protected override Playfield CreatePlayfield() => new OsuPlayfield();
protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo);
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs
new file mode 100644
index 0000000000..71aa007d3b
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs
@@ -0,0 +1,21 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.Replays;
+using osu.Game.Scoring;
+using osu.Game.Users;
+
+namespace osu.Game.Rulesets.Taiko.Mods
+{
+ public class TaikoModCinema : ModCinema
+ {
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
+ {
+ ScoreInfo = new ScoreInfo { User = new User { Username = "mekkadosu!" } },
+ Replay = new TaikoAutoGenerator(beatmap).Generate(),
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index 0db6498c12..2da5a9c403 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -105,19 +105,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
}
}
- public abstract class DrawableTaikoHitObject : DrawableTaikoHitObject
- where TaikoHitType : TaikoHitObject
+ public abstract class DrawableTaikoHitObject : DrawableTaikoHitObject
+ where TTaikoHit : TaikoHitObject
{
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
- public new TaikoHitType HitObject;
+ public new TTaikoHit HitObject;
protected readonly Vector2 BaseSize;
protected readonly TaikoPiece MainPiece;
private readonly Container strongHitContainer;
- protected DrawableTaikoHitObject(TaikoHitType hitObject)
+ protected DrawableTaikoHitObject(TTaikoHit hitObject)
: base(hitObject)
{
HitObject = hitObject;
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index ab9c95159c..4d6c5fa1c0 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -15,15 +15,21 @@ using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Taiko.Replays;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Difficulty;
+using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Scoring;
+using System;
namespace osu.Game.Rulesets.Taiko
{
public class TaikoRuleset : Ruleset
{
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableTaikoRuleset(this, beatmap, mods);
+
+ public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new TaikoScoreProcessor(beatmap);
+
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
public const string SHORT_NAME = "taiko";
@@ -50,7 +56,9 @@ namespace osu.Game.Rulesets.Taiko
else if (mods.HasFlag(LegacyMods.SuddenDeath))
yield return new TaikoModSuddenDeath();
- if (mods.HasFlag(LegacyMods.Autoplay))
+ if (mods.HasFlag(LegacyMods.Cinema))
+ yield return new TaikoModCinema();
+ else if (mods.HasFlag(LegacyMods.Autoplay))
yield return new TaikoModAutoplay();
if (mods.HasFlag(LegacyMods.Easy))
@@ -100,7 +108,7 @@ namespace osu.Game.Rulesets.Taiko
case ModType.Automation:
return new Mod[]
{
- new MultiMod(new TaikoModAutoplay(), new ModCinema()),
+ new MultiMod(new TaikoModAutoplay(), new TaikoModCinema()),
new TaikoModRelax(),
};
@@ -111,7 +119,7 @@ namespace osu.Game.Rulesets.Taiko
};
default:
- return new Mod[] { };
+ return Array.Empty();
}
}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
index 2233658428..0c7495aa52 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
@@ -5,10 +5,8 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
-using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Taiko.Replays;
using osu.Framework.Input;
@@ -40,8 +38,6 @@ namespace osu.Game.Rulesets.Taiko.UI
new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar)));
}
- public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(Beatmap);
-
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer();
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
index a51b90851c..2782e902fe 100644
--- a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
+++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Tests.NonVisual
cpi.Add(1000, new DifficultyControlPoint()); // is redundant
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
- Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
+ Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
cpi.Add(1000, new DifficultyControlPoint { SpeedMultiplier = 2 }); // is not redundant
@@ -60,18 +60,18 @@ namespace osu.Game.Tests.NonVisual
{
var cpi = new ControlPointInfo();
- cpi.Add(0, new SampleControlPoint()); // is redundant
+ cpi.Add(0, new SampleControlPoint()); // is *not* redundant, special exception for first sample point
cpi.Add(1000, new SampleControlPoint()); // is redundant
- Assert.That(cpi.Groups.Count, Is.EqualTo(0));
- Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
- Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
-
- cpi.Add(1000, new SampleControlPoint { SampleVolume = 50 }); // is not redundant
-
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
+
+ cpi.Add(1000, new SampleControlPoint { SampleVolume = 50 }); // is not redundant
+
+ Assert.That(cpi.Groups.Count, Is.EqualTo(2));
+ Assert.That(cpi.SamplePoints.Count, Is.EqualTo(2));
+ Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
}
[Test]
@@ -83,7 +83,7 @@ namespace osu.Game.Tests.NonVisual
cpi.Add(1000, new EffectControlPoint()); // is redundant
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
- Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
+ Assert.That(cpi.EffectPoints.Count, Is.EqualTo(0));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant
diff --git a/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs b/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs
index 1c78b63499..d5ac38008e 100644
--- a/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs
+++ b/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Tests.NonVisual
{
Assert.AreEqual(0, stack.Count);
- Assert.Throws(() =>
+ Assert.Throws(() =>
{
int unused = stack[0];
});
@@ -55,7 +55,7 @@ namespace osu.Game.Tests.NonVisual
// e.g. indices 3, 4, 5, 6 (out of range)
for (int i = stack.Count; i < stack.Count + capacity; i++)
{
- Assert.Throws(() =>
+ Assert.Throws(() =>
{
int unused = stack[i];
});
@@ -80,7 +80,7 @@ namespace osu.Game.Tests.NonVisual
// e.g. indices 3, 4, 5, 6 (out of range)
for (int i = stack.Count; i < stack.Count + capacity; i++)
{
- Assert.Throws(() =>
+ Assert.Throws(() =>
{
int unused = stack[i];
});
diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
index 89b5db9e1b..a139c3a8c2 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -10,6 +11,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
+using osu.Game.IO.Archives;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
@@ -154,7 +156,30 @@ namespace osu.Game.Tests.Scores.IO
}
}
- private async Task loadIntoOsu(OsuGameBase osu, ScoreInfo score)
+ [Test]
+ public async Task TestOnlineScoreIsAvailableLocally()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestOnlineScoreIsAvailableLocally"))
+ {
+ try
+ {
+ var osu = await loadOsu(host);
+
+ await loadIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader());
+
+ var scoreManager = osu.Dependencies.Get();
+
+ // Note: A new score reference is used here since the import process mutates the original object to set an ID
+ Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineScoreID = 2 }));
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ private async Task loadIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null)
{
var beatmapManager = osu.Dependencies.Get();
@@ -165,7 +190,7 @@ namespace osu.Game.Tests.Scores.IO
score.Ruleset = new OsuRuleset().RulesetInfo;
var scoreManager = osu.Dependencies.Get();
- await scoreManager.Import(score);
+ await scoreManager.Import(score, archive);
return scoreManager.GetAllUsableScores().FirstOrDefault();
}
@@ -196,5 +221,23 @@ namespace osu.Game.Tests.Scores.IO
Assert.IsTrue(task.Wait(timeout), failureMessage);
}
+
+ private class TestArchiveReader : ArchiveReader
+ {
+ public TestArchiveReader()
+ : base("test_archive")
+ {
+ }
+
+ public override Stream GetStream(string name) => new MemoryStream();
+
+ public override void Dispose()
+ {
+ }
+
+ public override IEnumerable Filenames => new[] { "test_file.osr" };
+
+ public override Stream GetUnderlyingStream() => new MemoryStream();
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs
index 472c43096f..fede99f450 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs
@@ -88,6 +88,20 @@ namespace osu.Game.Tests.Visual.Background
AddUntilStep("not lightened", () => userDimContainer.DimEqual(test_user_dim));
}
+ [Test]
+ public void TestIgnoreUserSettings()
+ {
+ AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim);
+ AddUntilStep("dim reached", () => userDimContainer.DimEqual(test_user_dim));
+
+ AddStep("ignore settings", () => userDimContainer.IgnoreUserSettings.Value = true);
+ AddUntilStep("no dim", () => userDimContainer.DimEqual(0));
+ AddStep("set break", () => isBreakTime.Value = true);
+ AddAssert("no dim", () => userDimContainer.DimEqual(0));
+ AddStep("clear break", () => isBreakTime.Value = false);
+ AddAssert("no dim", () => userDimContainer.DimEqual(0));
+ }
+
private class TestUserDimContainer : UserDimContainer
{
public bool DimEqual(float expectedDimLevel) => Content.Colour == OsuColour.Gray(1f - expectedDimLevel);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
new file mode 100644
index 0000000000..39c42980ab
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
@@ -0,0 +1,81 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Screens.Play;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneHUDOverlay : ManualInputManagerTestScene
+ {
+ private HUDOverlay hudOverlay;
+
+ private Drawable hideTarget => hudOverlay.KeyCounter; // best way of checking hideTargets without exposing.
+
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
+ [Test]
+ public void TestShownByDefault()
+ {
+ createNew();
+
+ AddAssert("showhud is set", () => hudOverlay.ShowHud.Value);
+
+ AddAssert("hidetarget is visible", () => hideTarget.IsPresent);
+ AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent);
+ }
+
+ [Test]
+ public void TestFadesInOnLoadComplete()
+ {
+ float? initialAlpha = null;
+
+ createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha);
+ AddUntilStep("wait for load", () => hudOverlay.IsAlive);
+ AddAssert("initial alpha was less than 1", () => initialAlpha != null && initialAlpha < 1);
+ }
+
+ [Test]
+ public void TestHideExternally()
+ {
+ createNew();
+
+ AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
+
+ AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
+ AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
+ }
+
+ [Test]
+ public void TestExternalHideDoesntAffectConfig()
+ {
+ bool originalConfigValue = false;
+
+ createNew();
+
+ AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.ShowInterface));
+
+ AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
+ AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.ShowInterface));
+
+ AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
+ AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.ShowInterface));
+ }
+
+ private void createNew(Action action = null)
+ {
+ AddStep("create overlay", () =>
+ {
+ Child = hudOverlay = new HUDOverlay(null, null, Array.Empty());
+
+ action?.Invoke(hudOverlay);
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
index 875e7b9758..4c5c18f38a 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
@@ -31,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay
requestCount = 0;
increment = skip_time;
- Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new Mod[] { }, 0)
+ Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), Array.Empty(), 0)
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs
index 50df4022dc..1ac914e27d 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
AddStep(@"set max", () => Room.MaxParticipants.Value = 10);
- AddStep(@"clear users", () => Room.Participants.Value = new User[] { });
+ AddStep(@"clear users", () => Room.Participants.Value = System.Array.Empty());
AddStep(@"set max to null", () => Room.MaxParticipants.Value = null);
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs
index 2a45e68c0a..96c0c59695 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("set second set", () => details.BeatmapSet = secondSet);
AddAssert("ratings set", () => details.Ratings.Metrics == secondSet.Metrics);
- BeatmapSetInfo createSet() => new BeatmapSetInfo
+ static BeatmapSetInfo createSet() => new BeatmapSetInfo
{
Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).Select(_ => RNG.Next(10)).ToArray() },
Beatmaps = new List
diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs
index 546f6ac182..d47c972564 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs
@@ -1,23 +1,66 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using osu.Framework.Graphics;
using osu.Game.Overlays;
+using osu.Game.Overlays.News;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneNewsOverlay : OsuTestScene
{
- private NewsOverlay news;
+ private TestNewsOverlay news;
protected override void LoadComplete()
{
base.LoadComplete();
- Add(news = new NewsOverlay());
+ Add(news = new TestNewsOverlay());
AddStep(@"Show", news.Show);
AddStep(@"Hide", news.Hide);
AddStep(@"Show front page", () => news.ShowFrontPage());
AddStep(@"Custom article", () => news.Current.Value = "Test Article 101");
+
+ AddStep(@"Article covers", () => news.LoadAndShowContent(new NewsCoverTest()));
+ }
+
+ private class TestNewsOverlay : NewsOverlay
+ {
+ public new void LoadAndShowContent(NewsContent content) => base.LoadAndShowContent(content);
+ }
+
+ private class NewsCoverTest : NewsContent
+ {
+ public NewsCoverTest()
+ {
+ Spacing = new osuTK.Vector2(0, 10);
+
+ var article = new NewsArticleCover.ArticleInfo
+ {
+ Author = "Ephemeral",
+ CoverUrl = "https://assets.ppy.sh/artists/58/header.jpg",
+ Time = new DateTime(2019, 12, 4),
+ Title = "New Featured Artist: Kurokotei"
+ };
+
+ Children = new Drawable[]
+ {
+ new NewsArticleCover(article)
+ {
+ Height = 200
+ },
+ new NewsArticleCover(article)
+ {
+ Height = 120
+ },
+ new NewsArticleCover(article)
+ {
+ RelativeSizeAxes = Axes.None,
+ Size = new osuTK.Vector2(400, 200),
+ }
+ };
+ }
}
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs b/osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs
new file mode 100644
index 0000000000..468239cf08
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs
@@ -0,0 +1,42 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Overlays.Profile.Sections;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneProfileCounterPill : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(CounterPill)
+ };
+
+ private readonly CounterPill pill;
+ private readonly BindableInt value = new BindableInt();
+
+ public TestSceneProfileCounterPill()
+ {
+ Child = pill = new CounterPill
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Current = { BindTarget = value }
+ };
+ }
+
+ [Test]
+ public void TestVisibility()
+ {
+ AddStep("Set value to 0", () => value.Value = 0);
+ AddAssert("Check hidden", () => !pill.IsPresent);
+ AddStep("Set value to 10", () => value.Value = 10);
+ AddAssert("Check visible", () => pill.IsPresent);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
index 98da63508b..15f9c9a013 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
@@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online
},
Title = "osu!volunteer",
Colour = "ff0000",
- Achievements = new User.UserAchievement[0],
+ Achievements = Array.Empty(),
};
public TestSceneUserProfileOverlay()
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs
index d09a50b12c..048a1950fd 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online
new User { PreviousUsernames = new[] { "longusername", "longerusername" } },
new User { PreviousUsernames = new[] { "test", "angelsim", "verylongusername" } },
new User { PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext", "anylonger" } },
- new User { PreviousUsernames = new string[0] },
+ new User { PreviousUsernames = Array.Empty() },
null
};
diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/TestSceneOsuGame.cs
index e495b2a95a..492494ada3 100644
--- a/osu.Game.Tests/Visual/TestSceneOsuGame.cs
+++ b/osu.Game.Tests/Visual/TestSceneOsuGame.cs
@@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual
foreach (var type in requiredGameDependencies)
{
if (game.Dependencies.Get(type) == null)
- throw new Exception($"{type} has not been cached");
+ throw new InvalidOperationException($"{type} has not been cached");
}
return true;
@@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual
foreach (var type in requiredGameBaseDependencies)
{
if (gameBase.Dependencies.Get(type) == null)
- throw new Exception($"{type} has not been cached");
+ throw new InvalidOperationException($"{type} has not been cached");
}
return true;
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
index ed44d82bce..b0b673d6a4 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
@@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.UserInterface
private TimingControlPoint getNextTimingPoint(TimingControlPoint current)
{
- if (timingPoints[timingPoints.Count - 1] == current)
+ if (timingPoints[^1] == current)
return current;
int index = timingPoints.IndexOf(current); // -1 means that this is a "default beat"
@@ -169,7 +169,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
if (timingPoints.Count == 0) return 0;
- if (timingPoints[timingPoints.Count - 1] == current)
+ if (timingPoints[^1] == current)
return (int)Math.Ceiling((Beatmap.Value.Track.Length - current.Time) / current.BeatLength);
return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength);
diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs
index ba63013886..f3eecf8afe 100644
--- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Tournament.Screens.Editors
get
{
if (editorInfo == null)
- return new MenuItem[0];
+ return Array.Empty