diff --git a/osu.Android.props b/osu.Android.props
index eff0eed278..17b5cb67e9 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,8 +51,8 @@
-
-
+
+
diff --git a/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationRequest.cs b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationRequest.cs
new file mode 100644
index 0000000000..d6ef390a8f
--- /dev/null
+++ b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationRequest.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Desktop.LegacyIpc
+{
+ ///
+ /// A difficulty calculation request from the legacy client.
+ ///
+ ///
+ /// Synchronise any changes with osu!stable.
+ ///
+ public class LegacyIpcDifficultyCalculationRequest
+ {
+ public string BeatmapFile { get; set; }
+ public int RulesetId { get; set; }
+ public int Mods { get; set; }
+ }
+}
diff --git a/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationResponse.cs b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationResponse.cs
new file mode 100644
index 0000000000..7b9fae5797
--- /dev/null
+++ b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationResponse.cs
@@ -0,0 +1,16 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Desktop.LegacyIpc
+{
+ ///
+ /// A difficulty calculation response returned to the legacy client.
+ ///
+ ///
+ /// Synchronise any changes with osu!stable.
+ ///
+ public class LegacyIpcDifficultyCalculationResponse
+ {
+ public double StarRating { get; set; }
+ }
+}
diff --git a/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs b/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs
new file mode 100644
index 0000000000..0fa60e2068
--- /dev/null
+++ b/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs
@@ -0,0 +1,53 @@
+// 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.Platform;
+using Newtonsoft.Json.Linq;
+
+namespace osu.Desktop.LegacyIpc
+{
+ ///
+ /// An that can be used to communicate to and from legacy clients.
+ ///
+ /// In order to deserialise types at either end, types must be serialised as their ,
+ /// however this cannot be done since osu!stable and osu!lazer live in two different assemblies.
+ ///
+ /// To get around this, this class exists which serialises a payload () as an type,
+ /// which can be deserialised at either end because it is part of the core library (mscorlib / System.Private.CorLib).
+ /// The payload contains the data to be sent over the IPC channel.
+ ///
+ /// At either end, Json.NET deserialises the payload into a which is manually converted back into the expected type,
+ /// which then further contains another representing the data sent over the IPC channel whose type can likewise be lazily matched through
+ /// .
+ ///
+ ///
+ ///
+ /// Synchronise any changes with osu-stable.
+ ///
+ public class LegacyIpcMessage : IpcMessage
+ {
+ public LegacyIpcMessage()
+ {
+ // Types/assemblies are not inter-compatible, so always serialise/deserialise into objects.
+ base.Type = typeof(object).FullName;
+ }
+
+ public new string Type => base.Type; // Hide setter.
+
+ public new object Value
+ {
+ get => base.Value;
+ set => base.Value = new Data
+ {
+ MessageType = value.GetType().Name,
+ MessageData = value
+ };
+ }
+
+ public class Data
+ {
+ public string MessageType { get; set; }
+ public object MessageData { get; set; }
+ }
+ }
+}
diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs
new file mode 100644
index 0000000000..97a4c57bf0
--- /dev/null
+++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs
@@ -0,0 +1,121 @@
+// 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 Newtonsoft.Json.Linq;
+using osu.Framework.Logging;
+using osu.Framework.Platform;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Catch;
+using osu.Game.Rulesets.Mania;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Taiko;
+
+#nullable enable
+
+namespace osu.Desktop.LegacyIpc
+{
+ ///
+ /// Provides IPC to legacy osu! clients.
+ ///
+ public class LegacyTcpIpcProvider : TcpIpcProvider
+ {
+ private static readonly Logger logger = Logger.GetLogger("legacy-ipc");
+
+ public LegacyTcpIpcProvider()
+ : base(45357)
+ {
+ MessageReceived += msg =>
+ {
+ try
+ {
+ logger.Add("Processing legacy IPC message...");
+ logger.Add($" {msg.Value}", LogLevel.Debug);
+
+ // See explanation in LegacyIpcMessage for why this is done this way.
+ var legacyData = ((JObject)msg.Value).ToObject();
+ object value = parseObject((JObject)legacyData!.MessageData, legacyData.MessageType);
+
+ return new LegacyIpcMessage
+ {
+ Value = onLegacyIpcMessageReceived(value)
+ };
+ }
+ catch (Exception ex)
+ {
+ logger.Add($"Processing IPC message failed: {msg.Value}", exception: ex);
+ return null;
+ }
+ };
+ }
+
+ private object parseObject(JObject value, string type)
+ {
+ switch (type)
+ {
+ case nameof(LegacyIpcDifficultyCalculationRequest):
+ return value.ToObject()
+ ?? throw new InvalidOperationException($"Failed to parse request {value}");
+
+ case nameof(LegacyIpcDifficultyCalculationResponse):
+ return value.ToObject()
+ ?? throw new InvalidOperationException($"Failed to parse request {value}");
+
+ default:
+ throw new ArgumentException($"Unsupported object type {type}");
+ }
+ }
+
+ private object onLegacyIpcMessageReceived(object message)
+ {
+ switch (message)
+ {
+ case LegacyIpcDifficultyCalculationRequest req:
+ try
+ {
+ var ruleset = getLegacyRulesetFromID(req.RulesetId);
+
+ Mod[] mods = ruleset.ConvertFromLegacyMods((LegacyMods)req.Mods).ToArray();
+ WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(req.BeatmapFile, _ => ruleset);
+
+ return new LegacyIpcDifficultyCalculationResponse
+ {
+ StarRating = ruleset.CreateDifficultyCalculator(beatmap).Calculate(mods).StarRating
+ };
+ }
+ catch
+ {
+ return new LegacyIpcDifficultyCalculationResponse();
+ }
+
+ default:
+ throw new ArgumentException($"Unsupported message type {message}");
+ }
+ }
+
+ private static Ruleset getLegacyRulesetFromID(int rulesetId)
+ {
+ switch (rulesetId)
+ {
+ case 0:
+ return new OsuRuleset();
+
+ case 1:
+ return new TaikoRuleset();
+
+ case 2:
+ return new CatchRuleset();
+
+ case 3:
+ return new ManiaRuleset();
+
+ default:
+ throw new ArgumentException("Invalid ruleset id");
+ }
+ }
+ }
+}
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 898f7d5105..a9e3575a49 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -5,6 +5,7 @@ using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
+using osu.Desktop.LegacyIpc;
using osu.Framework;
using osu.Framework.Development;
using osu.Framework.Logging;
@@ -18,8 +19,10 @@ namespace osu.Desktop
{
private const string base_game_name = @"osu";
+ private static LegacyTcpIpcProvider legacyIpc;
+
[STAThread]
- public static int Main(string[] args)
+ public static void Main(string[] args)
{
// Back up the cwd before DesktopGameHost changes it
string cwd = Environment.CurrentDirectory;
@@ -69,14 +72,29 @@ namespace osu.Desktop
throw new TimeoutException(@"IPC took too long to send");
}
- return 0;
+ return;
}
// we want to allow multiple instances to be started when in debug.
if (!DebugUtils.IsDebugBuild)
{
Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error);
- return 0;
+ return;
+ }
+ }
+
+ if (host.IsPrimaryInstance)
+ {
+ try
+ {
+ Logger.Log("Starting legacy IPC provider...");
+ legacyIpc = new LegacyTcpIpcProvider();
+ legacyIpc.Bind();
+ legacyIpc.StartAsync();
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex, "Failed to start legacy IPC provider");
}
}
@@ -84,8 +102,6 @@ namespace osu.Desktop
host.Run(new TournamentGame());
else
host.Run(new OsuGameDesktop(args));
-
- return 0;
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs
index 9d1f5429a1..1aa20f4737 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs
@@ -9,7 +9,6 @@ using osu.Framework.Bindables;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.UI;
@@ -46,12 +45,6 @@ namespace osu.Game.Rulesets.Mania.Edit
[Resolved]
private EditorBeatmap beatmap { get; set; }
- [Resolved]
- private IScrollingInfo scrollingInfo { get; set; }
-
- [Resolved]
- private Bindable working { get; set; }
-
[Resolved]
private OsuColour colours { get; set; }
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
index dc858fb54f..9fe1eb7932 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
@@ -7,16 +7,12 @@ using osu.Framework.Allocation;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit.Compose.Components;
namespace osu.Game.Rulesets.Mania.Edit
{
public class ManiaSelectionHandler : EditorSelectionHandler
{
- [Resolved]
- private IScrollingInfo scrollingInfo { get; set; }
-
[Resolved]
private HitObjectComposer composer { get; set; }
diff --git a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs
index 90d3c6c4c7..9f4963b022 100644
--- a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs
@@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.Mania.UI
public JudgementResult Result { get; private set; }
- [Resolved]
- private Column column { get; set; }
-
private SkinnableDrawable skinnableExplosion;
public PoolableHitExplosion()
diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
index 861b800038..16be20f7f3 100644
--- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
+++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
@@ -12,7 +12,6 @@ using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.UI;
-using osu.Game.Screens.Play;
using osu.Game.Skinning;
using osuTK;
@@ -149,9 +148,6 @@ namespace osu.Game.Rulesets.Taiko.UI
centreHit.Colour = colours.Pink;
}
- [Resolved(canBeNull: true)]
- private GameplayClock gameplayClock { get; set; }
-
public bool OnPressed(KeyBindingPressEvent e)
{
Drawable target = null;
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
index 6e5a546e87..a73ae9dcdb 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
@@ -2,14 +2,20 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
+using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Replays;
+using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko;
using osu.Game.Scoring;
@@ -21,6 +27,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
[TestFixture]
public class LegacyScoreDecoderTest
{
+ private CultureInfo originalCulture;
+
+ [SetUp]
+ public void SetUp()
+ {
+ originalCulture = CultureInfo.CurrentCulture;
+ }
+
[Test]
public void TestDecodeManiaReplay()
{
@@ -44,6 +58,59 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
+ [Test]
+ public void TestCultureInvariance()
+ {
+ var ruleset = new OsuRuleset().RulesetInfo;
+ var scoreInfo = new TestScoreInfo(ruleset);
+ var beatmap = new TestBeatmap(ruleset);
+ var score = new Score
+ {
+ ScoreInfo = scoreInfo,
+ Replay = new Replay
+ {
+ Frames = new List
+ {
+ new OsuReplayFrame(2000, OsuPlayfield.BASE_SIZE / 2, OsuAction.LeftButton)
+ }
+ }
+ };
+
+ // the "se" culture is used here, as it encodes the negative number sign as U+2212 MINUS SIGN,
+ // rather than the classic ASCII U+002D HYPHEN-MINUS.
+ CultureInfo.CurrentCulture = new CultureInfo("se");
+
+ var encodeStream = new MemoryStream();
+
+ var encoder = new LegacyScoreEncoder(score, beatmap);
+ encoder.Encode(encodeStream);
+
+ var decodeStream = new MemoryStream(encodeStream.GetBuffer());
+
+ var decoder = new TestLegacyScoreDecoder();
+ var decodedAfterEncode = decoder.Parse(decodeStream);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(decodedAfterEncode, Is.Not.Null);
+
+ Assert.That(decodedAfterEncode.ScoreInfo.User.Username, Is.EqualTo(scoreInfo.User.Username));
+ Assert.That(decodedAfterEncode.ScoreInfo.BeatmapInfoID, Is.EqualTo(scoreInfo.BeatmapInfoID));
+ Assert.That(decodedAfterEncode.ScoreInfo.Ruleset, Is.EqualTo(scoreInfo.Ruleset));
+ Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(scoreInfo.TotalScore));
+ Assert.That(decodedAfterEncode.ScoreInfo.MaxCombo, Is.EqualTo(scoreInfo.MaxCombo));
+ Assert.That(decodedAfterEncode.ScoreInfo.Date, Is.EqualTo(scoreInfo.Date));
+
+ Assert.That(decodedAfterEncode.Replay.Frames.Count, Is.EqualTo(1));
+ });
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ CultureInfo.CurrentCulture = originalCulture;
+ }
+
private class TestLegacyScoreDecoder : LegacyScoreDecoder
{
private static readonly Dictionary rulesets = new Ruleset[]
diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs
index 2a60a7b96d..3a82cbc785 100644
--- a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs
+++ b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs
@@ -25,9 +25,6 @@ namespace osu.Game.Tests.Beatmaps
private BeatmapSetInfo importedSet;
- [Resolved]
- private BeatmapManager beatmaps { get; set; }
-
private TestBeatmapDifficultyCache difficultyCache;
private IBindable starDifficultyBindable;
diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs
index f4e0838be1..cc7e8a0c97 100644
--- a/osu.Game.Tests/Database/RulesetStoreTests.cs
+++ b/osu.Game.Tests/Database/RulesetStoreTests.cs
@@ -45,9 +45,9 @@ namespace osu.Game.Tests.Database
{
var rulesets = new RealmRulesetStore(realmFactory, storage);
- Assert.IsTrue((rulesets.AvailableRulesets.First() as RealmRuleset)?.IsManaged == false);
- Assert.IsTrue((rulesets.GetRuleset(0) as RealmRuleset)?.IsManaged == false);
- Assert.IsTrue((rulesets.GetRuleset("mania") as RealmRuleset)?.IsManaged == false);
+ Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged);
+ Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged);
+ Assert.IsFalse(rulesets.GetRuleset("mania")?.IsManaged);
});
}
}
diff --git a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
index b612899d79..28937b2120 100644
--- a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
+++ b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
@@ -18,9 +18,6 @@ namespace osu.Game.Tests.Input
[Resolved]
private FrameworkConfigManager frameworkConfigManager { get; set; }
- [Resolved]
- private OsuConfigManager osuConfigManager { get; set; }
-
[TestCase(WindowMode.Windowed)]
[TestCase(WindowMode.Borderless)]
public void TestDisableConfining(WindowMode windowMode)
diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
index 840ff20a83..42305ccd81 100644
--- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
+++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
@@ -16,6 +16,27 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
[HeadlessTest]
public class StatefulMultiplayerClientTest : MultiplayerTestScene
{
+ [Test]
+ public void TestUserAddedOnJoin()
+ {
+ var user = new APIUser { Id = 33 };
+
+ AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3);
+ AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
+ }
+
+ [Test]
+ public void TestUserRemovedOnLeave()
+ {
+ var user = new APIUser { Id = 44 };
+
+ AddStep("add user", () => Client.AddUser(user));
+ AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
+
+ AddRepeatStep("remove user multiple times", () => Client.RemoveUser(user), 3);
+ AddAssert("room has 1 user", () => Client.Room?.Users.Count == 1);
+ }
+
[Test]
public void TestPlayingUserTracking()
{
diff --git a/osu.Game.Tests/OnlinePlay/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/OnlinePlay/StatefulMultiplayerClientTest.cs
deleted file mode 100644
index 5a621ecf84..0000000000
--- a/osu.Game.Tests/OnlinePlay/StatefulMultiplayerClientTest.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using NUnit.Framework;
-using osu.Framework.Testing;
-using osu.Game.Online.API.Requests.Responses;
-using osu.Game.Tests.Visual.Multiplayer;
-
-namespace osu.Game.Tests.OnlinePlay
-{
- [HeadlessTest]
- public class StatefulMultiplayerClientTest : MultiplayerTestScene
- {
- [Test]
- public void TestUserAddedOnJoin()
- {
- var user = new APIUser { Id = 33 };
-
- AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3);
- AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
- }
-
- [Test]
- public void TestUserRemovedOnLeave()
- {
- var user = new APIUser { Id = 44 };
-
- AddStep("add user", () => Client.AddUser(user));
- AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
-
- AddRepeatStep("remove user multiple times", () => Client.RemoveUser(user), 3);
- AddAssert("room has 1 user", () => Client.Room?.Users.Count == 1);
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
index a0b27755b7..a0602e21b9 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
@@ -2,12 +2,10 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
-using osu.Game.Skinning;
using osu.Game.Skinning.Editor;
namespace osu.Game.Tests.Visual.Gameplay
@@ -16,9 +14,6 @@ namespace osu.Game.Tests.Visual.Gameplay
{
private SkinEditor skinEditor;
- [Resolved]
- private SkinManager skinManager { get; set; }
-
protected override bool Autoplay => true;
[SetUpSteps]
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs
index 723e35ed55..3074a91dc6 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs
@@ -10,7 +10,6 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
-using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
@@ -36,9 +35,6 @@ namespace osu.Game.Tests.Visual.Gameplay
private Drawable hideTarget => hudOverlay.KeyCounter;
private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First();
- [Resolved]
- private OsuConfigManager config { get; set; }
-
[Test]
public void TestComboCounterIncrementing()
{
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
index 57d60cea9e..c65595d82e 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Menus
private TestToolbar toolbar;
[Resolved]
- private RulesetStore rulesets { get; set; }
+ private IRulesetStore rulesets { get; set; }
[SetUp]
public void SetUp() => Schedule(() =>
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs
index d66603a448..1d61a5d496 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs
@@ -2,11 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
using osu.Game.Online.Rooms;
-using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Components;
@@ -18,12 +15,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchBeatmapDetailArea : OnlinePlayTestScene
{
- [Resolved]
- private BeatmapManager beatmapManager { get; set; }
-
- [Resolved]
- private RulesetStore rulesetStore { get; set; }
-
[SetUp]
public new void Setup() => Schedule(() =>
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index 4521a7fa0f..2411f39ae3 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -397,6 +397,44 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle);
}
+ [Test]
+ public void TestPlayStartsWithCorrectBeatmapWhileAtSongSelect()
+ {
+ createRoom(() => new Room
+ {
+ Name = { Value = "Test Room" },
+ Playlist =
+ {
+ new PlaylistItem
+ {
+ Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
+ Ruleset = { Value = new OsuRuleset().RulesetInfo },
+ }
+ }
+ });
+
+ AddStep("Enter song select", () =>
+ {
+ var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerScreenStack.CurrentScreen).CurrentSubScreen;
+
+ ((MultiplayerMatchSubScreen)currentSubScreen).SelectBeatmap();
+ });
+
+ AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true);
+
+ AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == client.Room?.Playlist.First().BeatmapID);
+
+ AddStep("Select next beatmap", () => InputManager.Key(Key.Down));
+
+ AddUntilStep("Beatmap doesn't match current item", () => Beatmap.Value.BeatmapInfo.OnlineID != client.Room?.Playlist.First().BeatmapID);
+
+ AddStep("start match externally", () => client.StartMatch());
+
+ AddUntilStep("play started", () => multiplayerScreenStack.CurrentScreen is Player);
+
+ AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == client.Room?.Playlist.First().BeatmapID);
+ }
+
[Test]
public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap()
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs
new file mode 100644
index 0000000000..674ee0f186
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs
@@ -0,0 +1,234 @@
+// 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.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Graphics;
+using osu.Framework.Platform;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Online.Rooms;
+using osu.Game.Rulesets;
+using osu.Game.Screens.OnlinePlay;
+using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist;
+using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Resources;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ public class TestSceneMultiplayerPlaylist : MultiplayerTestScene
+ {
+ 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 RulesetStore(ContextFactory));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
+ }
+
+ [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()
+ {
+ AddStep("import beatmap", () =>
+ {
+ beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
+ importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
+ importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0);
+ });
+
+ AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }));
+ }
+
+ [Test]
+ public void TestNonExpiredItemsAddedToQueueList()
+ {
+ assertItemInQueueListStep(1, 0);
+
+ addItemStep();
+ assertItemInQueueListStep(2, 1);
+
+ addItemStep();
+ assertItemInQueueListStep(3, 2);
+ }
+
+ [Test]
+ public void TestExpiredItemsAddedToHistoryList()
+ {
+ assertItemInQueueListStep(1, 0);
+
+ addItemStep(true);
+ assertItemInHistoryListStep(2, 0);
+
+ addItemStep(true);
+ assertItemInHistoryListStep(3, 0);
+ assertItemInHistoryListStep(2, 1);
+
+ // Initial item is still in the queue.
+ assertItemInQueueListStep(1, 0);
+ }
+
+ [Test]
+ public void TestExpiredItemsMoveToQueueList()
+ {
+ addItemStep();
+ addItemStep();
+
+ AddStep("finish current item", () => Client.FinishCurrentItem());
+
+ assertItemInHistoryListStep(1, 0);
+ assertItemInQueueListStep(2, 0);
+ assertItemInQueueListStep(3, 1);
+
+ AddStep("finish current item", () => Client.FinishCurrentItem());
+
+ assertItemInHistoryListStep(2, 0);
+ assertItemInHistoryListStep(1, 1);
+ assertItemInQueueListStep(3, 0);
+
+ AddStep("finish current item", () => Client.FinishCurrentItem());
+
+ assertItemInHistoryListStep(3, 0);
+ assertItemInHistoryListStep(2, 1);
+ assertItemInHistoryListStep(1, 2);
+ }
+
+ [Test]
+ public void TestListsClearedWhenRoomLeft()
+ {
+ addItemStep();
+ AddStep("finish current item", () => Client.FinishCurrentItem());
+
+ AddStep("leave room", () => RoomManager.PartRoom());
+ AddUntilStep("wait for room part", () => Client.Room == null);
+
+ AddUntilStep("item 0 not in lists", () => !inHistoryList(0) && !inQueueList(0));
+ AddUntilStep("item 1 not in lists", () => !inHistoryList(0) && !inQueueList(0));
+ }
+
+ [Ignore("Expired items are initially removed from the room.")]
+ [Test]
+ public void TestJoinRoomWithMixedItemsAddedInCorrectLists()
+ {
+ AddStep("leave room", () => RoomManager.PartRoom());
+ AddUntilStep("wait for room part", () => Client.Room == null);
+
+ AddStep("join room with items", () =>
+ {
+ RoomManager.CreateRoom(new Room
+ {
+ Name = { Value = "test name" },
+ Playlist =
+ {
+ new PlaylistItem
+ {
+ Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo },
+ Ruleset = { Value = Ruleset.Value }
+ },
+ new PlaylistItem
+ {
+ Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo },
+ Ruleset = { Value = Ruleset.Value },
+ Expired = true
+ }
+ }
+ });
+ });
+
+ AddUntilStep("wait for room join", () => RoomJoined);
+
+ assertItemInQueueListStep(1, 0);
+ assertItemInHistoryListStep(2, 0);
+ }
+
+ ///
+ /// Adds a step to create a new playlist item.
+ ///
+ private void addItemStep(bool expired = false) => AddStep("add item", () => Client.AddPlaylistItem(new MultiplayerPlaylistItem(new PlaylistItem
+ {
+ Beatmap = { Value = importedBeatmap },
+ BeatmapID = importedBeatmap.OnlineID ?? -1,
+ Expired = expired,
+ PlayedAt = DateTimeOffset.Now
+ })));
+
+ ///
+ /// Asserts the position of a given playlist item in the queue list.
+ ///
+ /// The item id.
+ /// The index at which the item should appear visually. The item with index 0 is at the top of the list.
+ private void assertItemInQueueListStep(int playlistItemId, int visualIndex)
+ {
+ changeDisplayModeStep(MultiplayerPlaylistDisplayMode.Queue);
+
+ AddUntilStep($"{playlistItemId} in queue at pos = {visualIndex}", () =>
+ {
+ return !inHistoryList(playlistItemId)
+ && this.ChildrenOfType()
+ .Single()
+ .ChildrenOfType()
+ .OrderBy(drawable => drawable.Position.Y)
+ .TakeWhile(drawable => drawable.Item.ID != playlistItemId)
+ .Count() == visualIndex;
+ });
+ }
+
+ ///
+ /// Asserts the position of a given playlist item in the history list.
+ ///
+ /// The item id.
+ /// The index at which the item should appear visually. The item with index 0 is at the top of the list.
+ private void assertItemInHistoryListStep(int playlistItemId, int visualIndex)
+ {
+ changeDisplayModeStep(MultiplayerPlaylistDisplayMode.History);
+
+ AddUntilStep($"{playlistItemId} in history at pos = {visualIndex}", () =>
+ {
+ return !inQueueList(playlistItemId)
+ && this.ChildrenOfType()
+ .Single()
+ .ChildrenOfType()
+ .OrderBy(drawable => drawable.Position.Y)
+ .TakeWhile(drawable => drawable.Item.ID != playlistItemId)
+ .Count() == visualIndex;
+ });
+ }
+
+ private void changeDisplayModeStep(MultiplayerPlaylistDisplayMode mode) => AddStep($"change list to {mode}", () => list.DisplayMode.Value = mode);
+
+ private bool inQueueList(int playlistItemId)
+ {
+ return this.ChildrenOfType()
+ .Single()
+ .Items.Any(i => i.ID == playlistItemId);
+ }
+
+ private bool inHistoryList(int playlistItemId)
+ {
+ return this.ChildrenOfType()
+ .Single()
+ .Items.Any(i => i.ID == playlistItemId);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
index 35c66e8cda..5aac228f4b 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
@@ -24,9 +24,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestScenePlaylistsSongSelect : OnlinePlayTestScene
{
- [Resolved]
- private BeatmapManager beatmapManager { get; set; }
-
private BeatmapManager manager;
private RulesetStore rulesets;
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs b/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs
new file mode 100644
index 0000000000..9e684e4f10
--- /dev/null
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs
@@ -0,0 +1,99 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Configuration;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Beatmaps.IO;
+using osuTK.Input;
+
+namespace osu.Game.Tests.Visual.Navigation
+{
+ public class TestSceneMouseWheelVolumeAdjust : OsuGameTestScene
+ {
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+
+ // Headless tests are always at minimum volume. This covers interactive tests, matching that initial value.
+ AddStep("Set volume to min", () => Game.Audio.Volume.Value = 0);
+ AddAssert("Volume is min", () => Game.Audio.AggregateVolume.Value == 0);
+ AddStep("Move mouse to centre", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
+ }
+
+ [Test]
+ public void TestAdjustVolumeFromMainMenu()
+ {
+ // First scroll makes volume controls appear, second adjusts volume.
+ AddRepeatStep("Adjust volume using mouse wheel", () => InputManager.ScrollVerticalBy(5), 2);
+ AddUntilStep("Volume is above zero", () => Game.Audio.AggregateVolume.Value > 0);
+ }
+
+ [Test]
+ public void TestAdjustVolumeFromPlayerWheelEnabled()
+ {
+ loadToPlayerNonBreakTime();
+
+ // First scroll makes volume controls appear, second adjusts volume.
+ AddRepeatStep("Adjust volume using mouse wheel", () => InputManager.ScrollVerticalBy(5), 2);
+ AddAssert("Volume is above zero", () => Game.Audio.Volume.Value > 0);
+ }
+
+ [Test]
+ public void TestAdjustVolumeFromPlayerWheelDisabled()
+ {
+ AddStep("disable wheel volume adjust", () => Game.LocalConfig.SetValue(OsuSetting.MouseDisableWheel, true));
+
+ loadToPlayerNonBreakTime();
+
+ // First scroll makes volume controls appear, second adjusts volume.
+ AddRepeatStep("Adjust volume using mouse wheel", () => InputManager.ScrollVerticalBy(5), 2);
+ AddAssert("Volume is still zero", () => Game.Audio.Volume.Value == 0);
+ }
+
+ [Test]
+ public void TestAdjustVolumeFromPlayerWheelDisabledHoldingAlt()
+ {
+ AddStep("disable wheel volume adjust", () => Game.LocalConfig.SetValue(OsuSetting.MouseDisableWheel, true));
+
+ loadToPlayerNonBreakTime();
+
+ // First scroll makes volume controls appear, second adjusts volume.
+ AddRepeatStep("Adjust volume using mouse wheel holding alt", () =>
+ {
+ InputManager.PressKey(Key.AltLeft);
+ InputManager.ScrollVerticalBy(5);
+ InputManager.ReleaseKey(Key.AltLeft);
+ }, 2);
+
+ AddAssert("Volume is above zero", () => Game.Audio.Volume.Value > 0);
+ }
+
+ private void loadToPlayerNonBreakTime()
+ {
+ Player player = null;
+ Screens.Select.SongSelect songSelect = null;
+ PushAndConfirm(() => songSelect = new TestSceneScreenNavigation.TestPlaySongSelect());
+ AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
+
+ AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait());
+ AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
+ AddStep("press enter", () => InputManager.Key(Key.Enter));
+
+ AddUntilStep("wait for player", () =>
+ {
+ // dismiss any notifications that may appear (ie. muted notification).
+ clickMouseInCentre();
+ return (player = Game.ScreenStack.CurrentScreen as Player) != null;
+ });
+
+ AddUntilStep("wait for play time active", () => !player.IsBreakTime.Value);
+ }
+
+ private void clickMouseInCentre()
+ {
+ InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre);
+ InputManager.Click(MouseButton.Left);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
index 2706ff5ceb..4d1e279090 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
@@ -83,9 +83,6 @@ namespace osu.Game.Tests.Visual.Navigation
[Resolved]
private OsuGameBase gameBase { get; set; }
- [Resolved]
- private GameHost host { get; set; }
-
[Test]
public void TestNullRulesetHandled()
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs
index 90f3eb64e4..63741451f3 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Online
}
[Resolved]
- private RulesetStore rulesets { get; set; }
+ private IRulesetStore rulesets { get; set; }
[Test]
public void TestMultipleRulesetsBeatmapSet()
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
index 3314e291e8..f87cca80b0 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Online
}
[Resolved]
- private RulesetStore rulesets { get; set; }
+ private IRulesetStore rulesets { get; set; }
[Test]
public void TestLoading()
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
index 7028ecf39f..9c65b2dc51 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
@@ -13,7 +13,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
-using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
@@ -47,9 +46,6 @@ namespace osu.Game.Tests.Visual.Online
[CanBeNull]
private Func> onGetMessages;
- [Resolved]
- private GameHost host { get; set; }
-
public TestSceneChatOverlay()
{
channels = Enumerable.Range(1, 10)
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
index 19e06beaad..52d5eb2c65 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Online
private TestUserListPanel evast;
[Resolved]
- private RulesetStore rulesetStore { get; set; }
+ private IRulesetStore rulesetStore { get; set; }
[SetUp]
public void SetUp() => Schedule(() =>
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
index ce8136199f..1c92bb1e38 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
@@ -4,8 +4,6 @@
using System;
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Allocation;
-using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Profile;
@@ -20,9 +18,6 @@ namespace osu.Game.Tests.Visual.Online
private readonly TestUserProfileOverlay profile;
- [Resolved]
- private IAPIProvider api { get; set; }
-
public static readonly APIUser TEST_USER = new APIUser
{
Username = @"Somebody",
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs
index acacdf8644..f246560c82 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs
@@ -9,7 +9,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
-using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
@@ -20,9 +19,6 @@ namespace osu.Game.Tests.Visual.Ranking
{
public class TestSceneContractedPanelMiddleContent : OsuTestScene
{
- [Resolved]
- private RulesetStore rulesetStore { get; set; }
-
[Test]
public void TestShowPanel()
{
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
index e5bcc08924..ede89c6096 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
@@ -135,6 +135,35 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("bpm is default", () => lastBpm != null && Precision.AlmostEquals(lastBpm.Value, 60));
}
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestEarlyActivationEffectPoint(bool earlyActivating)
+ {
+ double earlyActivationMilliseconds = earlyActivating ? 100 : 0;
+ ControlPoint actualEffectPoint = null;
+
+ AddStep($"set early activation to {earlyActivationMilliseconds}", () => beatContainer.EarlyActivationMilliseconds = earlyActivationMilliseconds);
+
+ AddStep("seek before kiai effect point", () =>
+ {
+ ControlPoint expectedEffectPoint = Beatmap.Value.Beatmap.ControlPointInfo.EffectPoints.First(ep => ep.KiaiMode);
+ actualEffectPoint = null;
+ beatContainer.AllowMistimedEventFiring = false;
+
+ beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) =>
+ {
+ if (Precision.AlmostEquals(gameplayClockContainer.CurrentTime + earlyActivationMilliseconds, expectedEffectPoint.Time, BeatSyncedContainer.MISTIMED_ALLOWANCE))
+ actualEffectPoint = effectControlPoint;
+ };
+
+ gameplayClockContainer.Seek(expectedEffectPoint.Time - earlyActivationMilliseconds);
+ });
+
+ AddUntilStep("wait for effect point", () => actualEffectPoint != null);
+
+ AddAssert("effect has kiai", () => actualEffectPoint != null && ((EffectControlPoint)actualEffectPoint).KiaiMode);
+ }
+
private class TestBeatSyncedContainer : BeatSyncedContainer
{
private const int flash_layer_height = 150;
@@ -145,6 +174,12 @@ namespace osu.Game.Tests.Visual.UserInterface
set => base.AllowMistimedEventFiring = value;
}
+ public new double EarlyActivationMilliseconds
+ {
+ get => base.EarlyActivationMilliseconds;
+ set => base.EarlyActivationMilliseconds = value;
+ }
+
private readonly InfoString timingPointCount;
private readonly InfoString currentTimingPoint;
private readonly InfoString beatCount;
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
index d30f1e8889..3fa9b8b877 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
@@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual.UserInterface
private BeatmapSetInfo testBeatmap;
private IAPIProvider api;
- [Resolved]
- private BeatmapManager beatmaps { get; set; }
-
[BackgroundDependencyLoader]
private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets)
{
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs
index 8139387a96..b678f69b8f 100644
--- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs
+++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs
@@ -6,19 +6,20 @@ using osu.Framework.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
-using osu.Game.Rulesets;
+using osu.Game.Tests.Visual;
using osu.Game.Tournament.Components;
namespace osu.Game.Tournament.Tests.Components
{
public class TestSceneTournamentBeatmapPanel : TournamentTestScene
{
+ ///
+ /// Warning: the below API instance is actually the online API, rather than the dummy API provided by the test.
+ /// It cannot be trivially replaced because setting to causes to no longer be usable.
+ ///
[Resolved]
private IAPIProvider api { get; set; }
- [Resolved]
- private RulesetStore rulesets { get; set; }
-
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs
index 3cd13df0d3..9feef36a02 100644
--- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs
+++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Tournament.Tests.Components
private IAPIProvider api { get; set; }
[Resolved]
- private RulesetStore rulesets { get; set; }
+ private IRulesetStore rulesets { get; set; }
private FillFlowContainer fillFlow;
diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs
index d149ec145b..3619aae7e0 100644
--- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs
+++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs
@@ -96,7 +96,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
Directory.CreateDirectory(flagsPath);
// Define testing files corresponding to the specific file migrations that are needed
- string bracketFile = Path.Combine(osuRoot, "bracket.json");
+ string bracketFile = Path.Combine(osuRoot, TournamentGameBase.BRACKET_FILENAME);
string drawingsConfig = Path.Combine(osuRoot, "drawings.ini");
string drawingsFile = Path.Combine(osuRoot, "drawings.txt");
@@ -133,7 +133,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
Assert.That(storage.GetFullPath("."), Is.EqualTo(migratedPath));
- Assert.True(storage.Exists("bracket.json"));
+ Assert.True(storage.Exists(TournamentGameBase.BRACKET_FILENAME));
Assert.True(storage.Exists("drawings.txt"));
Assert.True(storage.Exists("drawings_results.txt"));
diff --git a/osu.Game.Tournament/Components/TournamentModIcon.cs b/osu.Game.Tournament/Components/TournamentModIcon.cs
index 0fde263bc8..ed8a36c220 100644
--- a/osu.Game.Tournament/Components/TournamentModIcon.cs
+++ b/osu.Game.Tournament/Components/TournamentModIcon.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Tournament.Components
private readonly string modAcronym;
[Resolved]
- private RulesetStore rulesets { get; set; }
+ private IRulesetStore rulesets { get; set; }
public TournamentModIcon(string modAcronym)
{
diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs
index 02cf567837..347d368a04 100644
--- a/osu.Game.Tournament/IO/TournamentStorage.cs
+++ b/osu.Game.Tournament/IO/TournamentStorage.cs
@@ -86,7 +86,7 @@ namespace osu.Game.Tournament.IO
DeleteRecursive(source);
}
- moveFileIfExists("bracket.json", destination);
+ moveFileIfExists(TournamentGameBase.BRACKET_FILENAME, destination);
moveFileIfExists("drawings.txt", destination);
moveFileIfExists("drawings_results.txt", destination);
moveFileIfExists("drawings.ini", destination);
diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs
index a57f9fd691..5278d538d2 100644
--- a/osu.Game.Tournament/IPC/FileBasedIPC.cs
+++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Tournament.IPC
protected IAPIProvider API { get; private set; }
[Resolved]
- protected RulesetStore Rulesets { get; private set; }
+ protected IRulesetStore Rulesets { get; private set; }
[Resolved]
private GameHost host { get; set; }
diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs
index 9abf1d3adb..5d2fddffd9 100644
--- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs
@@ -25,9 +25,6 @@ namespace osu.Game.Tournament.Screens.Editors
protected override BindableList Storage => team.SeedingResults;
- [Resolved(canBeNull: true)]
- private TournamentSceneManager sceneManager { get; set; }
-
public SeedingEditorScreen(TournamentTeam team, TournamentScreen parentScreen)
: base(parentScreen)
{
@@ -38,9 +35,6 @@ namespace osu.Game.Tournament.Screens.Editors
{
public SeedingResult Model { get; }
- [Resolved]
- private LadderInfo ladderInfo { get; set; }
-
public SeedingResultRow(TournamentTeam team, SeedingResult round)
{
Model = round;
diff --git a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs
index 8e9b32231f..5a1ceecd01 100644
--- a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs
+++ b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs
@@ -21,9 +21,6 @@ namespace osu.Game.Tournament.Screens.Setup
{
public class StablePathSelectScreen : TournamentScreen
{
- [Resolved]
- private GameHost host { get; set; }
-
[Resolved(canBeNull: true)]
private TournamentSceneManager sceneManager { get; set; }
diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs
index f03f815b83..5d613894d4 100644
--- a/osu.Game.Tournament/TournamentGame.cs
+++ b/osu.Game.Tournament/TournamentGame.cs
@@ -71,7 +71,7 @@ namespace osu.Game.Tournament
loadingSpinner.Expire();
Logger.Error(t.Exception, "Couldn't load bracket with error");
- Add(new WarningBox("Your bracket.json file could not be parsed. Please check runtime.log for more details."));
+ Add(new WarningBox($"Your {BRACKET_FILENAME} file could not be parsed. Please check runtime.log for more details."));
});
return;
diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs
index d2f146c4c2..d08322a3e8 100644
--- a/osu.Game.Tournament/TournamentGameBase.cs
+++ b/osu.Game.Tournament/TournamentGameBase.cs
@@ -11,6 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.IO.Stores;
+using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Graphics;
using osu.Game.Online.API.Requests;
@@ -26,15 +27,15 @@ namespace osu.Game.Tournament
[Cached(typeof(TournamentGameBase))]
public class TournamentGameBase : OsuGameBase
{
- private const string bracket_filename = "bracket.json";
+ public const string BRACKET_FILENAME = @"bracket.json";
private LadderInfo ladder;
private TournamentStorage storage;
private DependencyContainer dependencies;
private FileBasedIPC ipc;
- protected Task BracketLoadTask => taskCompletionSource.Task;
+ protected Task BracketLoadTask => bracketLoadTaskCompletionSource.Task;
- private readonly TaskCompletionSource taskCompletionSource = new TaskCompletionSource();
+ private readonly TaskCompletionSource bracketLoadTaskCompletionSource = new TaskCompletionSource();
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
@@ -71,9 +72,9 @@ namespace osu.Game.Tournament
{
try
{
- if (storage.Exists(bracket_filename))
+ if (storage.Exists(BRACKET_FILENAME))
{
- using (Stream stream = storage.GetStream(bracket_filename, FileAccess.Read, FileMode.Open))
+ using (Stream stream = storage.GetStream(BRACKET_FILENAME, FileAccess.Read, FileMode.Open))
using (var sr = new StreamReader(stream))
ladder = JsonConvert.DeserializeObject(sr.ReadToEnd(), new JsonPointConverter());
}
@@ -144,7 +145,7 @@ namespace osu.Game.Tournament
}
catch (Exception e)
{
- taskCompletionSource.SetException(e);
+ bracketLoadTaskCompletionSource.SetException(e);
return;
}
@@ -156,7 +157,7 @@ namespace osu.Game.Tournament
dependencies.CacheAs(ipc = new FileBasedIPC());
Add(ipc);
- taskCompletionSource.SetResult(true);
+ bracketLoadTaskCompletionSource.SetResult(true);
initialisationText.Expire();
});
@@ -292,6 +293,12 @@ namespace osu.Game.Tournament
protected virtual void SaveChanges()
{
+ if (!bracketLoadTaskCompletionSource.Task.IsCompletedSuccessfully)
+ {
+ Logger.Log("Inhibiting bracket save as bracket parsing failed");
+ return;
+ }
+
foreach (var r in ladder.Rounds)
r.Matches = ladder.Matches.Where(p => p.Round.Value == r).Select(p => p.ID).ToList();
@@ -309,7 +316,7 @@ namespace osu.Game.Tournament
Converters = new JsonConverter[] { new JsonPointConverter() }
});
- using (var stream = storage.GetStream(bracket_filename, FileAccess.Write, FileMode.Create))
+ using (var stream = storage.GetStream(BRACKET_FILENAME, FileAccess.Write, FileMode.Create))
using (var sw = new StreamWriter(stream))
sw.Write(serialisedLadder);
}
diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs
index e631d35180..6d56d152f1 100644
--- a/osu.Game/Audio/PreviewTrackManager.cs
+++ b/osu.Game/Audio/PreviewTrackManager.cs
@@ -18,9 +18,6 @@ namespace osu.Game.Audio
private readonly BindableDouble muteBindable = new BindableDouble();
- [Resolved]
- private AudioManager audio { get; set; }
-
private ITrackStore trackStore;
protected TrackManagerPreviewTrack CurrentTrack;
diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs
index 8b00d0f7f2..3949e84f4a 100644
--- a/osu.Game/Beatmaps/DifficultyRecommender.cs
+++ b/osu.Game/Beatmaps/DifficultyRecommender.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps
private IAPIProvider api { get; set; }
[Resolved]
- private RulesetStore rulesets { get; set; }
+ private IRulesetStore rulesets { get; set; }
[Resolved]
private Bindable ruleset { get; set; }
@@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps
///
private int? requestedUserId;
- private readonly Dictionary recommendedDifficultyMapping = new Dictionary();
+ private readonly Dictionary recommendedDifficultyMapping = new Dictionary();
private readonly IBindable apiState = new Bindable();
@@ -101,7 +101,7 @@ namespace osu.Game.Beatmaps
/// Rulesets ordered descending by their respective recommended difficulties.
/// The currently selected ruleset will always be first.
///
- private IEnumerable orderedRulesets
+ private IEnumerable orderedRulesets
{
get
{
diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
index 6e573cc2a0..82be0559a7 100644
--- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
+++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
@@ -86,7 +86,7 @@ namespace osu.Game.Beatmaps.Drawables
}
[Resolved]
- private RulesetStore rulesets { get; set; }
+ private IRulesetStore rulesets { get; set; }
[BackgroundDependencyLoader]
private void load(OsuColour colours)
diff --git a/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs b/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs
new file mode 100644
index 0000000000..8c915e2872
--- /dev/null
+++ b/osu.Game/Beatmaps/FlatFileWorkingBeatmap.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 System;
+using System.IO;
+using osu.Framework.Audio.Track;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Beatmaps.Formats;
+using osu.Game.IO;
+using osu.Game.Rulesets;
+using osu.Game.Skinning;
+
+namespace osu.Game.Beatmaps
+{
+ ///
+ /// A which can be constructed directly from a .osu file, providing an implementation for
+ /// .
+ ///
+ public class FlatFileWorkingBeatmap : WorkingBeatmap
+ {
+ private readonly Beatmap beatmap;
+
+ public FlatFileWorkingBeatmap(string file, Func rulesetProvider, int? beatmapId = null)
+ : this(readFromFile(file), rulesetProvider, beatmapId)
+ {
+ }
+
+ private FlatFileWorkingBeatmap(Beatmap beatmap, Func rulesetProvider, int? beatmapId = null)
+ : base(beatmap.BeatmapInfo, null)
+ {
+ this.beatmap = beatmap;
+
+ beatmap.BeatmapInfo.Ruleset = rulesetProvider(beatmap.BeatmapInfo.RulesetID).RulesetInfo;
+
+ if (beatmapId.HasValue)
+ beatmap.BeatmapInfo.OnlineID = beatmapId;
+ }
+
+ private static Beatmap readFromFile(string filename)
+ {
+ using (var stream = File.OpenRead(filename))
+ using (var reader = new LineBufferedReader(stream))
+ return Decoder.GetDecoder(reader).Decode(reader);
+ }
+
+ protected override IBeatmap GetBeatmap() => beatmap;
+ protected override Texture GetBackground() => throw new NotImplementedException();
+ protected override Track GetBeatmapTrack() => throw new NotImplementedException();
+ protected internal override ISkin GetSkin() => throw new NotImplementedException();
+ public override Stream GetStream(string storagePath) => throw new NotImplementedException();
+ }
+}
diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs
index ad23874b2e..77bda00107 100644
--- a/osu.Game/Collections/CollectionFilterDropdown.cs
+++ b/osu.Game/Collections/CollectionFilterDropdown.cs
@@ -13,7 +13,6 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
-using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osuTK;
@@ -193,9 +192,6 @@ namespace osu.Game.Collections
[NotNull]
protected new CollectionFilterMenuItem Item => ((DropdownMenuItem)base.Item).Value;
- [Resolved]
- private OsuColour colours { get; set; }
-
[Resolved]
private IBindable beatmap { get; set; }
diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs
index 9ff92032b7..c4f991094c 100644
--- a/osu.Game/Collections/CollectionManager.cs
+++ b/osu.Game/Collections/CollectionManager.cs
@@ -40,9 +40,6 @@ namespace osu.Game.Collections
public readonly BindableList Collections = new BindableList();
- [Resolved]
- private GameHost host { get; set; }
-
[Resolved]
private BeatmapManager beatmaps { get; set; }
diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
index 6e4901ab1a..2024d18570 100644
--- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
+++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
@@ -111,7 +111,7 @@ namespace osu.Game.Graphics.Containers
if (clock == null)
return;
- double currentTrackTime = clock.CurrentTime;
+ double currentTrackTime = clock.CurrentTime + EarlyActivationMilliseconds;
if (Beatmap.Value.TrackLoaded && Beatmap.Value.BeatmapLoaded)
{
@@ -132,13 +132,11 @@ namespace osu.Game.Graphics.Containers
{
// this may be the case where the beat syncing clock has been paused.
// we still want to show an idle animation, so use this container's time instead.
- currentTrackTime = Clock.CurrentTime;
+ currentTrackTime = Clock.CurrentTime + EarlyActivationMilliseconds;
timingPoint = TimingControlPoint.DEFAULT;
effectPoint = EffectControlPoint.DEFAULT;
}
- currentTrackTime += EarlyActivationMilliseconds;
-
double beatLength = timingPoint.BeatLength / Divisor;
while (beatLength < MinimumBeatLength)
diff --git a/osu.Game/IPC/ArchiveImportIPCChannel.cs b/osu.Game/IPC/ArchiveImportIPCChannel.cs
index d9d0e4c0ea..f381aad39a 100644
--- a/osu.Game/IPC/ArchiveImportIPCChannel.cs
+++ b/osu.Game/IPC/ArchiveImportIPCChannel.cs
@@ -18,6 +18,7 @@ namespace osu.Game.IPC
: base(host)
{
this.importer = importer;
+
MessageReceived += msg =>
{
Debug.Assert(importer != null);
@@ -25,6 +26,8 @@ namespace osu.Game.IPC
{
if (t.Exception != null) throw t.Exception;
}, TaskContinuationOptions.OnlyOnFaulted);
+
+ return null;
};
}
diff --git a/osu.Game/Localisation/MouseSettingsStrings.cs b/osu.Game/Localisation/MouseSettingsStrings.cs
index 5e894c4e0b..fd7225ad2e 100644
--- a/osu.Game/Localisation/MouseSettingsStrings.cs
+++ b/osu.Game/Localisation/MouseSettingsStrings.cs
@@ -35,9 +35,14 @@ namespace osu.Game.Localisation
public static LocalisableString ConfineMouseMode => new TranslatableString(getKey(@"confine_mouse_mode"), @"Confine mouse cursor to window");
///
- /// "Disable mouse wheel during gameplay"
+ /// "Disable mouse wheel adjusting volume during gameplay"
///
- public static LocalisableString DisableMouseWheel => new TranslatableString(getKey(@"disable_mouse_wheel"), @"Disable mouse wheel during gameplay");
+ public static LocalisableString DisableMouseWheelVolumeAdjust => new TranslatableString(getKey(@"disable_mouse_wheel_volume_adjust"), @"Disable mouse wheel adjusting volume during gameplay");
+
+ ///
+ /// "Volume can still be adjusted using the mouse wheel by holding "Alt""
+ ///
+ public static LocalisableString DisableMouseWheelVolumeAdjustTooltip => new TranslatableString(getKey(@"disable_mouse_wheel_volume_adjust_tooltip"), @"Volume can still be adjusted using the mouse wheel by holding ""Alt""");
///
/// "Disable mouse buttons during gameplay"
diff --git a/osu.Game/Models/RealmRuleset.cs b/osu.Game/Models/RealmRuleset.cs
index 9a7488fda2..b959d0b4dc 100644
--- a/osu.Game/Models/RealmRuleset.cs
+++ b/osu.Game/Models/RealmRuleset.cs
@@ -50,6 +50,8 @@ namespace osu.Game.Models
public bool Equals(RealmRuleset? other) => other != null && OnlineID == other.OnlineID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo;
+ public bool Equals(IRulesetInfo? other) => other is RealmRuleset b && Equals(b);
+
public override string ToString() => Name;
public RealmRuleset Clone() => new RealmRuleset
diff --git a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs
index 6cd45a41df..671f543422 100644
--- a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs
+++ b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs
@@ -26,9 +26,12 @@ namespace osu.Game.Online.API.Requests
{
var request = base.CreateWebRequest();
- request.AddParameter(@"id", beatmapInfo.OnlineID.ToString());
- request.AddParameter(@"checksum", beatmapInfo.MD5Hash);
- request.AddParameter(@"filename", filename);
+ if (beatmapInfo.OnlineID > 0)
+ request.AddParameter(@"id", beatmapInfo.OnlineID.ToString());
+ if (!string.IsNullOrEmpty(beatmapInfo.MD5Hash))
+ request.AddParameter(@"checksum", beatmapInfo.MD5Hash);
+ if (!string.IsNullOrEmpty(filename))
+ request.AddParameter(@"filename", filename);
return request;
}
diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs
index e32451fc2f..28da5222f9 100644
--- a/osu.Game/Online/API/Requests/GetUserRequest.cs
+++ b/osu.Game/Online/API/Requests/GetUserRequest.cs
@@ -9,7 +9,7 @@ namespace osu.Game.Online.API.Requests
public class GetUserRequest : APIRequest
{
public readonly string Lookup;
- public readonly RulesetInfo Ruleset;
+ public readonly IRulesetInfo Ruleset;
private readonly LookupType lookupType;
///
@@ -24,7 +24,7 @@ namespace osu.Game.Online.API.Requests
///
/// The user to get.
/// The ruleset to get the user's info for.
- public GetUserRequest(long? userId = null, RulesetInfo ruleset = null)
+ public GetUserRequest(long? userId = null, IRulesetInfo ruleset = null)
{
Lookup = userId.ToString();
lookupType = LookupType.Id;
@@ -36,7 +36,7 @@ namespace osu.Game.Online.API.Requests
///
/// The user to get.
/// The ruleset to get the user's info for.
- public GetUserRequest(string username = null, RulesetInfo ruleset = null)
+ public GetUserRequest(string username = null, IRulesetInfo ruleset = null)
{
Lookup = username;
lookupType = LookupType.Username;
diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs
index e01c7c9e49..644c2e2a99 100644
--- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs
+++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs
@@ -65,9 +65,6 @@ namespace osu.Game.Online.Leaderboards
[Resolved(CanBeNull = true)]
private SongSelect songSelect { get; set; }
- [Resolved]
- private ScoreManager scoreManager { get; set; }
-
[Resolved]
private Storage storage { get; set; }
diff --git a/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs
index ab4210251e..3db497bd6a 100644
--- a/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs
+++ b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs
@@ -3,13 +3,11 @@
using System;
using System.Threading;
-using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
-using osu.Game.Rulesets;
using osuTK;
namespace osu.Game.Online.Leaderboards
@@ -25,9 +23,6 @@ namespace osu.Game.Online.Leaderboards
protected override bool StartHidden => true;
- [Resolved]
- private RulesetStore rulesets { get; set; }
-
public UserTopScoreContainer(Func createScoreDelegate)
{
this.createScoreDelegate = createScoreDelegate;
diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
index 0822c29376..3ddd6ce62b 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
@@ -32,12 +32,36 @@ namespace osu.Game.Online.Multiplayer
///
public event Action? RoomUpdated;
+ ///
+ /// Invoked when a new user joins the room.
+ ///
public event Action? UserJoined;
+ ///
+ /// Invoked when a user leaves the room of their own accord.
+ ///
public event Action? UserLeft;
+ ///
+ /// Invoked when a user was kicked from the room forcefully.
+ ///
public event Action? UserKicked;
+ ///
+ /// Invoked when a new item is added to the playlist.
+ ///
+ public event Action? ItemAdded;
+
+ ///
+ /// Invoked when a playlist item is removed from the playlist. The provided long is the playlist's item ID.
+ ///
+ public event Action? ItemRemoved;
+
+ ///
+ /// Invoked when a playlist item's details change.
+ ///
+ public event Action? ItemChanged;
+
///
/// Invoked when the multiplayer server requests the current beatmap to be loaded into play.
///
@@ -94,7 +118,7 @@ namespace osu.Game.Online.Multiplayer
protected IAPIProvider API { get; private set; } = null!;
[Resolved]
- protected RulesetStore Rulesets { get; private set; } = null!;
+ protected IRulesetStore Rulesets { get; private set; } = null!;
[Resolved]
private UserLookupCache userLookupCache { get; set; } = null!;
@@ -617,6 +641,7 @@ namespace osu.Game.Online.Multiplayer
Room.Playlist.Add(item);
APIRoom.Playlist.Add(playlistItem);
+ ItemAdded?.Invoke(item);
RoomUpdated?.Invoke();
});
}
@@ -636,6 +661,7 @@ namespace osu.Game.Online.Multiplayer
Room.Playlist.Remove(Room.Playlist.Single(existing => existing.ID == playlistItemId));
APIRoom.Playlist.RemoveAll(existing => existing.ID == playlistItemId);
+ ItemRemoved?.Invoke(playlistItemId);
RoomUpdated?.Invoke();
});
@@ -666,6 +692,7 @@ namespace osu.Game.Online.Multiplayer
if (CurrentMatchPlayingItem.Value?.ID == playlistItem.ID)
CurrentMatchPlayingItem.Value = playlistItem;
+ ItemChanged?.Invoke(item);
RoomUpdated?.Invoke();
});
}
@@ -706,6 +733,9 @@ namespace osu.Game.Online.Multiplayer
var apiBeatmap = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false);
var ruleset = Rulesets.GetRuleset(item.RulesetID);
+
+ Debug.Assert(ruleset != null);
+
var rulesetInstance = ruleset.CreateInstance();
var playlistItem = new PlaylistItem
@@ -714,7 +744,9 @@ namespace osu.Game.Online.Multiplayer
OwnerID = item.OwnerID,
Beatmap = { Value = apiBeatmap },
Ruleset = { Value = ruleset },
- Expired = item.Expired
+ Expired = item.Expired,
+ PlaylistOrder = item.PlaylistOrder,
+ PlayedAt = item.PlayedAt
};
playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance)));
diff --git a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs
index 6ca0b822f3..cee6d8fe41 100644
--- a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs
+++ b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs
@@ -39,6 +39,22 @@ namespace osu.Game.Online.Rooms
[Key(7)]
public bool Expired { get; set; }
+ ///
+ /// The order in which this will be played relative to others.
+ /// Playlist items should be played in increasing order (lower values are played first).
+ ///
+ ///
+ /// This is only valid for items which are not . The value for expired items is undefined and should not be used.
+ ///
+ [Key(8)]
+ public ushort PlaylistOrder { get; set; }
+
+ ///
+ /// The date when this was played.
+ ///
+ [Key(9)]
+ public DateTimeOffset? PlayedAt { get; set; }
+
public MultiplayerPlaylistItem()
{
}
@@ -52,6 +68,8 @@ namespace osu.Game.Online.Rooms
RequiredMods = item.RequiredMods.Select(m => new APIMod(m)).ToArray();
AllowedMods = item.AllowedMods.Select(m => new APIMod(m)).ToArray();
Expired = item.Expired;
+ PlaylistOrder = item.PlaylistOrder ?? 0;
+ PlayedAt = item.PlayedAt;
}
}
}
diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs
index a1480865b8..b8700fd067 100644
--- a/osu.Game/Online/Rooms/PlaylistItem.cs
+++ b/osu.Game/Online/Rooms/PlaylistItem.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Diagnostics;
using System.Linq;
using Newtonsoft.Json;
using osu.Framework.Bindables;
@@ -33,6 +34,12 @@ namespace osu.Game.Online.Rooms
[JsonProperty("expired")]
public bool Expired { get; set; }
+ [JsonProperty("playlist_order")]
+ public ushort? PlaylistOrder { get; set; }
+
+ [JsonProperty("played_at")]
+ public DateTimeOffset? PlayedAt { get; set; }
+
[JsonIgnore]
public IBindable Valid => valid;
@@ -79,11 +86,13 @@ namespace osu.Game.Online.Rooms
public void MarkInvalid() => valid.Value = false;
- public void MapObjects(RulesetStore rulesets)
+ public void MapObjects(IRulesetStore rulesets)
{
Beatmap.Value ??= apiBeatmap;
Ruleset.Value ??= rulesets.GetRuleset(RulesetID);
+ Debug.Assert(Ruleset.Value != null);
+
Ruleset rulesetInstance = Ruleset.Value.CreateInstance();
if (allowedModsBacking != null)
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index ec296a03de..2e266e32ff 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -196,6 +196,7 @@ namespace osu.Game
runMigrations();
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory, Storage));
+ dependencies.CacheAs(RulesetStore);
dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client", contextFactory));
diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs
index 38f2bdb34f..f5b4785264 100644
--- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs
+++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs
@@ -15,7 +15,6 @@ using osu.Framework.Threading;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
-using osu.Game.Rulesets;
using osu.Game.Resources.Localisation.Web;
using osuTK;
using osuTK.Graphics;
@@ -61,9 +60,6 @@ namespace osu.Game.Overlays.BeatmapListing
[Resolved]
private IAPIProvider api { get; set; }
- [Resolved]
- private RulesetStore rulesets { get; set; }
-
public BeatmapListingFilterControl()
{
RelativeSizeAxes = Axes.X;
diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs
index a9723c9c62..25aed4c980 100644
--- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs
+++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Overlays.BeatmapSet
}
[Resolved]
- private RulesetStore rulesets { get; set; }
+ private IRulesetStore rulesets { get; set; }
private void onRulesetChanged(ValueChangedEvent ruleset)
{
@@ -57,8 +57,13 @@ namespace osu.Game.Overlays.BeatmapSet
if (ruleset.NewValue == null)
return;
+ var rulesetInstance = rulesets.GetRuleset(ruleset.NewValue.OnlineID)?.CreateInstance();
+
+ if (rulesetInstance == null)
+ return;
+
modsContainer.Add(new ModButton(new ModNoMod()));
- modsContainer.AddRange(rulesets.GetRuleset(ruleset.NewValue.OnlineID).CreateInstance().AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m)));
+ modsContainer.AddRange(rulesetInstance.AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m)));
modsContainer.ForEach(button =>
{
diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs
index fa5a7c66d0..b9d3854066 100644
--- a/osu.Game/Overlays/BeatmapSetOverlay.cs
+++ b/osu.Game/Overlays/BeatmapSetOverlay.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
-using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -12,7 +11,6 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Overlays.BeatmapSet.Scores;
using osu.Game.Overlays.Comments;
-using osu.Game.Rulesets;
using osuTK;
using osuTK.Graphics;
@@ -24,9 +22,6 @@ namespace osu.Game.Overlays
public const float Y_PADDING = 25;
public const float RIGHT_WIDTH = 275;
- [Resolved]
- private RulesetStore rulesets { get; set; }
-
private readonly Bindable beatmapSet = new Bindable();
// receive input outside our bounds so we can trigger a close event on ourselves.
diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs
index 269ed81bb5..0844975906 100644
--- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs
+++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs
@@ -43,9 +43,6 @@ namespace osu.Game.Overlays.Dashboard
};
}
- [Resolved]
- private IAPIProvider api { get; set; }
-
[Resolved]
private UserLookupCache users { get; set; }
diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs
index affe9ecb0c..130ae44273 100644
--- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs
+++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs
@@ -1,21 +1,20 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osuTK;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Online.API;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using osu.Game.Graphics.UserInterface;
-using osu.Game.Rulesets;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Graphics;
using osu.Framework.Localisation;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
+using osuTK;
namespace osu.Game.Overlays.Profile.Sections
{
@@ -24,9 +23,6 @@ namespace osu.Game.Overlays.Profile.Sections
[Resolved]
private IAPIProvider api { get; set; }
- [Resolved]
- protected RulesetStore Rulesets { get; private set; }
-
protected int VisiblePages;
protected int ItemsPerPage;
diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs
index cb8dae0bbc..7a27c6e4e1 100644
--- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs
+++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
private IAPIProvider api { get; set; }
[Resolved]
- private RulesetStore rulesets { get; set; }
+ private IRulesetStore rulesets { get; set; }
private readonly APIRecentActivity activity;
diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs
index cc553ad361..a37f762532 100644
--- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs
+++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs
@@ -1,21 +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.Framework.Graphics;
-using osu.Framework.Bindables;
-using osu.Game.Rulesets;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Online.API.Requests.Responses;
-using osuTK;
-using osu.Framework.Allocation;
-using osu.Game.Online.API;
-using osu.Game.Online.API.Requests;
-using osu.Game.Overlays.Rankings.Tables;
using System.Linq;
using System.Threading;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps.Drawables.Cards;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Overlays.Rankings.Tables;
+using osu.Game.Rulesets;
+using osuTK;
namespace osu.Game.Overlays.Rankings
{
@@ -29,9 +29,6 @@ namespace osu.Game.Overlays.Rankings
[Resolved]
private IAPIProvider api { get; set; }
- [Resolved]
- private RulesetStore rulesets { get; set; }
-
private CancellationTokenSource cancellationToken;
private GetSpotlightRankingsRequest getRankingsRequest;
private GetSpotlightsRequest spotlightsRequest;
diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
index 0334167759..4235dc0a05 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
@@ -67,7 +67,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
},
new SettingsCheckbox
{
- LabelText = MouseSettingsStrings.DisableMouseWheel,
+ LabelText = MouseSettingsStrings.DisableMouseWheelVolumeAdjust,
+ TooltipText = MouseSettingsStrings.DisableMouseWheelVolumeAdjustTooltip,
Current = osuConfig.GetBindable(OsuSetting.MouseDisableWheel)
},
new SettingsCheckbox
diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs
index 6f0b433acb..789ed457a4 100644
--- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs
+++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs
@@ -5,19 +5,14 @@ using System.Linq;
using Markdig.Extensions.Yaml;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
-using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Game.Graphics.Containers.Markdown;
-using osu.Game.Online.API;
namespace osu.Game.Overlays.Wiki.Markdown
{
public class WikiMarkdownContainer : OsuMarkdownContainer
{
- [Resolved]
- private IAPIProvider api { get; set; }
-
public string CurrentPath
{
set => DocumentUrl = value;
diff --git a/osu.Game/Rulesets/IRulesetInfo.cs b/osu.Game/Rulesets/IRulesetInfo.cs
index 4e529a73fb..6599e0d59d 100644
--- a/osu.Game/Rulesets/IRulesetInfo.cs
+++ b/osu.Game/Rulesets/IRulesetInfo.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 osu.Game.Database;
#nullable enable
@@ -10,7 +11,7 @@ namespace osu.Game.Rulesets
///
/// A representation of a ruleset's metadata.
///
- public interface IRulesetInfo : IHasOnlineID
+ public interface IRulesetInfo : IHasOnlineID, IEquatable
{
///
/// The user-exposed name of this ruleset.
diff --git a/osu.Game/Rulesets/IRulesetStore.cs b/osu.Game/Rulesets/IRulesetStore.cs
new file mode 100644
index 0000000000..08d907810b
--- /dev/null
+++ b/osu.Game/Rulesets/IRulesetStore.cs
@@ -0,0 +1,31 @@
+// 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;
+
+#nullable enable
+
+namespace osu.Game.Rulesets
+{
+ public interface IRulesetStore
+ {
+ ///
+ /// Retrieve a ruleset using a known ID.
+ ///
+ /// The ruleset's internal ID.
+ /// A ruleset, if available, else null.
+ IRulesetInfo? GetRuleset(int id);
+
+ ///
+ /// Retrieve a ruleset using a known short name.
+ ///
+ /// The ruleset's short name.
+ /// A ruleset, if available, else null.
+ IRulesetInfo? GetRuleset(string shortName);
+
+ ///
+ /// All available rulesets.
+ ///
+ IEnumerable AvailableRulesets { get; }
+ }
+}
diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs
index 4a146c05bf..d018cc4194 100644
--- a/osu.Game/Rulesets/RulesetInfo.cs
+++ b/osu.Game/Rulesets/RulesetInfo.cs
@@ -49,6 +49,8 @@ namespace osu.Game.Rulesets
public override bool Equals(object obj) => obj is RulesetInfo rulesetInfo && Equals(rulesetInfo);
+ public bool Equals(IRulesetInfo other) => other is RulesetInfo b && Equals(b);
+
[SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")]
public override int GetHashCode()
{
diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs
index 6dd036c0e6..5cc6a75f43 100644
--- a/osu.Game/Rulesets/RulesetStore.cs
+++ b/osu.Game/Rulesets/RulesetStore.cs
@@ -13,7 +13,7 @@ using osu.Game.Database;
namespace osu.Game.Rulesets
{
- public class RulesetStore : DatabaseBackedStore, IDisposable
+ public class RulesetStore : DatabaseBackedStore, IRulesetStore, IDisposable
{
private const string ruleset_library_prefix = "osu.Game.Rulesets";
@@ -236,5 +236,13 @@ namespace osu.Game.Rulesets
{
AppDomain.CurrentDomain.AssemblyResolve -= resolveRulesetDependencyAssembly;
}
+
+ #region Implementation of IRulesetStore
+
+ IRulesetInfo IRulesetStore.GetRuleset(int id) => GetRuleset(id);
+ IRulesetInfo IRulesetStore.GetRuleset(string shortName) => GetRuleset(shortName);
+ IEnumerable IRulesetStore.AvailableRulesets => AvailableRulesets;
+
+ #endregion
}
}
diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs
index 52aecb27de..d0bbf859af 100644
--- a/osu.Game/Rulesets/UI/Playfield.cs
+++ b/osu.Game/Rulesets/UI/Playfield.cs
@@ -19,7 +19,6 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Skinning;
using osuTK;
using System.Diagnostics;
-using osu.Framework.Audio.Sample;
namespace osu.Game.Rulesets.UI
{
@@ -88,9 +87,6 @@ namespace osu.Game.Rulesets.UI
[Resolved(CanBeNull = true)]
private IReadOnlyList mods { get; set; }
- [Resolved]
- private ISampleStore sampleStore { get; set; }
-
///
/// Creates a new .
///
diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs
index 7b8cacb35b..3d67aa9558 100644
--- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs
+++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Scoring.Legacy
sw.Write(LATEST_VERSION);
sw.Write(score.ScoreInfo.BeatmapInfo.MD5Hash);
sw.Write(score.ScoreInfo.UserString);
- sw.Write($"lazer-{score.ScoreInfo.UserString}-{score.ScoreInfo.Date}".ComputeMD5Hash());
+ sw.Write(FormattableString.Invariant($"lazer-{score.ScoreInfo.UserString}-{score.ScoreInfo.Date}").ComputeMD5Hash());
sw.Write((ushort)(score.ScoreInfo.GetCount300() ?? 0));
sw.Write((ushort)(score.ScoreInfo.GetCount100() ?? 0));
sw.Write((ushort)(score.ScoreInfo.GetCount50() ?? 0));
@@ -110,7 +110,9 @@ namespace osu.Game.Scoring.Legacy
}
}
- replayData.AppendFormat(@"{0}|{1}|{2}|{3},", -12345, 0, 0, 0);
+ // Warning: this is purposefully hardcoded as a string rather than interpolating, as in some cultures the minus sign is not encoded as the standard ASCII U+00C2 codepoint,
+ // which then would break decoding.
+ replayData.Append(@"-12345|0|0|0");
return replayData.ToString();
}
}
diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs
index 4629f9b540..f0e643f805 100644
--- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs
@@ -1,20 +1,15 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Graphics;
namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
{
public class GroupVisualisation : CompositeDrawable
{
- [Resolved]
- private OsuColour colours { get; set; }
-
public readonly ControlPointGroup Group;
private readonly IBindableList controlPoints = new BindableList();
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
index b8fa05e7eb..265f56534f 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
@@ -279,9 +279,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
editorClock.Start();
}
- [Resolved]
- private EditorBeatmap beatmap { get; set; }
-
[Resolved]
private IBeatSnapProvider beatSnapProvider { get; set; }
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs
index 2b2e66fb18..9610f6424c 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs
@@ -1,12 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Graphics;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
@@ -16,9 +14,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private readonly IBindableList controlPoints = new BindableList();
- [Resolved]
- private OsuColour colours { get; set; }
-
public TimelineControlPointGroup(ControlPointGroup group)
{
Group = group;
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs
index 80aa6972b1..1839b0507d 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs
@@ -184,9 +184,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private SamplePointPiece sampleOverrideDisplay;
private DifficultyPointPiece difficultyOverrideDisplay;
- [Resolved]
- private EditorBeatmap beatmap { get; set; }
-
private DifficultyControlPoint difficultyControlPoint;
private SampleControlPoint sampleControlPoint;
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index ac71298f36..48489c60ab 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -109,9 +109,6 @@ namespace osu.Game.Screens.Edit
[Resolved]
private IAPIProvider api { get; set; }
- [Resolved]
- private MusicController music { get; set; }
-
[Cached]
public readonly EditorClipboard Clipboard = new EditorClipboard();
diff --git a/osu.Game/Screens/Edit/EditorRoundedScreen.cs b/osu.Game/Screens/Edit/EditorRoundedScreen.cs
index 7f7b3abc2a..62f40f0325 100644
--- a/osu.Game/Screens/Edit/EditorRoundedScreen.cs
+++ b/osu.Game/Screens/Edit/EditorRoundedScreen.cs
@@ -5,7 +5,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
using osu.Game.Overlays;
namespace osu.Game.Screens.Edit
@@ -14,9 +13,6 @@ namespace osu.Game.Screens.Edit
{
public const int HORIZONTAL_PADDING = 100;
- [Resolved]
- private OsuColour colours { get; set; }
-
private Container roundedContent;
protected override Container Content => roundedContent;
diff --git a/osu.Game/Screens/Edit/EditorTable.cs b/osu.Game/Screens/Edit/EditorTable.cs
index ab8bd6a3bc..a67a060134 100644
--- a/osu.Game/Screens/Edit/EditorTable.cs
+++ b/osu.Game/Screens/Edit/EditorTable.cs
@@ -62,9 +62,6 @@ namespace osu.Game.Screens.Edit
private readonly Box hoveredBackground;
- [Resolved]
- private EditorClock clock { get; set; }
-
public RowBackground(object item)
{
Item = item;
diff --git a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs
index f833bc49f7..d1e35ae20d 100644
--- a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs
+++ b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs
@@ -15,7 +15,6 @@ using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Database;
-using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osuTK;
@@ -36,9 +35,6 @@ namespace osu.Game.Screens.Edit.Setup
[Resolved]
private OsuGameBase game { get; set; }
- [Resolved]
- private SectionsContainer sectionsContainer { get; set; }
-
public FileChooserLabelledTextBox(params string[] handledExtensions)
{
this.handledExtensions = handledExtensions;
diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs
index 7a98cf63c3..1e6899e05f 100644
--- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs
+++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs
@@ -132,9 +132,6 @@ namespace osu.Game.Screens.Edit.Timing
controlPoints.BindTo(group.ControlPoints);
}
- [Resolved]
- private OsuColour colours { get; set; }
-
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs
index fd238feeac..cadcdebc6e 100644
--- a/osu.Game/Screens/Edit/Verify/IssueList.cs
+++ b/osu.Game/Screens/Edit/Verify/IssueList.cs
@@ -23,9 +23,6 @@ namespace osu.Game.Screens.Edit.Verify
{
private IssueTable table;
- [Resolved]
- private EditorClock clock { get; set; }
-
[Resolved]
private IBindable workingBeatmap { get; set; }
diff --git a/osu.Game/Screens/Menu/StorageErrorDialog.cs b/osu.Game/Screens/Menu/StorageErrorDialog.cs
index dcaad4013a..250623ec68 100644
--- a/osu.Game/Screens/Menu/StorageErrorDialog.cs
+++ b/osu.Game/Screens/Menu/StorageErrorDialog.cs
@@ -15,9 +15,6 @@ namespace osu.Game.Screens.Menu
[Resolved]
private DialogOverlay dialogOverlay { get; set; }
- [Resolved]
- private OsuGameBase osuGame { get; set; }
-
public StorageErrorDialog(OsuStorage storage, OsuStorageError error)
{
HeaderText = "osu! storage error";
diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
index ddfdab18f7..02565c6ebe 100644
--- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
@@ -9,7 +9,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Logging;
-using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
@@ -28,10 +27,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
private readonly Bindable joinedRoom = new Bindable();
[Resolved]
- private RulesetStore rulesets { get; set; }
-
- [Resolved]
- private BeatmapManager beatmaps { get; set; }
+ private IRulesetStore rulesets { get; set; }
[Resolved]
private IAPIProvider api { get; set; }
diff --git a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs
index b9d2bdf23e..22842fbb9e 100644
--- a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System.Threading.Tasks;
-using osu.Framework.Allocation;
using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Components
@@ -12,9 +11,6 @@ namespace osu.Game.Screens.OnlinePlay.Components
///
public class SelectionPollingComponent : RoomPollingComponent
{
- [Resolved]
- private IRoomManager roomManager { get; set; }
-
private readonly Room room;
public SelectionPollingComponent(Room room)
diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs
index 6deca0482a..f2d31c8e67 100644
--- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs
+++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs
@@ -1,9 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Collections.Generic;
using System.Collections.Specialized;
-using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
@@ -22,13 +20,11 @@ namespace osu.Game.Screens.OnlinePlay
private readonly bool allowSelection;
private readonly bool showItemOwner;
- public DrawableRoomPlaylist(bool allowEdit, bool allowSelection, bool reverse = false, bool showItemOwner = false)
+ public DrawableRoomPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false)
{
this.allowEdit = allowEdit;
this.allowSelection = allowSelection;
this.showItemOwner = showItemOwner;
-
- ((ReversibleFillFlowContainer)ListContainer).Reverse = reverse;
}
protected override void LoadComplete()
@@ -53,7 +49,7 @@ namespace osu.Game.Screens.OnlinePlay
d.ScrollbarVisible = false;
});
- protected override FillFlowContainer> CreateListFillFlowContainer() => new ReversibleFillFlowContainer
+ protected override FillFlowContainer> CreateListFillFlowContainer() => new FillFlowContainer>
{
Spacing = new Vector2(0, 2)
};
@@ -76,22 +72,5 @@ namespace osu.Game.Screens.OnlinePlay
Items.Remove(item);
}
-
- private class ReversibleFillFlowContainer : FillFlowContainer>
- {
- private bool reverse;
-
- public bool Reverse
- {
- get => reverse;
- set
- {
- reverse = value;
- Invalidate();
- }
- }
-
- public override IEnumerable FlowingChildren => Reverse ? base.FlowingChildren.OrderBy(d => -GetLayoutPosition(d)) : base.FlowingChildren;
- }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
index 9920883078..0502c4abe6 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
@@ -30,9 +30,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
public readonly Room Room;
- [Resolved]
- private BeatmapManager beatmaps { get; set; }
-
protected Container ButtonsContainer { get; private set; }
private readonly Bindable roomType = new Bindable();
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
index 54c762b8ce..f4d7823fcc 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
@@ -33,9 +33,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
[Resolved]
private IRoomManager roomManager { get; set; }
- [Resolved(CanBeNull = true)]
- private LoungeSubScreen loungeSubScreen { get; set; }
-
// handle deselection
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
index 7c5ed3f5cc..184ac2c563 100644
--- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
@@ -319,6 +319,16 @@ namespace osu.Game.Screens.OnlinePlay.Match
protected void StartPlay()
{
+ // User may be at song select or otherwise when the host starts gameplay.
+ // Ensure that they first return to this screen, else global bindables (beatmap etc.) may be in a bad state.
+ if (!this.IsCurrentScreen())
+ {
+ this.MakeCurrent();
+
+ Schedule(StartPlay);
+ return;
+ }
+
sampleStart?.Play();
// fallback is to allow this class to operate when there is no parent OnlineScreen (testing purposes).
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
index 34edc1ccd1..39d60a0b05 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
@@ -12,7 +12,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Screens;
-using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@@ -20,7 +19,6 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
-using osu.Game.Rulesets;
using osu.Game.Screens.OnlinePlay.Match.Components;
using osuTK;
@@ -84,12 +82,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
[Resolved]
private MultiplayerClient client { get; set; }
- [Resolved]
- private Bindable beatmap { get; set; }
-
- [Resolved]
- private Bindable ruleset { get; set; }
-
[Resolved]
private OngoingOperationTracker ongoingOperationTracker { get; set; }
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs
index ce988e377f..874113d859 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs
@@ -11,7 +11,6 @@ using osu.Framework.Graphics;
using osu.Framework.Threading;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
-using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.OnlinePlay.Components;
using osuTK;
@@ -25,9 +24,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
set => button.Action = value;
}
- [Resolved]
- private IAPIProvider api { get; set; }
-
[Resolved]
private OsuColour colours { get; set; }
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs
new file mode 100644
index 0000000000..d708b39898
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs
@@ -0,0 +1,33 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Online.Rooms;
+using osuTK;
+
+namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
+{
+ ///
+ /// A historically-ordered list of s.
+ ///
+ public class MultiplayerHistoryList : DrawableRoomPlaylist
+ {
+ public MultiplayerHistoryList()
+ : base(false, false, true)
+ {
+ }
+
+ protected override FillFlowContainer> CreateListFillFlowContainer() => new HistoryFillFlowContainer
+ {
+ Spacing = new Vector2(0, 2)
+ };
+
+ private class HistoryFillFlowContainer : FillFlowContainer>
+ {
+ public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderByDescending(item => item.Model.PlayedAt);
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs
new file mode 100644
index 0000000000..c3245b550f
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs
@@ -0,0 +1,131 @@
+// 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 osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Online.Rooms;
+
+namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
+{
+ ///
+ /// The multiplayer playlist, containing lists to show the items from a in both gameplay-order and historical-order.
+ ///
+ public class MultiplayerPlaylist : MultiplayerRoomComposite
+ {
+ public readonly Bindable DisplayMode = new Bindable();
+
+ private MultiplayerQueueList queueList;
+ private MultiplayerHistoryList historyList;
+ private bool firstPopulation = true;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ const float tab_control_height = 25;
+
+ InternalChildren = new Drawable[]
+ {
+ new OsuTabControl
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = tab_control_height,
+ Current = { BindTarget = DisplayMode }
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Top = tab_control_height + 5 },
+ Masking = true,
+ Children = new Drawable[]
+ {
+ queueList = new MultiplayerQueueList
+ {
+ RelativeSizeAxes = Axes.Both,
+ SelectedItem = { BindTarget = SelectedItem }
+ },
+ historyList = new MultiplayerHistoryList
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ SelectedItem = { BindTarget = SelectedItem }
+ }
+ }
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ DisplayMode.BindValueChanged(onDisplayModeChanged, true);
+ }
+
+ private void onDisplayModeChanged(ValueChangedEvent mode)
+ {
+ historyList.FadeTo(mode.NewValue == MultiplayerPlaylistDisplayMode.History ? 1 : 0, 100);
+ queueList.FadeTo(mode.NewValue == MultiplayerPlaylistDisplayMode.Queue ? 1 : 0, 100);
+ }
+
+ protected override void OnRoomUpdated()
+ {
+ base.OnRoomUpdated();
+
+ if (Room == null)
+ {
+ historyList.Items.Clear();
+ queueList.Items.Clear();
+ firstPopulation = true;
+ return;
+ }
+
+ if (firstPopulation)
+ {
+ foreach (var item in Room.Playlist)
+ addItemToLists(item);
+
+ firstPopulation = false;
+ }
+ }
+
+ protected override void PlaylistItemAdded(MultiplayerPlaylistItem item)
+ {
+ base.PlaylistItemAdded(item);
+ addItemToLists(item);
+ }
+
+ protected override void PlaylistItemRemoved(long item)
+ {
+ base.PlaylistItemRemoved(item);
+ removeItemFromLists(item);
+ }
+
+ protected override void PlaylistItemChanged(MultiplayerPlaylistItem item)
+ {
+ base.PlaylistItemChanged(item);
+
+ removeItemFromLists(item.ID);
+ addItemToLists(item);
+ }
+
+ private void addItemToLists(MultiplayerPlaylistItem item)
+ {
+ var apiItem = Playlist.Single(i => i.ID == item.ID);
+
+ if (item.Expired)
+ historyList.Items.Add(apiItem);
+ else
+ queueList.Items.Add(apiItem);
+ }
+
+ private void removeItemFromLists(long item)
+ {
+ queueList.Items.RemoveAll(i => i.ID == item);
+ historyList.Items.RemoveAll(i => i.ID == item);
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistDisplayMode.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistDisplayMode.cs
new file mode 100644
index 0000000000..cc3dca6a34
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistDisplayMode.cs
@@ -0,0 +1,14 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
+{
+ ///
+ /// The type of list displayed in a .
+ ///
+ public enum MultiplayerPlaylistDisplayMode
+ {
+ Queue,
+ History,
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs
new file mode 100644
index 0000000000..1b1b66273f
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs
@@ -0,0 +1,44 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Online.Rooms;
+using osuTK;
+
+namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
+{
+ ///
+ /// A gameplay-ordered list of s.
+ ///
+ public class MultiplayerQueueList : DrawableRoomPlaylist
+ {
+ public MultiplayerQueueList()
+ : base(false, false, true)
+ {
+ }
+
+ protected override FillFlowContainer> CreateListFillFlowContainer() => new QueueFillFlowContainer
+ {
+ Spacing = new Vector2(0, 2)
+ };
+
+ private class QueueFillFlowContainer : FillFlowContainer>
+ {
+ [Resolved(typeof(Room), nameof(Room.Playlist))]
+ private BindableList roomPlaylist { get; set; }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ roomPlaylist.BindCollectionChanged((_, __) => InvalidateLayout());
+ }
+
+ public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderBy(item => item.Model.PlaylistOrder);
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
index 077e9cef93..3a25bd7b06 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
@@ -26,6 +26,7 @@ using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Screens.OnlinePlay.Match.Components;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
+using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist;
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
using osu.Game.Screens.Play;
@@ -56,8 +57,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[CanBeNull]
private IDisposable readyClickOperation;
- private DrawableRoomPlaylist playlist;
-
public MultiplayerMatchSubScreen(Room room)
: base(room)
{
@@ -74,9 +73,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true);
UserMods.BindValueChanged(onUserModsChanged);
- playlist.Items.BindTo(Room.Playlist);
- playlist.SelectedItem.BindTo(SelectedItem);
-
client.LoadRequested += onLoadRequested;
client.RoomUpdated += onRoomUpdated;
@@ -142,21 +138,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
RelativeSizeAxes = Axes.X,
Height = 40,
- Action = () =>
- {
- if (this.IsCurrentScreen())
- this.Push(new MultiplayerMatchSongSelect(Room));
- },
+ Action = SelectBeatmap,
Alpha = 0
},
},
null,
new Drawable[]
{
- playlist = new DrawableRoomPlaylist(false, false, true, true)
+ new MultiplayerPlaylist
{
- RelativeSizeAxes = Axes.Both,
- },
+ RelativeSizeAxes = Axes.Both
+ }
},
new[]
{
@@ -228,6 +220,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
}
};
+ internal void SelectBeatmap()
+ {
+ if (!this.IsCurrentScreen())
+ return;
+
+ this.Push(new MultiplayerMatchSongSelect(Room));
+ }
+
protected override Drawable CreateFooter() => new MultiplayerMatchFooter
{
OnReadyClick = onReadyClick,
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs
index a380ddef25..7d2fe44c4e 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs
@@ -4,6 +4,7 @@
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Game.Online.Multiplayer;
+using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
@@ -23,14 +24,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Client.UserLeft += invokeUserLeft;
Client.UserKicked += invokeUserKicked;
Client.UserJoined += invokeUserJoined;
+ Client.ItemAdded += invokeItemAdded;
+ Client.ItemRemoved += invokeItemRemoved;
+ Client.ItemChanged += invokeItemChanged;
OnRoomUpdated();
}
private void invokeOnRoomUpdated() => Scheduler.AddOnce(OnRoomUpdated);
- private void invokeUserJoined(MultiplayerRoomUser user) => Scheduler.AddOnce(UserJoined, user);
- private void invokeUserKicked(MultiplayerRoomUser user) => Scheduler.AddOnce(UserKicked, user);
- private void invokeUserLeft(MultiplayerRoomUser user) => Scheduler.AddOnce(UserLeft, user);
+ private void invokeUserJoined(MultiplayerRoomUser user) => Scheduler.Add(() => UserJoined(user));
+ private void invokeUserKicked(MultiplayerRoomUser user) => Scheduler.Add(() => UserKicked(user));
+ private void invokeUserLeft(MultiplayerRoomUser user) => Scheduler.Add(() => UserLeft(user));
+ private void invokeItemAdded(MultiplayerPlaylistItem item) => Schedule(() => PlaylistItemAdded(item));
+ private void invokeItemRemoved(long item) => Schedule(() => PlaylistItemRemoved(item));
+ private void invokeItemChanged(MultiplayerPlaylistItem item) => Schedule(() => PlaylistItemChanged(item));
///
/// Invoked when a user has joined the room.
@@ -56,6 +63,30 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
}
+ ///
+ /// Invoked when a playlist item is added to the room.
+ ///
+ /// The added playlist item.
+ protected virtual void PlaylistItemAdded(MultiplayerPlaylistItem item)
+ {
+ }
+
+ ///
+ /// Invoked when a playlist item is removed from the room.
+ ///
+ /// The ID of the removed playlist item.
+ protected virtual void PlaylistItemRemoved(long item)
+ {
+ }
+
+ ///
+ /// Invoked when a playlist item is changed in the room.
+ ///
+ /// The new playlist item, with an existing item's ID.
+ protected virtual void PlaylistItemChanged(MultiplayerPlaylistItem item)
+ {
+ }
+
///
/// Invoked when any change occurs to the multiplayer room.
///
@@ -71,6 +102,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Client.UserLeft -= invokeUserLeft;
Client.UserKicked -= invokeUserKicked;
Client.UserJoined -= invokeUserJoined;
+ Client.ItemAdded -= invokeItemAdded;
+ Client.ItemRemoved -= invokeItemRemoved;
+ Client.ItemChanged -= invokeItemChanged;
}
base.Dispose(isDisposing);
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs
index 3152f50d3d..8fbaebadfe 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
private IAPIProvider api { get; set; }
[Resolved]
- private RulesetStore rulesets { get; set; }
+ private IRulesetStore rulesets { get; set; }
private SpriteIcon crown;
@@ -185,9 +185,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
const double fade_time = 50;
// Todo: Should use the room's selected item to determine ruleset.
- var ruleset = rulesets.GetRuleset(0).CreateInstance();
+ var ruleset = rulesets.GetRuleset(0)?.CreateInstance();
- int? currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank;
+ int? currentModeRank = ruleset != null ? User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank : null;
userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty;
userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability);
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
index 57d0d2c198..9ac64add9a 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
@@ -36,9 +36,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
[Resolved]
private OsuColour colours { get; set; }
- [Resolved]
- private SpectatorClient spectatorClient { get; set; }
-
[Resolved]
private MultiplayerClient multiplayerClient { get; set; }
diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
index a18e4b45cf..19153521cd 100644
--- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
@@ -40,18 +40,9 @@ namespace osu.Game.Screens.OnlinePlay
[Cached]
private readonly OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker();
- [Resolved(CanBeNull = true)]
- private MusicController music { get; set; }
-
- [Resolved]
- private OsuGameBase game { get; set; }
-
[Resolved]
protected IAPIProvider API { get; private set; }
- [Resolved(CanBeNull = true)]
- private OsuLogo logo { get; set; }
-
protected OnlinePlayScreen()
{
Anchor = Anchor.Centre;
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
index 6d2a426e70..7e045802f7 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
@@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Input;
-using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Match;
@@ -29,9 +28,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
public override string ShortTitle => "playlist";
- [Resolved]
- private IAPIProvider api { get; set; }
-
private readonly IBindable isIdle = new BindableBool();
private MatchLeaderboard leaderboard;
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs
index 03c95ec060..0fd76f7e25 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs
@@ -2,9 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
-using osu.Framework.Allocation;
using osu.Framework.Screens;
-using osu.Game.Beatmaps;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.Select;
@@ -13,9 +11,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{
public class PlaylistsSongSelect : OnlinePlaySongSelect
{
- [Resolved]
- private BeatmapManager beatmaps { get; set; }
-
public PlaylistsSongSelect(Room room)
: base(room)
{
diff --git a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs
index 324e5d43b5..06b53e8426 100644
--- a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs
+++ b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs
@@ -9,9 +9,6 @@ namespace osu.Game.Screens.Play.HUD
{
public class DefaultAccuracyCounter : GameplayAccuracyCounter, ISkinnableDrawable
{
- [Resolved(canBeNull: true)]
- private HUDOverlay hud { get; set; }
-
public bool UsesFixedAnchor { get; set; }
[BackgroundDependencyLoader]
diff --git a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs
index 6d87211ddc..52f86d2bc3 100644
--- a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs
+++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs
@@ -15,9 +15,6 @@ namespace osu.Game.Screens.Play.HUD
{
public class DefaultComboCounter : RollingCounter, ISkinnableDrawable
{
- [Resolved(canBeNull: true)]
- private HUDOverlay hud { get; set; }
-
public bool UsesFixedAnchor { get; set; }
public DefaultComboCounter()
diff --git a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs
index 87b19e8433..6af89404e0 100644
--- a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs
+++ b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs
@@ -16,9 +16,6 @@ namespace osu.Game.Screens.Play.HUD
Origin = Anchor.TopCentre;
}
- [Resolved(canBeNull: true)]
- private HUDOverlay hud { get; set; }
-
public bool UsesFixedAnchor { get; set; }
[BackgroundDependencyLoader]
diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs
index 5c5b66d496..4859f1b977 100644
--- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs
+++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs
@@ -40,9 +40,6 @@ namespace osu.Game.Screens.Play.HUD
private bool isRolling;
- [Resolved]
- private ISkinSource skin { get; set; }
-
private readonly Container counterContainer;
///
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 4d574dea99..a0e9428cff 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -768,7 +768,15 @@ namespace osu.Game.Screens.Play
Scheduler.Add(resultsDisplayDelegate);
}
- protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value;
+ protected override bool OnScroll(ScrollEvent e)
+ {
+ // During pause, allow global volume adjust regardless of settings.
+ if (GameplayClockContainer.IsPaused.Value)
+ return false;
+
+ // Block global volume adjust if the user has asked for it (special case when holding "Alt").
+ return mouseWheelDisabled.Value && !e.AltPressed;
+ }
#region Fail Logic
diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs
index 45601999a0..7fea44b3ea 100644
--- a/osu.Game/Screens/Play/SoloSpectator.cs
+++ b/osu.Game/Screens/Play/SoloSpectator.cs
@@ -23,12 +23,10 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Spectator;
using osu.Game.Overlays;
using osu.Game.Overlays.Settings;
-using osu.Game.Rulesets;
using osu.Game.Screens.OnlinePlay.Match.Components;
using osu.Game.Screens.Spectate;
using osu.Game.Users;
using osuTK;
-using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
namespace osu.Game.Screens.Play
{
@@ -44,9 +42,6 @@ namespace osu.Game.Screens.Play
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; }
- [Resolved]
- private RulesetStore rulesets { get; set; }
-
[Resolved]
private BeatmapManager beatmaps { get; set; }
diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs
index d5b8a4c8ea..22be91b974 100644
--- a/osu.Game/Screens/Ranking/ScorePanelList.cs
+++ b/osu.Game/Screens/Ranking/ScorePanelList.cs
@@ -13,7 +13,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
-using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
using osu.Game.Scoring;
using osuTK;
@@ -70,9 +69,6 @@ namespace osu.Game.Screens.Ranking
[Resolved]
private ScoreManager scoreManager { get; set; }
- [Resolved]
- private BeatmapDifficultyCache difficultyCache { get; set; }
-
private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource();
private readonly Flow flow;
private readonly Scroll scroll;
diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs
index 7543c89f17..bbe0a37d8e 100644
--- a/osu.Game/Screens/Select/BeatmapDetails.cs
+++ b/osu.Game/Screens/Select/BeatmapDetails.cs
@@ -16,7 +16,6 @@ using osu.Game.Online;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.BeatmapSet;
-using osu.Game.Rulesets;
using osu.Game.Screens.Select.Details;
using osuTK;
using osuTK.Graphics;
@@ -38,9 +37,6 @@ namespace osu.Game.Screens.Select
[Resolved]
private IAPIProvider api { get; set; }
- [Resolved]
- private RulesetStore rulesets { get; set; }
-
private IBeatmapInfo beatmapInfo;
private APIFailTimes failTimes;
diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
index 1fd6d8c921..872e630ba0 100644
--- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
+++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
@@ -64,9 +64,6 @@ namespace osu.Game.Screens.Select.Leaderboards
[Resolved]
private ScoreManager scoreManager { get; set; }
- [Resolved]
- private BeatmapDifficultyCache difficultyCache { get; set; }
-
[Resolved]
private IBindable ruleset { get; set; }
diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs
index d27122aea8..340c6ed931 100644
--- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs
+++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs
@@ -3,13 +3,11 @@
using System.Diagnostics;
using JetBrains.Annotations;
-using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
-using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Input.Bindings;
@@ -28,9 +26,6 @@ namespace osu.Game.Skinning.Editor
public const float VISIBLE_TARGET_SCALE = 0.8f;
- [Resolved]
- private OsuColour colours { get; set; }
-
public SkinEditorOverlay(ScalingContainer target)
{
this.target = target;
diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs
index fd5a9500d9..bdcb85456a 100644
--- a/osu.Game/Skinning/LegacyAccuracyCounter.cs
+++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs
@@ -1,10 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Graphics.Sprites;
-using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osuTK;
@@ -23,9 +21,6 @@ namespace osu.Game.Skinning
Margin = new MarginPadding(10);
}
- [Resolved(canBeNull: true)]
- private HUDOverlay hud { get; set; }
-
protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(LegacyFont.Score)
{
Anchor = Anchor.TopRight,
diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs
index 3fcca74fb8..5db4f00b46 100644
--- a/osu.Game/Skinning/PoolableSkinnableSample.cs
+++ b/osu.Game/Skinning/PoolableSkinnableSample.cs
@@ -4,7 +4,6 @@
using System;
using System.Linq;
using JetBrains.Annotations;
-using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
@@ -30,9 +29,6 @@ namespace osu.Game.Skinning
private ISampleInfo sampleInfo;
private SampleChannel activeChannel;
- [Resolved]
- private ISampleStore sampleStore { get; set; }
-
///
/// Creates a new with no applied .
/// An can be applied later via .
diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs
index f935adf7a5..c9e55c09aa 100644
--- a/osu.Game/Skinning/SkinnableSound.cs
+++ b/osu.Game/Skinning/SkinnableSound.cs
@@ -7,7 +7,6 @@ using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio;
-using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
@@ -43,9 +42,6 @@ namespace osu.Game.Skinning
private readonly AudioContainer samplesContainer;
- [Resolved]
- private ISampleStore sampleStore { get; set; }
-
[Resolved(CanBeNull = true)]
private IPooledSampleProvider samplePool { get; set; }
diff --git a/osu.Game/Stores/RealmRulesetStore.cs b/osu.Game/Stores/RealmRulesetStore.cs
index 0119aec9a4..93b6d29e7d 100644
--- a/osu.Game/Stores/RealmRulesetStore.cs
+++ b/osu.Game/Stores/RealmRulesetStore.cs
@@ -18,7 +18,7 @@ using osu.Game.Rulesets;
namespace osu.Game.Stores
{
- public class RealmRulesetStore : IDisposable
+ public class RealmRulesetStore : IRulesetStore, IDisposable
{
private readonly RealmContextFactory realmFactory;
@@ -29,9 +29,9 @@ namespace osu.Game.Stores
///
/// All available rulesets.
///
- public IEnumerable AvailableRulesets => availableRulesets;
+ public IEnumerable AvailableRulesets => availableRulesets;
- private readonly List availableRulesets = new List();
+ private readonly List availableRulesets = new List();
public RealmRulesetStore(RealmContextFactory realmFactory, Storage? storage = null)
{
@@ -64,14 +64,14 @@ namespace osu.Game.Stores
///
/// The ruleset's internal ID.
/// A ruleset, if available, else null.
- public IRulesetInfo? GetRuleset(int id) => AvailableRulesets.FirstOrDefault(r => r.OnlineID == id);
+ public RealmRuleset? GetRuleset(int id) => AvailableRulesets.FirstOrDefault(r => r.OnlineID == id);
///
/// Retrieve a ruleset using a known short name.
///
/// The ruleset's short name.
/// A ruleset, if available, else null.
- public IRulesetInfo? GetRuleset(string shortName) => AvailableRulesets.FirstOrDefault(r => r.ShortName == shortName);
+ public RealmRuleset? GetRuleset(string shortName) => AvailableRulesets.FirstOrDefault(r => r.ShortName == shortName);
private Assembly? resolveRulesetDependencyAssembly(object? sender, ResolveEventArgs args)
{
@@ -258,5 +258,13 @@ namespace osu.Game.Stores
{
AppDomain.CurrentDomain.AssemblyResolve -= resolveRulesetDependencyAssembly;
}
+
+ #region Implementation of IRulesetStore
+
+ IRulesetInfo? IRulesetStore.GetRuleset(int id) => GetRuleset(id);
+ IRulesetInfo? IRulesetStore.GetRuleset(string shortName) => GetRuleset(shortName);
+ IEnumerable IRulesetStore.AvailableRulesets => AvailableRulesets;
+
+ #endregion
}
}
diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
index 05b7c11e34..2510ddb432 100644
--- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
@@ -50,6 +50,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
private MultiplayerPlaylistItem? currentItem => Room?.Playlist[currentIndex];
private int currentIndex;
+ private long lastPlaylistItemId;
+
public TestMultiplayerClient(TestMultiplayerRoomManager roomManager)
{
this.roomManager = roomManager;
@@ -145,7 +147,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
((IMultiplayerClient)this).ResultsReady();
- finishCurrentItem().Wait();
+ FinishCurrentItem().Wait();
}
break;
@@ -169,6 +171,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
serverSidePlaylist.Clear();
serverSidePlaylist.AddRange(apiRoom.Playlist.Select(item => new MultiplayerPlaylistItem(item)));
+ lastPlaylistItemId = serverSidePlaylist.Max(item => item.ID);
var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id)
{
@@ -189,6 +192,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Host = localUser
};
+ await updatePlaylistOrder(room).ConfigureAwait(false);
await updateCurrentItem(room, false).ConfigureAwait(false);
RoomSetupAction?.Invoke(room);
@@ -308,12 +312,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
if (Room.Settings.QueueMode == QueueMode.HostOnly && Room.Host?.UserID != LocalUser?.UserID)
throw new InvalidOperationException("Local user is not the room host.");
+ item.OwnerID = userId;
+
switch (Room.Settings.QueueMode)
{
case QueueMode.HostOnly:
// In host-only mode, the current item is re-used.
item.ID = currentItem.ID;
- item.OwnerID = currentItem.OwnerID;
+ item.PlaylistOrder = currentItem.PlaylistOrder;
serverSidePlaylist[currentIndex] = item;
await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false);
@@ -323,12 +329,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
break;
default:
- item.ID = serverSidePlaylist.Last().ID + 1;
- item.OwnerID = userId;
-
- serverSidePlaylist.Add(item);
- await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false);
+ await addItem(item).ConfigureAwait(false);
+ // The current item can change as a result of an item being added. For example, if all items earlier in the queue were expired.
await updateCurrentItem(Room).ConfigureAwait(false);
break;
}
@@ -385,11 +388,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
if (newMode == QueueMode.HostOnly && serverSidePlaylist.All(item => item.Expired))
await duplicateCurrentItem().ConfigureAwait(false);
- // When changing modes, items could have been added (above) or the queueing order could have changed.
+ await updatePlaylistOrder(Room).ConfigureAwait(false);
await updateCurrentItem(Room).ConfigureAwait(false);
}
- private async Task finishCurrentItem()
+ public async Task FinishCurrentItem()
{
Debug.Assert(Room != null);
Debug.Assert(APIRoom != null);
@@ -397,10 +400,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
// Expire the current playlist item.
currentItem.Expired = true;
+ currentItem.PlayedAt = DateTimeOffset.Now;
+
await ((IMultiplayerClient)this).PlaylistItemChanged(currentItem).ConfigureAwait(false);
+ await updatePlaylistOrder(Room).ConfigureAwait(false);
// In host-only mode, a duplicate playlist item will be used for the next round.
- if (Room.Settings.QueueMode == QueueMode.HostOnly)
+ if (Room.Settings.QueueMode == QueueMode.HostOnly && serverSidePlaylist.All(item => item.Expired))
await duplicateCurrentItem().ConfigureAwait(false);
await updateCurrentItem(Room).ConfigureAwait(false);
@@ -408,47 +414,96 @@ namespace osu.Game.Tests.Visual.Multiplayer
private async Task duplicateCurrentItem()
{
- Debug.Assert(Room != null);
- Debug.Assert(APIRoom != null);
Debug.Assert(currentItem != null);
- var newItem = new MultiplayerPlaylistItem
+ await addItem(new MultiplayerPlaylistItem
{
- ID = serverSidePlaylist.Last().ID + 1,
BeatmapID = currentItem.BeatmapID,
BeatmapChecksum = currentItem.BeatmapChecksum,
RulesetID = currentItem.RulesetID,
RequiredMods = currentItem.RequiredMods,
AllowedMods = currentItem.AllowedMods
- };
+ }).ConfigureAwait(false);
+ }
- serverSidePlaylist.Add(newItem);
- await ((IMultiplayerClient)this).PlaylistItemAdded(newItem).ConfigureAwait(false);
+ private async Task addItem(MultiplayerPlaylistItem item)
+ {
+ Debug.Assert(Room != null);
+
+ item.ID = ++lastPlaylistItemId;
+
+ serverSidePlaylist.Add(item);
+ await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false);
+
+ await updatePlaylistOrder(Room).ConfigureAwait(false);
}
private async Task updateCurrentItem(MultiplayerRoom room, bool notify = true)
{
- MultiplayerPlaylistItem newItem;
+ // Pick the next non-expired playlist item by playlist order, or default to the most-recently-expired item.
+ MultiplayerPlaylistItem nextItem = serverSidePlaylist.Where(i => !i.Expired).OrderBy(i => i.PlaylistOrder).FirstOrDefault()
+ ?? serverSidePlaylist.OrderByDescending(i => i.PlayedAt).First();
+
+ currentIndex = serverSidePlaylist.IndexOf(nextItem);
+
+ long lastItem = room.Settings.PlaylistItemId;
+ room.Settings.PlaylistItemId = nextItem.ID;
+
+ if (notify && nextItem.ID != lastItem)
+ await ((IMultiplayerClient)this).SettingsChanged(room.Settings).ConfigureAwait(false);
+ }
+
+ private async Task updatePlaylistOrder(MultiplayerRoom room)
+ {
+ List orderedActiveItems;
switch (room.Settings.QueueMode)
{
default:
- // Pick the single non-expired playlist item.
- newItem = serverSidePlaylist.FirstOrDefault(i => !i.Expired) ?? serverSidePlaylist.Last();
+ orderedActiveItems = serverSidePlaylist.Where(item => !item.Expired).OrderBy(item => item.ID).ToList();
break;
case QueueMode.AllPlayersRoundRobin:
- // Group playlist items by (user_id -> count_expired), and select the first available playlist item from a user that has available beatmaps where count_expired is the lowest.
- throw new NotImplementedException();
+ orderedActiveItems = new List();
+
+ // Todo: This could probably be more efficient, likely at the cost of increased complexity.
+ // Number of "expired" or "used" items per player.
+ Dictionary perUserCounts = serverSidePlaylist
+ .GroupBy(item => item.OwnerID)
+ .ToDictionary(group => group.Key, group => group.Count(item => item.Expired));
+
+ // We'll run a simulation over all items which are not expired ("unprocessed"). Expired items will not have their ordering updated.
+ List unprocessedItems = serverSidePlaylist.Where(item => !item.Expired).ToList();
+
+ // In every iteration of the simulation, pick the first available item from the user with the lowest number of items in the queue to add to the result set.
+ // If multiple users have the same number of items in the queue, then the item with the lowest ID is chosen.
+ while (unprocessedItems.Count > 0)
+ {
+ MultiplayerPlaylistItem candidateItem = unprocessedItems
+ .OrderBy(item => perUserCounts[item.OwnerID])
+ .ThenBy(item => item.ID)
+ .First();
+
+ unprocessedItems.Remove(candidateItem);
+ orderedActiveItems.Add(candidateItem);
+
+ perUserCounts[candidateItem.OwnerID]++;
+ }
+
+ break;
}
- currentIndex = serverSidePlaylist.IndexOf(newItem);
+ for (int i = 0; i < orderedActiveItems.Count; i++)
+ {
+ var item = orderedActiveItems[i];
- long lastItem = room.Settings.PlaylistItemId;
- room.Settings.PlaylistItemId = newItem.ID;
+ if (item.PlaylistOrder == i)
+ continue;
- if (notify && newItem.ID != lastItem)
- await ((IMultiplayerClient)this).SettingsChanged(room.Settings).ConfigureAwait(false);
+ item.PlaylistOrder = (ushort)i;
+
+ await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false);
+ }
}
}
}
diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs
index abcf31c007..a4586dea12 100644
--- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs
+++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs
@@ -25,9 +25,9 @@ namespace osu.Game.Tests.Visual.OnlinePlay
private readonly List serverSideRooms = new List();
- private int currentRoomId;
- private int currentPlaylistItemId;
- private int currentScoreId;
+ private int currentRoomId = 1;
+ private int currentPlaylistItemId = 1;
+ private int currentScoreId = 1;
///
/// Handles an API request, while also updating the local state to match
diff --git a/osu.Game/Updater/NoActionUpdateManager.cs b/osu.Game/Updater/NoActionUpdateManager.cs
index 641263ed0f..8f9c4c6f16 100644
--- a/osu.Game/Updater/NoActionUpdateManager.cs
+++ b/osu.Game/Updater/NoActionUpdateManager.cs
@@ -5,7 +5,6 @@ using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites;
-using osu.Framework.Platform;
using osu.Game.Online.API;
using osu.Game.Overlays.Notifications;
@@ -19,9 +18,6 @@ namespace osu.Game.Updater
{
private string version;
- [Resolved]
- private GameHost host { get; set; }
-
[BackgroundDependencyLoader]
private void load(OsuGameBase game)
{
diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs
index 0874685f49..516aa80652 100644
--- a/osu.Game/Users/UserActivity.cs
+++ b/osu.Game/Users/UserActivity.cs
@@ -29,9 +29,9 @@ namespace osu.Game.Users
{
public IBeatmapInfo BeatmapInfo { get; }
- public RulesetInfo Ruleset { get; }
+ public IRulesetInfo Ruleset { get; }
- protected InGame(IBeatmapInfo beatmapInfo, RulesetInfo ruleset)
+ protected InGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset)
{
BeatmapInfo = beatmapInfo;
Ruleset = ruleset;
@@ -42,7 +42,7 @@ namespace osu.Game.Users
public class InMultiplayerGame : InGame
{
- public InMultiplayerGame(IBeatmapInfo beatmapInfo, RulesetInfo ruleset)
+ public InMultiplayerGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset)
: base(beatmapInfo, ruleset)
{
}
@@ -52,7 +52,7 @@ namespace osu.Game.Users
public class InPlaylistGame : InGame
{
- public InPlaylistGame(IBeatmapInfo beatmapInfo, RulesetInfo ruleset)
+ public InPlaylistGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset)
: base(beatmapInfo, ruleset)
{
}
@@ -60,7 +60,7 @@ namespace osu.Game.Users
public class InSoloGame : InGame
{
- public InSoloGame(IBeatmapInfo beatmapInfo, RulesetInfo ruleset)
+ public InSoloGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset)
: base(beatmapInfo, ruleset)
{
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 7cc8893d8d..53a3337c9d 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -36,8 +36,8 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 9c21f76617..f3dc163a67 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -60,8 +60,8 @@
-
-
+
+
@@ -83,7 +83,7 @@
-
+