diff --git a/FodyWeavers.xml b/FodyWeavers.xml
index cc07b89533..ea490e3297 100644
--- a/FodyWeavers.xml
+++ b/FodyWeavers.xml
@@ -1,3 +1,3 @@
-
+
\ No newline at end of file
diff --git a/osu.Android.props b/osu.Android.props
index 3c4380e355..481ddc118f 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,7 +52,7 @@
-
+
diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
index 1c89d9cd00..f89750a96e 100644
--- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
+++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
@@ -3,6 +3,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mania.Configuration;
@@ -47,7 +48,7 @@ namespace osu.Game.Rulesets.Mania
private class TimeSlider : OsuSliderBar
{
- public override string TooltipText => Current.Value.ToString("N0") + "ms";
+ public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms";
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
index e111bb1054..3252e6d912 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
@@ -37,11 +37,13 @@ namespace osu.Game.Rulesets.Osu.Tests
private readonly BindableBool snakingIn = new BindableBool();
private readonly BindableBool snakingOut = new BindableBool();
+ private IBeatmap beatmap;
+
private const double duration_of_span = 3605;
private const double fade_in_modifier = -1200;
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
- => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
+ => new ClockBackedTestWorkingBeatmap(this.beatmap = beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
[BackgroundDependencyLoader]
private void load(RulesetConfigCache configCache)
@@ -51,8 +53,16 @@ namespace osu.Game.Rulesets.Osu.Tests
config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut);
}
+ private Slider slider;
private DrawableSlider drawableSlider;
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ slider = null;
+ drawableSlider = null;
+ });
+
[SetUpSteps]
public override void SetUpSteps()
{
@@ -67,21 +77,19 @@ namespace osu.Game.Rulesets.Osu.Tests
base.SetUpSteps();
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
- double startTime = hitObjects[sliderIndex].StartTime;
- addSeekStep(startTime);
- retrieveDrawableSlider((Slider)hitObjects[sliderIndex]);
+ retrieveSlider(sliderIndex);
setSnaking(true);
- ensureSnakingIn(startTime + fade_in_modifier);
+ addEnsureSnakingInSteps(() => slider.StartTime + fade_in_modifier);
for (int i = 0; i < sliderIndex; i++)
{
// non-final repeats should not snake out
- ensureNoSnakingOut(startTime, i);
+ addEnsureNoSnakingOutStep(() => slider.StartTime, i);
}
// final repeat should snake out
- ensureSnakingOut(startTime, sliderIndex);
+ addEnsureSnakingOutSteps(() => slider.StartTime, sliderIndex);
}
[TestCase(0)]
@@ -93,17 +101,15 @@ namespace osu.Game.Rulesets.Osu.Tests
base.SetUpSteps();
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
- double startTime = hitObjects[sliderIndex].StartTime;
- addSeekStep(startTime);
- retrieveDrawableSlider((Slider)hitObjects[sliderIndex]);
+ retrieveSlider(sliderIndex);
setSnaking(false);
- ensureNoSnakingIn(startTime + fade_in_modifier);
+ addEnsureNoSnakingInSteps(() => slider.StartTime + fade_in_modifier);
for (int i = 0; i <= sliderIndex; i++)
{
// no snaking out ever, including final repeat
- ensureNoSnakingOut(startTime, i);
+ addEnsureNoSnakingOutStep(() => slider.StartTime, i);
}
}
@@ -116,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests
// repeat might have a chance to update its position depending on where in the frame its hit,
// so some leniency is allowed here instead of checking strict equality
- checkPositionChange(16600, sliderRepeat, positionAlmostSame);
+ addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionAlmostSame);
}
[Test]
@@ -126,38 +132,41 @@ namespace osu.Game.Rulesets.Osu.Tests
setSnaking(true);
base.SetUpSteps();
- checkPositionChange(16600, sliderRepeat, positionDecreased);
+ addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionDecreased);
}
- private void retrieveDrawableSlider(Slider slider) => AddUntilStep($"retrieve slider @ {slider.StartTime}", () =>
- (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null);
-
- private void ensureSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionIncreased);
- private void ensureNoSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionRemainsSame);
-
- private void ensureSnakingOut(double startTime, int repeatIndex)
+ private void retrieveSlider(int index)
{
- var repeatTime = timeAtRepeat(startTime, repeatIndex);
+ AddStep("retrieve slider at index", () => slider = (Slider)beatmap.HitObjects[index]);
+ addSeekStep(() => slider);
+ AddUntilStep("retrieve drawable slider", () =>
+ (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null);
+ }
+ private void addEnsureSnakingInSteps(Func startTime) => addCheckPositionChangeSteps(startTime, getSliderEnd, positionIncreased);
+ private void addEnsureNoSnakingInSteps(Func startTime) => addCheckPositionChangeSteps(startTime, getSliderEnd, positionRemainsSame);
+
+ private void addEnsureSnakingOutSteps(Func startTime, int repeatIndex)
+ {
if (repeatIndex % 2 == 0)
- checkPositionChange(repeatTime, sliderStart, positionIncreased);
+ addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), getSliderStart, positionIncreased);
else
- checkPositionChange(repeatTime, sliderEnd, positionDecreased);
+ addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), getSliderEnd, positionDecreased);
}
- private void ensureNoSnakingOut(double startTime, int repeatIndex) =>
- checkPositionChange(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame);
+ private void addEnsureNoSnakingOutStep(Func startTime, int repeatIndex)
+ => addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame);
- private double timeAtRepeat(double startTime, int repeatIndex) => startTime + 100 + duration_of_span * repeatIndex;
- private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func)sliderStart : sliderEnd;
+ private Func timeAtRepeat(Func startTime, int repeatIndex) => () => startTime() + 100 + duration_of_span * repeatIndex;
+ private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func)getSliderStart : getSliderEnd;
- private List sliderCurve => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
- private Vector2 sliderStart() => sliderCurve.First();
- private Vector2 sliderEnd() => sliderCurve.Last();
+ private List getSliderCurve() => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
+ private Vector2 getSliderStart() => getSliderCurve().First();
+ private Vector2 getSliderEnd() => getSliderCurve().Last();
- private Vector2 sliderRepeat()
+ private Vector2 getSliderRepeat()
{
- var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == hitObjects[1]);
+ var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == beatmap.HitObjects[1]);
var repeat = drawable.ChildrenOfType>().First().Children.First();
return repeat.Position;
}
@@ -167,7 +176,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private bool positionDecreased(Vector2 previous, Vector2 current) => current.X < previous.X && current.Y < previous.Y;
private bool positionAlmostSame(Vector2 previous, Vector2 current) => Precision.AlmostEquals(previous, current, 1);
- private void checkPositionChange(double startTime, Func positionToCheck, Func positionAssertion)
+ private void addCheckPositionChangeSteps(Func startTime, Func positionToCheck, Func positionAssertion)
{
Vector2 previousPosition = Vector2.Zero;
@@ -176,7 +185,7 @@ namespace osu.Game.Rulesets.Osu.Tests
addSeekStep(startTime);
AddStep($"save {positionDescription} position", () => previousPosition = positionToCheck.Invoke());
- addSeekStep(startTime + 100);
+ addSeekStep(() => startTime() + 100);
AddAssert($"{positionDescription} {assertionDescription}", () =>
{
var currentPosition = positionToCheck.Invoke();
@@ -193,19 +202,21 @@ namespace osu.Game.Rulesets.Osu.Tests
});
}
- private void addSeekStep(double time)
+ private void addSeekStep(Func slider)
{
- AddStep($"seek to {time}", () => MusicController.SeekTo(time));
-
- AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
+ AddStep("seek to slider", () => Player.GameplayClockContainer.Seek(slider().StartTime));
+ AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(slider().StartTime, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
}
- protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
+ private void addSeekStep(Func time)
{
- HitObjects = hitObjects
- };
+ AddStep("seek to time", () => Player.GameplayClockContainer.Seek(time()));
+ AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time(), Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
+ }
- private readonly List hitObjects = new List
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { HitObjects = createHitObjects() };
+
+ private static List createHitObjects() => new List
{
new Slider
{
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
index 8ff21057b5..9da583a073 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
@@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private void addSeekStep(double time)
{
- AddStep($"seek to {time}", () => MusicController.SeekTo(time));
+ AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time));
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
index 48e4db11ca..5b476526c9 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
@@ -13,6 +13,7 @@ using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
@@ -283,6 +284,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
}
}
- public string TooltipText => ControlPoint.Type.Value.ToString() ?? string.Empty;
+ public LocalisableString TooltipText => ControlPoint.Type.Value.ToString() ?? string.Empty;
}
}
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index 0d117f8755..5dc25d6643 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -19,7 +19,9 @@ using osu.Game.Database;
using osu.Game.IO;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Scoring;
using osu.Game.Tests.Resources;
+using osu.Game.Tests.Scores.IO;
using osu.Game.Users;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
@@ -185,13 +187,62 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
- private string hashFile(string filename)
+ [Test]
+ public async Task TestImportThenImportWithChangedHashedFile()
{
- using (var s = File.OpenRead(filename))
- return s.ComputeMD5Hash();
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
+ {
+ try
+ {
+ var osu = LoadOsuIntoHost(host);
+
+ var temp = TestResources.GetTestBeatmapForImport();
+
+ string extractedFolder = $"{temp}_extracted";
+ Directory.CreateDirectory(extractedFolder);
+
+ try
+ {
+ var imported = await LoadOszIntoOsu(osu);
+
+ await createScoreForBeatmap(osu, imported.Beatmaps.First());
+
+ using (var zip = ZipArchive.Open(temp))
+ zip.WriteToDirectory(extractedFolder);
+
+ // arbitrary write to hashed file
+ // this triggers the special BeatmapManager.PreImport deletion/replacement flow.
+ using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.osu").First()).AppendText())
+ await sw.WriteLineAsync("// changed");
+
+ using (var zip = ZipArchive.Create())
+ {
+ zip.AddAllFromDirectory(extractedFolder);
+ zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
+ }
+
+ var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp));
+
+ ensureLoaded(osu);
+
+ // check the newly "imported" beatmap is not the original.
+ Assert.IsTrue(imported.ID != importedSecondTime.ID);
+ Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID);
+ }
+ finally
+ {
+ Directory.Delete(extractedFolder, true);
+ }
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
}
[Test]
+ [Ignore("intentionally broken by import optimisations")]
public async Task TestImportThenImportWithChangedFile()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
@@ -294,6 +345,7 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
+ [Ignore("intentionally broken by import optimisations")]
public async Task TestImportCorruptThenImport()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
@@ -439,12 +491,11 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
- [TestCase(true)]
- [TestCase(false)]
- public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
+ [Test]
+ public async Task TestImportThenDeleteThenImportWithOnlineIDsMissing()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-{set}"))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}"))
{
try
{
@@ -452,10 +503,8 @@ namespace osu.Game.Tests.Beatmaps.IO
var imported = await LoadOszIntoOsu(osu);
- if (set)
- imported.OnlineBeatmapSetID = 1234;
- else
- imported.Beatmaps.First().OnlineBeatmapID = 1234;
+ foreach (var b in imported.Beatmaps)
+ b.OnlineBeatmapID = null;
osu.Dependencies.Get().Update(imported);
@@ -895,7 +944,17 @@ namespace osu.Game.Tests.Beatmaps.IO
Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending);
}
- private void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false)
+ private static Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmap)
+ {
+ return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
+ {
+ OnlineScoreID = 2,
+ Beatmap = beatmap,
+ BeatmapInfoID = beatmap.ID
+ }, new ImportScoreTest.TestArchiveReader());
+ }
+
+ private static void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false)
{
var manager = osu.Dependencies.Get();
@@ -904,12 +963,18 @@ namespace osu.Game.Tests.Beatmaps.IO
: manager.GetAllUsableBeatmapSets().Count);
}
- private void checkBeatmapCount(OsuGameBase osu, int expected)
+ private static string hashFile(string filename)
+ {
+ using (var s = File.OpenRead(filename))
+ return s.ComputeMD5Hash();
+ }
+
+ private static void checkBeatmapCount(OsuGameBase osu, int expected)
{
Assert.AreEqual(expected, osu.Dependencies.Get().QueryBeatmaps(_ => true).ToList().Count);
}
- private void checkSingleReferencedFileCount(OsuGameBase osu, int expected)
+ private static void checkSingleReferencedFileCount(OsuGameBase osu, int expected)
{
Assert.AreEqual(expected, osu.Dependencies.Get().QueryFiles(f => f.ReferenceCount == 1).Count());
}
diff --git a/osu.Game.Tests/Chat/TestSceneChannelManager.cs b/osu.Game.Tests/Chat/TestSceneChannelManager.cs
new file mode 100644
index 0000000000..0ec21a4c7b
--- /dev/null
+++ b/osu.Game.Tests/Chat/TestSceneChannelManager.cs
@@ -0,0 +1,105 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Online.Chat;
+using osu.Game.Tests.Visual;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Chat
+{
+ [HeadlessTest]
+ public class TestSceneChannelManager : OsuTestScene
+ {
+ private ChannelManager channelManager;
+ private int currentMessageId;
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ var container = new ChannelManagerContainer();
+ Child = container;
+ channelManager = container.ChannelManager;
+ });
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("register request handling", () =>
+ {
+ currentMessageId = 0;
+
+ ((DummyAPIAccess)API).HandleRequest = req =>
+ {
+ switch (req)
+ {
+ case JoinChannelRequest joinChannel:
+ joinChannel.TriggerSuccess();
+ return true;
+
+ case PostMessageRequest postMessage:
+ postMessage.TriggerSuccess(new Message(++currentMessageId)
+ {
+ IsAction = postMessage.Message.IsAction,
+ ChannelId = postMessage.Message.ChannelId,
+ Content = postMessage.Message.Content,
+ Links = postMessage.Message.Links,
+ Timestamp = postMessage.Message.Timestamp,
+ Sender = postMessage.Message.Sender
+ });
+
+ return true;
+ }
+
+ return false;
+ };
+ });
+ }
+
+ [Test]
+ public void TestCommandsPostedToCorrectChannelWhenNotCurrent()
+ {
+ Channel channel1 = null;
+ Channel channel2 = null;
+
+ AddStep("join 2 rooms", () =>
+ {
+ channelManager.JoinChannel(channel1 = createChannel(1, ChannelType.Public));
+ channelManager.JoinChannel(channel2 = createChannel(2, ChannelType.Public));
+ });
+
+ AddStep("select channel 1", () => channelManager.CurrentChannel.Value = channel1);
+
+ AddStep("post /me command to channel 2", () => channelManager.PostCommand("me dances", channel2));
+ AddAssert("/me command received by channel 2", () => channel2.Messages.Last().Content == "dances");
+
+ AddStep("post /np command to channel 2", () => channelManager.PostCommand("np", channel2));
+ AddAssert("/np command received by channel 2", () => channel2.Messages.Last().Content.Contains("is listening to"));
+ }
+
+ private Channel createChannel(int id, ChannelType type) => new Channel(new User())
+ {
+ Id = id,
+ Name = $"Channel {id}",
+ Topic = $"Topic of channel {id} with type {type}",
+ Type = type,
+ };
+
+ private class ChannelManagerContainer : CompositeDrawable
+ {
+ [Cached]
+ public ChannelManager ChannelManager { get; } = new ChannelManager();
+
+ public ChannelManagerContainer()
+ {
+ InternalChild = ChannelManager;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs
new file mode 100644
index 0000000000..cf5b3a42a4
--- /dev/null
+++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs
@@ -0,0 +1,241 @@
+// 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 NUnit.Framework;
+using osu.Game.Audio;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Edit.Checks;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Tests.Editing.Checks
+{
+ [TestFixture]
+ public class CheckFewHitsoundsTest
+ {
+ private CheckFewHitsounds check;
+
+ private List notHitsounded;
+ private List hitsounded;
+
+ [SetUp]
+ public void Setup()
+ {
+ check = new CheckFewHitsounds();
+ notHitsounded = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) };
+ hitsounded = new List
+ {
+ new HitSampleInfo(HitSampleInfo.HIT_NORMAL),
+ new HitSampleInfo(HitSampleInfo.HIT_FINISH)
+ };
+ }
+
+ [Test]
+ public void TestHitsounded()
+ {
+ var hitObjects = new List();
+
+ for (int i = 0; i < 16; ++i)
+ {
+ var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) };
+
+ if ((i + 1) % 2 == 0)
+ samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP));
+ if ((i + 1) % 3 == 0)
+ samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE));
+ if ((i + 1) % 4 == 0)
+ samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH));
+
+ hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples });
+ }
+
+ assertOk(hitObjects);
+ }
+
+ [Test]
+ public void TestHitsoundedWithBreak()
+ {
+ var hitObjects = new List();
+
+ for (int i = 0; i < 32; ++i)
+ {
+ var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) };
+
+ if ((i + 1) % 2 == 0)
+ samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP));
+ if ((i + 1) % 3 == 0)
+ samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE));
+ if ((i + 1) % 4 == 0)
+ samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH));
+ // Leaves a gap in which no hitsounds exist or can be added, and so shouldn't be an issue.
+ if (i > 8 && i < 24)
+ continue;
+
+ hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples });
+ }
+
+ assertOk(hitObjects);
+ }
+
+ [Test]
+ public void TestLightlyHitsounded()
+ {
+ var hitObjects = new List();
+
+ for (int i = 0; i < 30; ++i)
+ {
+ var samples = i % 8 == 0 ? hitsounded : notHitsounded;
+
+ hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples });
+ }
+
+ assertLongPeriodNegligible(hitObjects, count: 3);
+ }
+
+ [Test]
+ public void TestRarelyHitsounded()
+ {
+ var hitObjects = new List();
+
+ for (int i = 0; i < 30; ++i)
+ {
+ var samples = (i == 0 || i == 15) ? hitsounded : notHitsounded;
+
+ hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples });
+ }
+
+ // Should prompt one warning between 1st and 16th, and another between 16th and 31st.
+ assertLongPeriodWarning(hitObjects, count: 2);
+ }
+
+ [Test]
+ public void TestExtremelyRarelyHitsounded()
+ {
+ var hitObjects = new List();
+
+ for (int i = 0; i < 80; ++i)
+ {
+ var samples = i == 40 ? hitsounded : notHitsounded;
+
+ hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples });
+ }
+
+ // Should prompt one problem between 1st and 41st, and another between 41st and 81st.
+ assertLongPeriodProblem(hitObjects, count: 2);
+ }
+
+ [Test]
+ public void TestNotHitsounded()
+ {
+ var hitObjects = new List();
+
+ for (int i = 0; i < 20; ++i)
+ hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = notHitsounded });
+
+ assertNoHitsounds(hitObjects);
+ }
+
+ [Test]
+ public void TestNestedObjectsHitsounded()
+ {
+ var ticks = new List();
+ for (int i = 1; i < 16; ++i)
+ ticks.Add(new SliderTick { StartTime = 1000 * i, Samples = hitsounded });
+
+ var nested = new MockNestableHitObject(ticks.ToList(), 0, 16000)
+ {
+ Samples = hitsounded
+ };
+ nested.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ assertOk(new List { nested });
+ }
+
+ [Test]
+ public void TestNestedObjectsRarelyHitsounded()
+ {
+ var ticks = new List();
+ for (int i = 1; i < 16; ++i)
+ ticks.Add(new SliderTick { StartTime = 1000 * i, Samples = i == 0 ? hitsounded : notHitsounded });
+
+ var nested = new MockNestableHitObject(ticks.ToList(), 0, 16000)
+ {
+ Samples = hitsounded
+ };
+ nested.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ assertLongPeriodWarning(new List { nested });
+ }
+
+ [Test]
+ public void TestConcurrentObjects()
+ {
+ var hitObjects = new List();
+
+ var ticks = new List();
+ for (int i = 1; i < 10; ++i)
+ ticks.Add(new SliderTick { StartTime = 5000 * i, Samples = hitsounded });
+
+ var nested = new MockNestableHitObject(ticks.ToList(), 0, 50000)
+ {
+ Samples = notHitsounded
+ };
+ nested.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+ hitObjects.Add(nested);
+
+ for (int i = 1; i <= 6; ++i)
+ hitObjects.Add(new HitCircle { StartTime = 10000 * i, Samples = notHitsounded });
+
+ assertOk(hitObjects);
+ }
+
+ private void assertOk(List hitObjects)
+ {
+ Assert.That(check.Run(getContext(hitObjects)), Is.Empty);
+ }
+
+ private void assertLongPeriodProblem(List hitObjects, int count = 1)
+ {
+ var issues = check.Run(getContext(hitObjects)).ToList();
+
+ Assert.That(issues, Has.Count.EqualTo(count));
+ Assert.That(issues.All(issue => issue.Template is CheckFewHitsounds.IssueTemplateLongPeriodProblem));
+ }
+
+ private void assertLongPeriodWarning(List hitObjects, int count = 1)
+ {
+ var issues = check.Run(getContext(hitObjects)).ToList();
+
+ Assert.That(issues, Has.Count.EqualTo(count));
+ Assert.That(issues.All(issue => issue.Template is CheckFewHitsounds.IssueTemplateLongPeriodWarning));
+ }
+
+ private void assertLongPeriodNegligible(List hitObjects, int count = 1)
+ {
+ var issues = check.Run(getContext(hitObjects)).ToList();
+
+ Assert.That(issues, Has.Count.EqualTo(count));
+ Assert.That(issues.All(issue => issue.Template is CheckFewHitsounds.IssueTemplateLongPeriodNegligible));
+ }
+
+ private void assertNoHitsounds(List hitObjects)
+ {
+ var issues = check.Run(getContext(hitObjects)).ToList();
+
+ Assert.That(issues, Has.Count.EqualTo(1));
+ Assert.That(issues.Any(issue => issue.Template is CheckFewHitsounds.IssueTemplateNoHitsounds));
+ }
+
+ private BeatmapVerifierContext getContext(List hitObjects)
+ {
+ var beatmap = new Beatmap { HitObjects = hitObjects };
+
+ return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
+ }
+ }
+}
diff --git a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs
new file mode 100644
index 0000000000..41a8f72305
--- /dev/null
+++ b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs
@@ -0,0 +1,289 @@
+// 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 NUnit.Framework;
+using osu.Game.Audio;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Edit.Checks;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Tests.Editing.Checks
+{
+ [TestFixture]
+ public class CheckMutedObjectsTest
+ {
+ private CheckMutedObjects check;
+ private ControlPointInfo cpi;
+
+ private const int volume_regular = 50;
+ private const int volume_low = 15;
+ private const int volume_muted = 5;
+
+ [SetUp]
+ public void Setup()
+ {
+ check = new CheckMutedObjects();
+
+ cpi = new ControlPointInfo();
+ cpi.Add(0, new SampleControlPoint { SampleVolume = volume_regular });
+ cpi.Add(1000, new SampleControlPoint { SampleVolume = volume_low });
+ cpi.Add(2000, new SampleControlPoint { SampleVolume = volume_muted });
+ }
+
+ [Test]
+ public void TestNormalControlPointVolume()
+ {
+ var hitcircle = new HitCircle
+ {
+ StartTime = 0,
+ Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
+ };
+ hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ assertOk(new List { hitcircle });
+ }
+
+ [Test]
+ public void TestLowControlPointVolume()
+ {
+ var hitcircle = new HitCircle
+ {
+ StartTime = 1000,
+ Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
+ };
+ hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ assertLowVolume(new List { hitcircle });
+ }
+
+ [Test]
+ public void TestMutedControlPointVolume()
+ {
+ var hitcircle = new HitCircle
+ {
+ StartTime = 2000,
+ Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
+ };
+ hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ assertMuted(new List { hitcircle });
+ }
+
+ [Test]
+ public void TestNormalSampleVolume()
+ {
+ // The sample volume should take precedence over the control point volume.
+ var hitcircle = new HitCircle
+ {
+ StartTime = 2000,
+ Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) }
+ };
+ hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ assertOk(new List { hitcircle });
+ }
+
+ [Test]
+ public void TestLowSampleVolume()
+ {
+ var hitcircle = new HitCircle
+ {
+ StartTime = 2000,
+ Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_low) }
+ };
+ hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ assertLowVolume(new List { hitcircle });
+ }
+
+ [Test]
+ public void TestMutedSampleVolume()
+ {
+ var hitcircle = new HitCircle
+ {
+ StartTime = 0,
+ Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) }
+ };
+ hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ assertMuted(new List { hitcircle });
+ }
+
+ [Test]
+ public void TestNormalSampleVolumeSlider()
+ {
+ var sliderHead = new SliderHeadCircle
+ {
+ StartTime = 0,
+ Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
+ };
+ sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ var sliderTick = new SliderTick
+ {
+ StartTime = 250,
+ Samples = new List { new HitSampleInfo("slidertick", volume: volume_muted) } // Should be fine.
+ };
+ sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 500)
+ {
+ Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
+ };
+ slider.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ assertOk(new List { slider });
+ }
+
+ [Test]
+ public void TestMutedSampleVolumeSliderHead()
+ {
+ var sliderHead = new SliderHeadCircle
+ {
+ StartTime = 0,
+ Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) }
+ };
+ sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ var sliderTick = new SliderTick
+ {
+ StartTime = 250,
+ Samples = new List { new HitSampleInfo("slidertick") }
+ };
+ sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 500)
+ {
+ Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } // Applies to the tail.
+ };
+ slider.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ assertMuted(new List { slider });
+ }
+
+ [Test]
+ public void TestMutedSampleVolumeSliderTail()
+ {
+ var sliderHead = new SliderHeadCircle
+ {
+ StartTime = 0,
+ Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
+ };
+ sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ var sliderTick = new SliderTick
+ {
+ StartTime = 250,
+ Samples = new List { new HitSampleInfo("slidertick") }
+ };
+ sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 2500)
+ {
+ Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) } // Applies to the tail.
+ };
+ slider.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ assertMutedPassive(new List { slider });
+ }
+
+ [Test]
+ public void TestMutedControlPointVolumeSliderHead()
+ {
+ var sliderHead = new SliderHeadCircle
+ {
+ StartTime = 2000,
+ Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
+ };
+ sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ var sliderTick = new SliderTick
+ {
+ StartTime = 2250,
+ Samples = new List { new HitSampleInfo("slidertick") }
+ };
+ sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 2000, endTime: 2500)
+ {
+ Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) }
+ };
+ slider.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ assertMuted(new List { slider });
+ }
+
+ [Test]
+ public void TestMutedControlPointVolumeSliderTail()
+ {
+ var sliderHead = new SliderHeadCircle
+ {
+ StartTime = 0,
+ Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
+ };
+ sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ var sliderTick = new SliderTick
+ {
+ StartTime = 250,
+ Samples = new List { new HitSampleInfo("slidertick") }
+ };
+ sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ // Ends after the 5% control point.
+ var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 2500)
+ {
+ Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
+ };
+ slider.ApplyDefaults(cpi, new BeatmapDifficulty());
+
+ assertMutedPassive(new List { slider });
+ }
+
+ private void assertOk(List hitObjects)
+ {
+ Assert.That(check.Run(getContext(hitObjects)), Is.Empty);
+ }
+
+ private void assertLowVolume(List hitObjects, int count = 1)
+ {
+ var issues = check.Run(getContext(hitObjects)).ToList();
+
+ Assert.That(issues, Has.Count.EqualTo(count));
+ Assert.That(issues.All(issue => issue.Template is CheckMutedObjects.IssueTemplateLowVolumeActive));
+ }
+
+ private void assertMuted(List hitObjects, int count = 1)
+ {
+ var issues = check.Run(getContext(hitObjects)).ToList();
+
+ Assert.That(issues, Has.Count.EqualTo(count));
+ Assert.That(issues.All(issue => issue.Template is CheckMutedObjects.IssueTemplateMutedActive));
+ }
+
+ private void assertMutedPassive(List hitObjects)
+ {
+ var issues = check.Run(getContext(hitObjects)).ToList();
+
+ Assert.That(issues, Has.Count.EqualTo(1));
+ Assert.That(issues.Any(issue => issue.Template is CheckMutedObjects.IssueTemplateMutedPassive));
+ }
+
+ private BeatmapVerifierContext getContext(List hitObjects)
+ {
+ var beatmap = new Beatmap
+ {
+ ControlPointInfo = cpi,
+ HitObjects = hitObjects
+ };
+
+ return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
+ }
+ }
+}
diff --git a/osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs b/osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs
new file mode 100644
index 0000000000..29938839d3
--- /dev/null
+++ b/osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs
@@ -0,0 +1,36 @@
+// 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.Threading;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+
+namespace osu.Game.Tests.Editing.Checks
+{
+ public sealed class MockNestableHitObject : HitObject, IHasDuration
+ {
+ private readonly IEnumerable toBeNested;
+
+ public MockNestableHitObject(IEnumerable toBeNested, double startTime, double endTime)
+ {
+ this.toBeNested = toBeNested;
+ StartTime = startTime;
+ EndTime = endTime;
+ }
+
+ protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
+ {
+ foreach (var hitObject in toBeNested)
+ AddNested(hitObject);
+ }
+
+ public double EndTime { get; }
+
+ public double Duration
+ {
+ get => EndTime - StartTime;
+ set => throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs
index ea351e0d45..e888f51e98 100644
--- a/osu.Game.Tests/ImportTest.cs
+++ b/osu.Game.Tests/ImportTest.cs
@@ -17,7 +17,8 @@ namespace osu.Game.Tests
protected virtual TestOsuGameBase LoadOsuIntoHost(GameHost host, bool withBeatmap = false)
{
var osu = new TestOsuGameBase(withBeatmap);
- Task.Run(() => host.Run(osu));
+ Task.Run(() => host.Run(osu))
+ .ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted);
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
index 9bd262a569..a55bdd2df8 100644
--- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
+++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
@@ -90,6 +90,20 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.Less(filterCriteria.DrainRate.Min, 6.1f);
}
+ [Test]
+ public void TestApplyOverallDifficultyQueries()
+ {
+ const string query = "od>4 easy od<8";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual("easy", filterCriteria.SearchText.Trim());
+ Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
+ Assert.Greater(filterCriteria.OverallDifficulty.Min, 4.0);
+ Assert.Less(filterCriteria.OverallDifficulty.Min, 4.1);
+ Assert.Greater(filterCriteria.OverallDifficulty.Max, 7.9);
+ Assert.Less(filterCriteria.OverallDifficulty.Max, 8.0);
+ }
+
[Test]
public void TestApplyBPMQueries()
{
diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
index adc1d6aede..0983b806e2 100644
--- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
+++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
@@ -7,6 +7,7 @@ using NUnit.Framework;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Testing;
using osu.Game.Online.Multiplayer;
+using osu.Game.Online.Rooms;
using osu.Game.Tests.Visual.Multiplayer;
using osu.Game.Users;
@@ -50,7 +51,10 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
AddStep("create room initially in gameplay", () =>
{
- Room.RoomID.Value = null;
+ var newRoom = new Room();
+ newRoom.CopyFrom(SelectedRoom.Value);
+
+ newRoom.RoomID.Value = null;
Client.RoomSetupAction = room =>
{
room.State = MultiplayerRoomState.Playing;
@@ -61,7 +65,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
});
};
- RoomManager.CreateRoom(Room);
+ RoomManager.CreateRoom(newRoom);
});
AddUntilStep("wait for room join", () => Client.Room != null);
diff --git a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs
index d4e591cf09..6851df3832 100644
--- a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs
+++ b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs
@@ -31,32 +31,24 @@ namespace osu.Game.Tests.OnlinePlay
}
[Test]
- public void TestMasterClockStartsWhenAllPlayerClocksHaveFrames()
+ public void TestPlayerClocksStartWhenAllHaveFrames()
{
setWaiting(() => player1, false);
- assertMasterState(false);
assertPlayerClockState(() => player1, false);
assertPlayerClockState(() => player2, false);
setWaiting(() => player2, false);
- assertMasterState(true);
assertPlayerClockState(() => player1, true);
assertPlayerClockState(() => player2, true);
}
[Test]
- public void TestMasterClockDoesNotStartWhenNoneReadyForMaximumDelayTime()
- {
- AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction));
- assertMasterState(false);
- }
-
- [Test]
- public void TestMasterClockStartsWhenAnyReadyForMaximumDelayTime()
+ public void TestReadyPlayersStartWhenReadyForMaximumDelayTime()
{
setWaiting(() => player1, false);
AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction));
- assertMasterState(true);
+ assertPlayerClockState(() => player1, true);
+ assertPlayerClockState(() => player2, false);
}
[Test]
@@ -153,9 +145,6 @@ namespace osu.Game.Tests.OnlinePlay
private void setPlayerClockTime(Func playerClock, double offsetFromMaster)
=> AddStep($"set player clock {playerClock().Id} = master - {offsetFromMaster}", () => playerClock().Seek(master.CurrentTime - offsetFromMaster));
- private void assertMasterState(bool running)
- => AddAssert($"master clock {(running ? "is" : "is not")} running", () => master.IsRunning == running);
-
private void assertCatchingUp(Func playerClock, bool catchingUp) =>
AddAssert($"player clock {playerClock().Id} {(catchingUp ? "is" : "is not")} catching up", () => playerClock().IsCatchingUp == catchingUp);
@@ -201,6 +190,11 @@ namespace osu.Game.Tests.OnlinePlay
private class TestManualClock : ManualClock, IAdjustableClock
{
+ public TestManualClock()
+ {
+ IsRunning = true;
+ }
+
public void Start() => IsRunning = true;
public void Stop() => IsRunning = false;
diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs
new file mode 100644
index 0000000000..28ad7ed6a7
--- /dev/null
+++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs
@@ -0,0 +1,89 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.OpenGL.Textures;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Testing;
+using osu.Game.Audio;
+using osu.Game.Rulesets;
+using osu.Game.Skinning;
+using osu.Game.Tests.Testing;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Rulesets
+{
+ [HeadlessTest]
+ public class TestSceneRulesetSkinProvidingContainer : OsuTestScene
+ {
+ private SkinRequester requester;
+
+ protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset();
+
+ [Test]
+ public void TestRulesetResources()
+ {
+ setupProviderStep();
+
+ AddAssert("ruleset texture retrieved via skin", () => requester.GetTexture("test-image") != null);
+ AddAssert("ruleset sample retrieved via skin", () => requester.GetSample(new SampleInfo("test-sample")) != null);
+ }
+
+ [Test]
+ public void TestEarlyAddedSkinRequester()
+ {
+ Texture textureOnLoad = null;
+
+ AddStep("setup provider", () =>
+ {
+ var rulesetSkinProvider = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin);
+
+ rulesetSkinProvider.Add(requester = new SkinRequester());
+
+ requester.OnLoadAsync += () => textureOnLoad = requester.GetTexture("test-image");
+
+ Child = rulesetSkinProvider;
+ });
+
+ AddAssert("requester got correct initial texture", () => textureOnLoad != null);
+ }
+
+ private void setupProviderStep()
+ {
+ AddStep("setup provider", () =>
+ {
+ Child = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin)
+ .WithChild(requester = new SkinRequester());
+ });
+ }
+
+ private class SkinRequester : Drawable, ISkin
+ {
+ private ISkinSource skin;
+
+ public event Action OnLoadAsync;
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ this.skin = skin;
+
+ OnLoadAsync?.Invoke();
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component);
+
+ public Texture GetTexture(string componentName, WrapMode wrapModeS = default, WrapMode wrapModeT = default) => skin.GetTexture(componentName);
+
+ public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
+
+ public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
index 7522aca5dc..cd7d744f53 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Tests.Scores.IO
OnlineScoreID = 12345,
};
- var imported = await loadScoreIntoOsu(osu, toImport);
+ var imported = await LoadScoreIntoOsu(osu, toImport);
Assert.AreEqual(toImport.Rank, imported.Rank);
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
@@ -75,7 +75,7 @@ namespace osu.Game.Tests.Scores.IO
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
};
- var imported = await loadScoreIntoOsu(osu, toImport);
+ var imported = await LoadScoreIntoOsu(osu, toImport);
Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock));
Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime));
@@ -105,7 +105,7 @@ namespace osu.Game.Tests.Scores.IO
}
};
- var imported = await loadScoreIntoOsu(osu, toImport);
+ var imported = await LoadScoreIntoOsu(osu, toImport);
Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]);
Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]);
@@ -136,7 +136,7 @@ namespace osu.Game.Tests.Scores.IO
}
};
- var imported = await loadScoreIntoOsu(osu, toImport);
+ var imported = await LoadScoreIntoOsu(osu, toImport);
var beatmapManager = osu.Dependencies.Get();
var scoreManager = osu.Dependencies.Get();
@@ -144,7 +144,7 @@ namespace osu.Game.Tests.Scores.IO
beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.Beatmap.ID)));
Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true));
- var secondImport = await loadScoreIntoOsu(osu, imported);
+ var secondImport = await LoadScoreIntoOsu(osu, imported);
Assert.That(secondImport, Is.Null);
}
finally
@@ -163,7 +163,7 @@ namespace osu.Game.Tests.Scores.IO
{
var osu = LoadOsuIntoHost(host, true);
- await loadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader());
+ await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader());
var scoreManager = osu.Dependencies.Get();
@@ -177,7 +177,7 @@ namespace osu.Game.Tests.Scores.IO
}
}
- private async Task loadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null)
+ public static async Task LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null)
{
var beatmapManager = osu.Dependencies.Get();
@@ -190,7 +190,7 @@ namespace osu.Game.Tests.Scores.IO
return scoreManager.GetAllUsableScores().FirstOrDefault();
}
- private class TestArchiveReader : ArchiveReader
+ internal class TestArchiveReader : ArchiveReader
{
public TestArchiveReader()
: base("test_archive")
diff --git a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs
index 97087e31ab..fb50da32f3 100644
--- a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs
+++ b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Tests.Testing
Dependencies.Get() != null);
}
- private class TestRuleset : Ruleset
+ public class TestRuleset : Ruleset
{
public override string Description => string.Empty;
public override string ShortName => string.Empty;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs
index 6eeb3596a8..7584d67afe 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs
@@ -2,8 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -41,8 +39,6 @@ namespace osu.Game.Tests.Visual.Gameplay
[Resolved]
private OsuGameBase game { get; set; }
- private int nextFrame;
-
private BeatmapSetInfo importedBeatmap;
private int importedBeatmapId;
@@ -51,8 +47,6 @@ namespace osu.Game.Tests.Visual.Gameplay
{
base.SetUpSteps();
- AddStep("reset sent frames", () => nextFrame = 0);
-
AddStep("import beatmap", () =>
{
importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result;
@@ -105,7 +99,8 @@ namespace osu.Game.Tests.Visual.Gameplay
waitForPlayer();
checkPaused(true);
- sendFrames(1000); // send enough frames to ensure play won't be paused
+ // send enough frames to ensure play won't be paused
+ sendFrames(100);
checkPaused(false);
}
@@ -114,12 +109,12 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestSpectatingDuringGameplay()
{
start();
+ sendFrames(300);
loadSpectatingScreen();
waitForPlayer();
- AddStep("advance frame count", () => nextFrame = 300);
- sendFrames();
+ sendFrames(300);
AddUntilStep("playing from correct point in time", () => player.ChildrenOfType().First().FrameStableClock.CurrentTime > 30000);
}
@@ -220,11 +215,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void sendFrames(int count = 10)
{
- AddStep("send frames", () =>
- {
- testSpectatorClient.SendFrames(streamingUser.Id, nextFrame, count);
- nextFrame += count;
- });
+ AddStep("send frames", () => testSpectatorClient.SendFrames(streamingUser.Id, count));
}
private void loadSpectatingScreen()
@@ -232,14 +223,5 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("load screen", () => LoadScreen(spectatorScreen = new SoloSpectator(streamingUser)));
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded);
}
-
- internal class TestUserLookupCache : UserLookupCache
- {
- protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) => Task.FromResult(new User
- {
- Id = lookup,
- Username = $"User {lookup}"
- });
- }
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs
deleted file mode 100644
index c665a57452..0000000000
--- a/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using osu.Framework.Allocation;
-using osu.Game.Beatmaps;
-using osu.Game.Online.Rooms;
-using osu.Game.Rulesets;
-using osu.Game.Screens.OnlinePlay;
-using osu.Game.Users;
-
-namespace osu.Game.Tests.Visual.Multiplayer
-{
- public abstract class RoomManagerTestScene : RoomTestScene
- {
- [Cached(Type = typeof(IRoomManager))]
- protected TestRoomManager RoomManager { get; } = new TestRoomManager();
-
- public override void SetUpSteps()
- {
- base.SetUpSteps();
-
- AddStep("clear rooms", () => RoomManager.Rooms.Clear());
- }
-
- protected void AddRooms(int count, RulesetInfo ruleset = null)
- {
- AddStep("add rooms", () =>
- {
- for (int i = 0; i < count; i++)
- {
- var room = new Room
- {
- RoomID = { Value = i },
- Name = { Value = $"Room {i}" },
- Host = { Value = new User { Username = "Host" } },
- EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) },
- Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }
- };
-
- if (ruleset != null)
- {
- room.Playlist.Add(new PlaylistItem
- {
- Ruleset = { Value = ruleset },
- Beatmap =
- {
- Value = new BeatmapInfo
- {
- Metadata = new BeatmapMetadata()
- }
- }
- });
- }
-
- RoomManager.Rooms.Add(room);
- }
- });
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs
deleted file mode 100644
index 1785c99784..0000000000
--- a/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using osu.Framework.Bindables;
-using osu.Game.Online.Rooms;
-using osu.Game.Screens.OnlinePlay;
-
-namespace osu.Game.Tests.Visual.Multiplayer
-{
- public class TestRoomManager : IRoomManager
- {
- public event Action RoomsUpdated
- {
- add { }
- remove { }
- }
-
- public readonly BindableList Rooms = new BindableList();
-
- public IBindable InitialRoomsReceived { get; } = new Bindable(true);
-
- IBindableList IRoomManager.Rooms => Rooms;
-
- public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) => Rooms.Add(room);
-
- public void JoinRoom(Room room, Action onSuccess = null, Action onError = null)
- {
- }
-
- public void PartRoom()
- {
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs
index 9f24347ae9..471d0b6c98 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs
@@ -4,17 +4,21 @@
using System;
using NUnit.Framework;
using osu.Framework.Graphics;
+using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
+using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestSceneLoungeRoomInfo : RoomTestScene
+ public class TestSceneLoungeRoomInfo : OnlinePlayTestScene
{
[SetUp]
public new void Setup() => Schedule(() =>
{
+ SelectedRoom.Value = new Room();
+
Child = new RoomInfo
{
Anchor = Anchor.Centre,
@@ -23,15 +27,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
};
});
- public override void SetUpSteps()
- {
- // Todo: Temp
- }
-
[Test]
public void TestNonSelectedRoom()
{
- AddStep("set null room", () => Room.RoomID.Value = null);
+ AddStep("set null room", () => SelectedRoom.Value.RoomID.Value = null);
}
[Test]
@@ -39,11 +38,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("set open room", () =>
{
- Room.RoomID.Value = 0;
- Room.Name.Value = "Room 0";
- Room.Host.Value = new User { Username = "peppy", Id = 2 };
- Room.EndDate.Value = DateTimeOffset.Now.AddMonths(1);
- Room.Status.Value = new RoomStatusOpen();
+ SelectedRoom.Value.RoomID.Value = 0;
+ SelectedRoom.Value.Name.Value = "Room 0";
+ SelectedRoom.Value.Host.Value = new User { Username = "peppy", Id = 2 };
+ SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMonths(1);
+ SelectedRoom.Value.Status.Value = new RoomStatusOpen();
});
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
index 5682fd5c3c..75cc687ee8 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
@@ -3,24 +3,26 @@
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
+using osu.Game.Tests.Visual.OnlinePlay;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestSceneLoungeRoomsContainer : RoomManagerTestScene
+ public class TestSceneLoungeRoomsContainer : OnlinePlayTestScene
{
+ protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager;
+
private RoomsContainer container;
- [BackgroundDependencyLoader]
- private void load()
+ [SetUp]
+ public new void Setup() => Schedule(() =>
{
Child = container = new RoomsContainer
{
@@ -29,12 +31,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
Width = 0.5f,
JoinRequested = joinRequested
};
- }
+ });
[Test]
public void TestBasicListChanges()
{
- AddRooms(3);
+ AddStep("add rooms", () => RoomManager.AddRooms(3));
AddAssert("has 3 rooms", () => container.Rooms.Count == 3);
AddStep("remove first room", () => RoomManager.Rooms.Remove(RoomManager.Rooms.FirstOrDefault()));
@@ -51,7 +53,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestKeyboardNavigation()
{
- AddRooms(3);
+ AddStep("add rooms", () => RoomManager.AddRooms(3));
AddAssert("no selection", () => checkRoomSelected(null));
@@ -72,7 +74,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestClickDeselection()
{
- AddRooms(1);
+ AddStep("add room", () => RoomManager.AddRooms(1));
AddAssert("no selection", () => checkRoomSelected(null));
@@ -91,7 +93,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestStringFiltering()
{
- AddRooms(4);
+ AddStep("add rooms", () => RoomManager.AddRooms(4));
AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
@@ -107,21 +109,21 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestRulesetFiltering()
{
- AddRooms(2, new OsuRuleset().RulesetInfo);
- AddRooms(3, new CatchRuleset().RulesetInfo);
+ AddStep("add rooms", () => RoomManager.AddRooms(2, new OsuRuleset().RulesetInfo));
+ AddStep("add rooms", () => RoomManager.AddRooms(3, new CatchRuleset().RulesetInfo));
+ // Todo: What even is this case...?
+ AddStep("set empty filter criteria", () => container.Filter(null));
AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5);
AddStep("filter osu! rooms", () => container.Filter(new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo }));
-
AddUntilStep("2 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2);
AddStep("filter catch rooms", () => container.Filter(new FilterCriteria { Ruleset = new CatchRuleset().RulesetInfo }));
-
AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3);
}
- private bool checkRoomSelected(Room room) => Room == room;
+ private bool checkRoomSelected(Room room) => SelectedRoom.Value == room;
private void joinRequested(Room room) => room.Status.Value = new JoinedRoomStatus();
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs
index 9ad9f2c883..d66603a448 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs
@@ -11,11 +11,12 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Visual.OnlinePlay;
using osuTK;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestSceneMatchBeatmapDetailArea : RoomTestScene
+ public class TestSceneMatchBeatmapDetailArea : OnlinePlayTestScene
{
[Resolved]
private BeatmapManager beatmapManager { get; set; }
@@ -26,6 +27,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[SetUp]
public new void Setup() => Schedule(() =>
{
+ SelectedRoom.Value = new Room();
+
Child = new MatchBeatmapDetailArea
{
Anchor = Anchor.Centre,
@@ -37,9 +40,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void createNewItem()
{
- Room.Playlist.Add(new PlaylistItem
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
- ID = Room.Playlist.Count,
+ ID = SelectedRoom.Value.Playlist.Count,
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
RequiredMods =
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs
index 7cdc6b1a7d..71ba5db481 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs
@@ -7,46 +7,49 @@ using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Match.Components;
+using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestSceneMatchHeader : RoomTestScene
+ public class TestSceneMatchHeader : OnlinePlayTestScene
{
- public TestSceneMatchHeader()
- {
- Child = new Header();
- }
-
[SetUp]
public new void Setup() => Schedule(() =>
{
- Room.Playlist.Add(new PlaylistItem
+ SelectedRoom.Value = new Room
{
- Beatmap =
+ Name = { Value = "A very awesome room" },
+ Host = { Value = new User { Id = 2, Username = "peppy" } },
+ Playlist =
{
- Value = new BeatmapInfo
+ new PlaylistItem
{
- Metadata = new BeatmapMetadata
+ Beatmap =
{
- Title = "Title",
- Artist = "Artist",
- AuthorString = "Author",
+ Value = new BeatmapInfo
+ {
+ Metadata = new BeatmapMetadata
+ {
+ Title = "Title",
+ Artist = "Artist",
+ AuthorString = "Author",
+ },
+ Version = "Version",
+ Ruleset = new OsuRuleset().RulesetInfo
+ }
},
- Version = "Version",
- Ruleset = new OsuRuleset().RulesetInfo
+ RequiredMods =
+ {
+ new OsuModDoubleTime(),
+ new OsuModNoFail(),
+ new OsuModRelax(),
+ }
}
- },
- RequiredMods =
- {
- new OsuModDoubleTime(),
- new OsuModNoFail(),
- new OsuModRelax(),
}
- });
+ };
- Room.Name.Value = "A very awesome room";
- Room.Host.Value = new User { Id = 2, Username = "peppy" };
+ Child = new Header();
});
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
index 64eaf0556b..a7a5f3af39 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
@@ -2,72 +2,74 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
-using Newtonsoft.Json;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Online.API;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Match.Components;
+using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestSceneMatchLeaderboard : RoomTestScene
+ public class TestSceneMatchLeaderboard : OnlinePlayTestScene
{
- protected override bool UseOnlineAPI => true;
-
- public TestSceneMatchLeaderboard()
- {
- Add(new MatchLeaderboard
- {
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- Size = new Vector2(550f, 450f),
- Scope = MatchLeaderboardScope.Overall,
- });
- }
-
[BackgroundDependencyLoader]
- private void load(IAPIProvider api)
+ private void load()
{
- var req = new GetRoomScoresRequest();
- req.Success += v => { };
- req.Failure += _ => { };
+ ((DummyAPIAccess)API).HandleRequest = r =>
+ {
+ switch (r)
+ {
+ case GetRoomLeaderboardRequest leaderboardRequest:
+ leaderboardRequest.TriggerSuccess(new APILeaderboard
+ {
+ Leaderboard = new List
+ {
+ new APIUserScoreAggregate
+ {
+ UserID = 2,
+ User = new User { Id = 2, Username = "peppy" },
+ TotalScore = 995533,
+ RoomID = 3,
+ CompletedBeatmaps = 1,
+ TotalAttempts = 6,
+ Accuracy = 0.9851
+ },
+ new APIUserScoreAggregate
+ {
+ UserID = 1040328,
+ User = new User { Id = 1040328, Username = "smoogipoo" },
+ TotalScore = 981100,
+ RoomID = 3,
+ CompletedBeatmaps = 1,
+ TotalAttempts = 9,
+ Accuracy = 0.937
+ }
+ }
+ });
+ return true;
+ }
- api.Queue(req);
+ return false;
+ };
}
[SetUp]
public new void Setup() => Schedule(() =>
{
- Room.RoomID.Value = 3;
+ SelectedRoom.Value = new Room { RoomID = { Value = 3 } };
+
+ Child = new MatchLeaderboard
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ Size = new Vector2(550f, 450f),
+ Scope = MatchLeaderboardScope.Overall,
+ };
});
-
- private class GetRoomScoresRequest : APIRequest>
- {
- protected override string Target => "rooms/3/leaderboard";
- }
-
- private class RoomScore
- {
- [JsonProperty("user")]
- public User User { get; set; }
-
- [JsonProperty("accuracy")]
- public double Accuracy { get; set; }
-
- [JsonProperty("total_score")]
- public int TotalScore { get; set; }
-
- [JsonProperty("pp")]
- public double PP { get; set; }
-
- [JsonProperty("attempts")]
- public int TotalAttempts { get; set; }
-
- [JsonProperty("completed")]
- public int CompletedAttempts { get; set; }
- }
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
index 5ad35be0ec..e14df62af1 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
@@ -3,74 +3,40 @@
using System.Collections.Generic;
using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
using NUnit.Framework;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Timing;
-using osu.Game.Database;
-using osu.Game.Online.Spectator;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
using osu.Game.Screens.Play.HUD;
-using osu.Game.Tests.Visual.Spectator;
-using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene
{
- [Cached(typeof(SpectatorClient))]
- private TestSpectatorClient spectatorClient = new TestSpectatorClient();
-
- [Cached(typeof(UserLookupCache))]
- private UserLookupCache lookupCache = new TestUserLookupCache();
-
- protected override Container Content => content;
- private readonly Container content;
-
- private readonly Dictionary clocks = new Dictionary
- {
- { PLAYER_1_ID, new ManualClock() },
- { PLAYER_2_ID, new ManualClock() }
- };
-
- public TestSceneMultiSpectatorLeaderboard()
- {
- base.Content.AddRange(new Drawable[]
- {
- spectatorClient,
- lookupCache,
- content = new Container { RelativeSizeAxes = Axes.Both }
- });
- }
+ private Dictionary clocks;
+ private MultiSpectatorLeaderboard leaderboard;
[SetUpSteps]
public new void SetUpSteps()
{
- MultiSpectatorLeaderboard leaderboard = null;
-
AddStep("reset", () =>
{
Clear();
- foreach (var (userId, clock) in clocks)
+ clocks = new Dictionary
{
- spectatorClient.EndPlay(userId);
- clock.CurrentTime = 0;
- }
+ { PLAYER_1_ID, new ManualClock() },
+ { PLAYER_2_ID, new ManualClock() }
+ };
+
+ foreach (var (userId, _) in clocks)
+ SpectatorClient.StartPlay(userId, 0);
});
AddStep("create leaderboard", () =>
{
- foreach (var (userId, _) in clocks)
- spectatorClient.StartPlay(userId, 0);
-
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
-
var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
var scoreProcessor = new OsuScoreProcessor();
scoreProcessor.ApplyBeatmap(playable);
@@ -96,10 +62,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
// For player 2, send frames in sets of 10.
for (int i = 0; i < 100; i++)
{
- spectatorClient.SendFrames(PLAYER_1_ID, i, 1);
+ SpectatorClient.SendFrames(PLAYER_1_ID, 1);
if (i % 10 == 0)
- spectatorClient.SendFrames(PLAYER_2_ID, i, 10);
+ SpectatorClient.SendFrames(PLAYER_2_ID, 10);
}
});
@@ -145,17 +111,5 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void assertCombo(int userId, int expectedCombo)
=> AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo);
-
- private class TestUserLookupCache : UserLookupCache
- {
- protected override Task ComputeValueAsync(int lookup, CancellationToken token = default)
- {
- return Task.FromResult(new User
- {
- Id = lookup,
- Username = $"User {lookup}"
- });
- }
- }
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
index b91391c409..b8db4067fb 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
@@ -3,31 +3,20 @@
using System.Collections.Generic;
using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
-using osu.Game.Database;
-using osu.Game.Online.Spectator;
+using osu.Game.Rulesets.UI;
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps.IO;
-using osu.Game.Tests.Visual.Spectator;
-using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiSpectatorScreen : MultiplayerTestScene
{
- [Cached(typeof(SpectatorClient))]
- private TestSpectatorClient spectatorClient = new TestSpectatorClient();
-
- [Cached(typeof(UserLookupCache))]
- private UserLookupCache lookupCache = new TestUserLookupCache();
-
[Resolved]
private OsuGameBase game { get; set; }
@@ -37,7 +26,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
private MultiSpectatorScreen spectatorScreen;
private readonly List playingUserIds = new List();
- private readonly Dictionary nextFrame = new Dictionary();
private BeatmapSetInfo importedSet;
private BeatmapInfo importedBeatmap;
@@ -51,25 +39,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
importedBeatmapId = importedBeatmap.OnlineBeatmapID ?? -1;
}
- public override void SetUpSteps()
- {
- base.SetUpSteps();
-
- AddStep("reset sent frames", () => nextFrame.Clear());
-
- AddStep("add streaming client", () =>
- {
- Remove(spectatorClient);
- Add(spectatorClient);
- });
-
- AddStep("finish previous gameplay", () =>
- {
- foreach (var id in playingUserIds)
- spectatorClient.EndPlay(id);
- playingUserIds.Clear();
- });
- }
+ [SetUp]
+ public new void Setup() => Schedule(() => playingUserIds.Clear());
[Test]
public void TestDelayedStart()
@@ -80,18 +51,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
Client.CurrentMatchPlayingUserIds.Add(PLAYER_2_ID);
playingUserIds.Add(PLAYER_1_ID);
playingUserIds.Add(PLAYER_2_ID);
- nextFrame[PLAYER_1_ID] = 0;
- nextFrame[PLAYER_2_ID] = 0;
});
loadSpectateScreen(false);
AddWaitStep("wait a bit", 10);
- AddStep("load player first_player_id", () => spectatorClient.StartPlay(PLAYER_1_ID, importedBeatmapId));
+ AddStep("load player first_player_id", () => SpectatorClient.StartPlay(PLAYER_1_ID, importedBeatmapId));
AddUntilStep("one player added", () => spectatorScreen.ChildrenOfType().Count() == 1);
AddWaitStep("wait a bit", 10);
- AddStep("load player second_player_id", () => spectatorClient.StartPlay(PLAYER_2_ID, importedBeatmapId));
+ AddStep("load player second_player_id", () => SpectatorClient.StartPlay(PLAYER_2_ID, importedBeatmapId));
AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType().Count() == 2);
}
@@ -107,6 +76,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddWaitStep("wait a bit", 20);
}
+ [Test]
+ public void TestTimeDoesNotProgressWhileAllPlayersPaused()
+ {
+ start(new[] { PLAYER_1_ID, PLAYER_2_ID });
+ loadSpectateScreen();
+
+ sendFrames(PLAYER_1_ID, 40);
+ sendFrames(PLAYER_2_ID, 20);
+
+ checkPaused(PLAYER_2_ID, true);
+ checkPausedInstant(PLAYER_1_ID, false);
+ AddAssert("master clock still running", () => this.ChildrenOfType().Single().IsRunning);
+
+ checkPaused(PLAYER_1_ID, true);
+ AddUntilStep("master clock paused", () => !this.ChildrenOfType().Single().IsRunning);
+ }
+
[Test]
public void TestPlayersMustStartSimultaneously()
{
@@ -182,7 +168,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
// Send initial frames for both players. A few more for player 1.
sendFrames(PLAYER_1_ID, 1000);
- sendFrames(PLAYER_2_ID, 10);
+ sendFrames(PLAYER_2_ID, 30);
checkPausedInstant(PLAYER_1_ID, false);
checkPausedInstant(PLAYER_2_ID, false);
@@ -210,8 +196,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
sendFrames(PLAYER_1_ID, 10);
sendFrames(PLAYER_2_ID, 20);
- assertMuted(PLAYER_1_ID, false);
- assertMuted(PLAYER_2_ID, true);
+ checkPaused(PLAYER_1_ID, false);
+ assertOneNotMuted();
checkPaused(PLAYER_1_ID, true);
assertMuted(PLAYER_1_ID, true);
@@ -229,6 +215,36 @@ namespace osu.Game.Tests.Visual.Multiplayer
assertMuted(PLAYER_2_ID, true);
}
+ [Test]
+ public void TestSpectatingDuringGameplay()
+ {
+ var players = new[] { PLAYER_1_ID, PLAYER_2_ID };
+
+ start(players);
+ sendFrames(players, 300);
+
+ loadSpectateScreen();
+ sendFrames(players, 300);
+
+ AddUntilStep("playing from correct point in time", () => this.ChildrenOfType().All(r => r.FrameStableClock.CurrentTime > 30000));
+ }
+
+ [Test]
+ public void TestSpectatingDuringGameplayWithLateFrames()
+ {
+ start(new[] { PLAYER_1_ID, PLAYER_2_ID });
+ sendFrames(new[] { PLAYER_1_ID, PLAYER_2_ID }, 300);
+
+ loadSpectateScreen();
+ sendFrames(PLAYER_1_ID, 300);
+
+ AddWaitStep("wait maximum start delay seconds", (int)(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction));
+ checkPaused(PLAYER_1_ID, false);
+
+ sendFrames(PLAYER_2_ID, 300);
+ AddUntilStep("player 2 playing from correct point in time", () => getPlayer(PLAYER_2_ID).ChildrenOfType().Single().FrameStableClock.CurrentTime > 30000);
+ }
+
private void loadSpectateScreen(bool waitForPlayerLoad = true)
{
AddStep("load screen", () =>
@@ -242,8 +258,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded));
}
- private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId);
-
private void start(int[] userIds, int? beatmapId = null)
{
AddStep("start play", () =>
@@ -251,23 +265,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
foreach (int id in userIds)
{
Client.CurrentMatchPlayingUserIds.Add(id);
- spectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId);
+ SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId);
playingUserIds.Add(id);
- nextFrame[id] = 0;
}
});
}
- private void finish(int userId)
- {
- AddStep("end play", () =>
- {
- spectatorClient.EndPlay(userId);
- playingUserIds.Remove(userId);
- nextFrame.Remove(userId);
- });
- }
-
private void sendFrames(int userId, int count = 10) => sendFrames(new[] { userId }, count);
private void sendFrames(int[] userIds, int count = 10)
@@ -275,10 +278,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("send frames", () =>
{
foreach (int id in userIds)
- {
- spectatorClient.SendFrames(id, nextFrame[id], count);
- nextFrame[id] += count;
- }
+ SpectatorClient.SendFrames(id, count);
});
}
@@ -286,7 +286,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
=> AddUntilStep($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state);
private void checkPausedInstant(int userId, bool state)
- => AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state);
+ {
+ checkPaused(userId, state);
+
+ // Todo: The following should work, but is broken because SpectatorScreen retrieves the WorkingBeatmap via the BeatmapManager, bypassing the test scene clock and running real-time.
+ // AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state);
+ }
+
+ private void assertOneNotMuted() => AddAssert("one player not muted", () => spectatorScreen.ChildrenOfType().Count(p => !p.Mute) == 1);
private void assertMuted(int userId, bool muted)
=> AddAssert($"{userId} {(muted ? "is" : "is not")} muted", () => getInstance(userId).Mute == muted);
@@ -297,17 +304,5 @@ namespace osu.Game.Tests.Visual.Multiplayer
private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType().Single();
private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType().Single(p => p.UserId == userId);
-
- internal class TestUserLookupCache : UserLookupCache
- {
- protected override Task ComputeValueAsync(int lookup, CancellationToken token = default)
- {
- return Task.FromResult(new User
- {
- Id = lookup,
- Username = $"User {lookup}"
- });
- }
- }
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index 599dfb082b..c93640e7b5 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -18,6 +18,7 @@ using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Screens;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Match.Components;
using osu.Game.Screens.OnlinePlay.Multiplayer;
@@ -30,14 +31,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiplayer : ScreenTestScene
{
- private TestMultiplayer multiplayerScreen;
-
private BeatmapManager beatmaps;
private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
- private TestMultiplayerClient client => multiplayerScreen.Client;
- private Room room => client.APIRoom;
+ private DependenciesScreen dependenciesScreen;
+ private TestMultiplayer multiplayerScreen;
+ private TestMultiplayerClient client;
public TestSceneMultiplayer()
{
@@ -229,30 +229,43 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void loadMultiplayer()
{
- AddStep("show", () =>
+ AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer());
+
+ AddStep("load dependencies", () =>
{
- multiplayerScreen = new TestMultiplayer();
+ client = new TestMultiplayerClient(multiplayerScreen.RoomManager);
- // Needs to be added at a higher level since the multiplayer screen becomes non-current.
- Child = multiplayerScreen.Client;
+ // The screen gets suspended so it stops receiving updates.
+ Child = client;
- LoadScreen(multiplayerScreen);
+ LoadScreen(dependenciesScreen = new DependenciesScreen(client));
});
- AddUntilStep("wait for loaded", () => multiplayerScreen.IsLoaded);
+ AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded);
+
+ AddStep("load multiplayer", () => LoadScreen(multiplayerScreen));
+ AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded);
}
- private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
+ ///
+ /// Used for the sole purpose of adding as a resolvable dependency.
+ ///
+ private class DependenciesScreen : OsuScreen
{
[Cached(typeof(MultiplayerClient))]
public readonly TestMultiplayerClient Client;
- public TestMultiplayer()
+ public DependenciesScreen(TestMultiplayerClient client)
{
- Client = new TestMultiplayerClient((TestMultiplayerRoomManager)RoomManager);
+ Client = client;
}
+ }
- protected override RoomManager CreateRoomManager() => new TestMultiplayerRoomManager();
+ private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
+ {
+ public new TestMultiplayerRoomManager RoomManager { get; private set; }
+
+ protected override RoomManager CreateRoomManager() => RoomManager = new TestMultiplayerRoomManager();
}
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
index af2f6fa5fe..0e368b59dd 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
@@ -6,12 +6,11 @@ using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Configuration;
-using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Online.Spectator;
using osu.Game.Replays.Legacy;
@@ -19,37 +18,20 @@ using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play.HUD;
-using osu.Game.Tests.Visual.Online;
+using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Tests.Visual.Spectator;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiplayerGameplayLeaderboard : MultiplayerTestScene
{
- private const int users = 16;
+ private static IEnumerable users => Enumerable.Range(0, 16);
- [Cached(typeof(SpectatorClient))]
- private TestMultiplayerSpectatorClient spectatorClient = new TestMultiplayerSpectatorClient();
-
- [Cached(typeof(UserLookupCache))]
- private UserLookupCache lookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache();
+ public new TestMultiplayerSpectatorClient SpectatorClient => (TestMultiplayerSpectatorClient)OnlinePlayDependencies?.SpectatorClient;
private MultiplayerGameplayLeaderboard leaderboard;
-
- protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
-
private OsuConfigManager config;
- public TestSceneMultiplayerGameplayLeaderboard()
- {
- base.Content.Children = new Drawable[]
- {
- spectatorClient,
- lookupCache,
- Content
- };
- }
-
[BackgroundDependencyLoader]
private void load()
{
@@ -59,7 +41,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[SetUpSteps]
public override void SetUpSteps()
{
- AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = lookupCache.GetUserAsync(1).Result);
+ AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result);
AddStep("create leaderboard", () =>
{
@@ -70,14 +52,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
- for (int i = 0; i < users; i++)
- spectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
+ foreach (var user in users)
+ SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
- spectatorClient.Schedule(() =>
- {
- Client.CurrentMatchPlayingUserIds.Clear();
- Client.CurrentMatchPlayingUserIds.AddRange(spectatorClient.PlayingUsers);
- });
+ // Todo: This is REALLY bad.
+ Client.CurrentMatchPlayingUserIds.AddRange(users);
Children = new Drawable[]
{
@@ -86,7 +65,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
scoreProcessor.ApplyBeatmap(playable);
- LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, spectatorClient.PlayingUsers.ToArray())
+ LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, users.ToArray())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -100,24 +79,32 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestScoreUpdates()
{
- AddRepeatStep("update state", () => spectatorClient.RandomlyUpdateState(), 100);
+ AddRepeatStep("update state", () => SpectatorClient.RandomlyUpdateState(), 100);
AddToggleStep("switch compact mode", expanded => leaderboard.Expanded.Value = expanded);
}
[Test]
public void TestUserQuit()
{
- AddRepeatStep("mark user quit", () => Client.CurrentMatchPlayingUserIds.RemoveAt(0), users);
+ foreach (var user in users)
+ AddStep($"mark user {user} quit", () => Client.RemoveUser(LookupCache.GetUserAsync(user).Result.AsNonNull()));
}
[Test]
public void TestChangeScoringMode()
{
- AddRepeatStep("update state", () => spectatorClient.RandomlyUpdateState(), 5);
+ AddRepeatStep("update state", () => SpectatorClient.RandomlyUpdateState(), 5);
AddStep("change to classic", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Classic));
AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised));
}
+ protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies();
+
+ protected class TestDependencies : MultiplayerTestSceneDependencies
+ {
+ protected override TestSpectatorClient CreateSpectatorClient() => new TestMultiplayerSpectatorClient();
+ }
+
public class TestMultiplayerSpectatorClient : TestSpectatorClient
{
private readonly Dictionary lastHeaders = new Dictionary();
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs
index 6b03b53b4b..4e08ffef17 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs
@@ -1,7 +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 osu.Framework.Allocation;
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
@@ -10,18 +10,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiplayerMatchFooter : MultiplayerTestScene
{
- [Cached]
- private readonly OnlinePlayBeatmapAvailabilityTracker availablilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
-
- [BackgroundDependencyLoader]
- private void load()
+ [SetUp]
+ public new void Setup() => Schedule(() =>
{
+ SelectedRoom.Value = new Room();
+
Child = new MultiplayerMatchFooter
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Height = 50
};
- }
+ });
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
index 5b059c06f5..8bcb9cebbc 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
@@ -29,7 +29,7 @@ using osu.Game.Screens.Select;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestSceneMultiplayerMatchSongSelect : RoomTestScene
+ public class TestSceneMultiplayerMatchSongSelect : MultiplayerTestScene
{
private BeatmapManager manager;
private RulesetStore rulesets;
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
index e8ebc0c426..955be6ca21 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
@@ -49,13 +49,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
[SetUp]
public new void Setup() => Schedule(() =>
{
- Room.Name.Value = "Test Room";
+ SelectedRoom.Value = new Room { Name = { Value = "Test Room" } };
});
[SetUpSteps]
public void SetupSteps()
{
- AddStep("load match", () => LoadScreen(screen = new MultiplayerMatchSubScreen(Room)));
+ AddStep("load match", () => LoadScreen(screen = new MultiplayerMatchSubScreen(SelectedRoom.Value)));
AddUntilStep("wait for load", () => screen.IsCurrentScreen());
}
@@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("set playlist", () =>
{
- Room.Playlist.Add(new PlaylistItem
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
@@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("set playlist", () =>
{
- Room.Playlist.Add(new PlaylistItem
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
@@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("set playlist", () =>
{
- Room.Playlist.Add(new PlaylistItem
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
index 7f8f04b718..6526f7eea7 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
@@ -22,8 +22,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiplayerParticipantsList : MultiplayerTestScene
{
- [SetUp]
- public new void Setup() => Schedule(createNewParticipantsList);
+ [SetUpSteps]
+ public void SetupSteps()
+ {
+ createNewParticipantsList();
+ }
[Test]
public void TestAddUser()
@@ -88,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestCorrectInitialState()
{
AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0)));
- AddStep("recreate list", createNewParticipantsList);
+ createNewParticipantsList();
checkProgressBarVisibility(true);
}
@@ -233,7 +236,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void createNewParticipantsList()
{
- Child = new ParticipantsList { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, Size = new Vector2(380, 0.7f) };
+ ParticipantsList participantsList = null;
+
+ AddStep("create new list", () => Child = participantsList = new ParticipantsList
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Y,
+ Size = new Vector2(380, 0.7f)
+ });
+
+ AddUntilStep("wait for list to load", () => participantsList.IsLoaded);
}
private void checkProgressBarVisibility(bool visible) =>
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
index 929cd6ca80..4f2ca34fb0 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
@@ -27,7 +27,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
public class TestSceneMultiplayerReadyButton : MultiplayerTestScene
{
private MultiplayerReadyButton button;
- private OnlinePlayBeatmapAvailabilityTracker beatmapTracker;
private BeatmapSetInfo importedSet;
private readonly Bindable selectedItem = new Bindable();
@@ -43,18 +42,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
-
- Add(beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker
- {
- SelectedItem = { BindTarget = selectedItem }
- });
-
- Dependencies.Cache(beatmapTracker);
}
[SetUp]
public new void Setup() => Schedule(() =>
{
+ AvailabilityTracker.SelectedItem.BindTo(selectedItem);
+
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
selectedItem.Value = new PlaylistItem
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs
index c008771fd9..b17427a30b 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs
@@ -3,37 +3,38 @@
using System;
using NUnit.Framework;
-using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.Multiplayer
{
[HeadlessTest]
- public class TestSceneMultiplayerRoomManager : RoomTestScene
+ public class TestSceneMultiplayerRoomManager : MultiplayerTestScene
{
- private TestMultiplayerRoomContainer roomContainer;
- private TestMultiplayerRoomManager roomManager => roomContainer.RoomManager;
+ protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies();
+
+ public TestSceneMultiplayerRoomManager()
+ : base(false)
+ {
+ }
[Test]
public void TestPollsInitially()
{
AddStep("create room manager with a few rooms", () =>
{
- createRoomManager().With(d => d.OnLoadComplete += _ =>
- {
- roomManager.CreateRoom(createRoom(r => r.Name.Value = "1"));
- roomManager.PartRoom();
- roomManager.CreateRoom(createRoom(r => r.Name.Value = "2"));
- roomManager.PartRoom();
- roomManager.ClearRooms();
- });
+ RoomManager.CreateRoom(createRoom(r => r.Name.Value = "1"));
+ RoomManager.PartRoom();
+ RoomManager.CreateRoom(createRoom(r => r.Name.Value = "2"));
+ RoomManager.PartRoom();
+ RoomManager.ClearRooms();
});
- AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2);
- AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value);
+ AddAssert("manager polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 2);
+ AddAssert("initial rooms received", () => RoomManager.InitialRoomsReceived.Value);
}
[Test]
@@ -41,19 +42,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room manager with a few rooms", () =>
{
- createRoomManager().With(d => d.OnLoadComplete += _ =>
- {
- roomManager.CreateRoom(createRoom());
- roomManager.PartRoom();
- roomManager.CreateRoom(createRoom());
- roomManager.PartRoom();
- });
+ RoomManager.CreateRoom(createRoom());
+ RoomManager.PartRoom();
+ RoomManager.CreateRoom(createRoom());
+ RoomManager.PartRoom();
});
- AddStep("disconnect", () => roomContainer.Client.Disconnect());
+ AddStep("disconnect", () => Client.Disconnect());
- AddAssert("rooms cleared", () => ((RoomManager)roomManager).Rooms.Count == 0);
- AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value);
+ AddAssert("rooms cleared", () => ((RoomManager)RoomManager).Rooms.Count == 0);
+ AddAssert("initial rooms not received", () => !RoomManager.InitialRoomsReceived.Value);
}
[Test]
@@ -61,20 +59,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room manager with a few rooms", () =>
{
- createRoomManager().With(d => d.OnLoadComplete += _ =>
- {
- roomManager.CreateRoom(createRoom());
- roomManager.PartRoom();
- roomManager.CreateRoom(createRoom());
- roomManager.PartRoom();
- });
+ RoomManager.CreateRoom(createRoom());
+ RoomManager.PartRoom();
+ RoomManager.CreateRoom(createRoom());
+ RoomManager.PartRoom();
});
- AddStep("disconnect", () => roomContainer.Client.Disconnect());
- AddStep("connect", () => roomContainer.Client.Connect());
+ AddStep("disconnect", () => Client.Disconnect());
+ AddStep("connect", () => Client.Connect());
- AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2);
- AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value);
+ AddAssert("manager polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 2);
+ AddAssert("initial rooms received", () => RoomManager.InitialRoomsReceived.Value);
}
[Test]
@@ -82,15 +77,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room manager with a room", () =>
{
- createRoomManager().With(d => d.OnLoadComplete += _ =>
- {
- roomManager.CreateRoom(createRoom());
- roomManager.ClearRooms();
- });
+ RoomManager.CreateRoom(createRoom());
+ RoomManager.ClearRooms();
});
- AddAssert("manager not polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 0);
- AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value);
+ AddAssert("manager not polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 0);
+ AddAssert("initial rooms not received", () => !RoomManager.InitialRoomsReceived.Value);
}
[Test]
@@ -98,13 +90,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room manager with a room", () =>
{
- createRoomManager().With(d => d.OnLoadComplete += _ =>
- {
- roomManager.CreateRoom(createRoom());
- });
+ RoomManager.CreateRoom(createRoom());
});
- AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null);
+ AddUntilStep("multiplayer room joined", () => Client.Room != null);
}
[Test]
@@ -112,14 +101,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room manager with a room", () =>
{
- createRoomManager().With(d => d.OnLoadComplete += _ =>
- {
- roomManager.CreateRoom(createRoom());
- roomManager.PartRoom();
- });
+ RoomManager.CreateRoom(createRoom());
+ RoomManager.PartRoom();
});
- AddAssert("multiplayer room parted", () => roomContainer.Client.Room == null);
+ AddAssert("multiplayer room parted", () => Client.Room == null);
}
[Test]
@@ -127,16 +113,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room manager with a room", () =>
{
- createRoomManager().With(d => d.OnLoadComplete += _ =>
- {
- var r = createRoom();
- roomManager.CreateRoom(r);
- roomManager.PartRoom();
- roomManager.JoinRoom(r);
- });
+ var r = createRoom();
+ RoomManager.CreateRoom(r);
+ RoomManager.PartRoom();
+ RoomManager.JoinRoom(r);
});
- AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null);
+ AddUntilStep("multiplayer room joined", () => Client.Room != null);
}
private Room createRoom(Action initFunc = null)
@@ -161,18 +144,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
return room;
}
- private TestMultiplayerRoomManager createRoomManager()
+ private class TestDependencies : MultiplayerTestSceneDependencies
{
- Child = roomContainer = new TestMultiplayerRoomContainer
+ public TestDependencies()
{
- RoomManager =
- {
- TimeBetweenListingPolls = { Value = 1 },
- TimeBetweenSelectionPolls = { Value = 1 }
- }
- };
-
- return roomManager;
+ // Need to set these values as early as possible.
+ RoomManager.TimeBetweenListingPolls.Value = 1;
+ RoomManager.TimeBetweenSelectionPolls.Value = 1;
+ }
}
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs
index d00404102c..070158f552 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs
@@ -37,40 +37,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
private IDisposable readyClickOperation;
- protected override Container Content => content;
- private readonly Container content;
-
- public TestSceneMultiplayerSpectateButton()
- {
- base.Content.Add(content = new Container
- {
- RelativeSizeAxes = Axes.Both
- });
- }
-
- protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
- {
- var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
-
- return dependencies;
- }
-
[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));
-
- var beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = selectedItem } };
- base.Content.Add(beatmapTracker);
- Dependencies.Cache(beatmapTracker);
-
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
}
[SetUp]
public new void Setup() => Schedule(() =>
{
+ AvailabilityTracker.SelectedItem.BindTo(selectedItem);
+
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
selectedItem.Value = new PlaylistItem
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
index d95a95ebe5..e4bf9b36ed 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
@@ -14,16 +14,18 @@ using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
+using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Playlists;
+using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestScenePlaylistsSongSelect : RoomTestScene
+ public class TestScenePlaylistsSongSelect : OnlinePlayTestScene
{
[Resolved]
private BeatmapManager beatmapManager { get; set; }
@@ -85,6 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("reset", () =>
{
+ SelectedRoom.Value = new Room();
Ruleset.Value = new OsuRuleset().RulesetInfo;
Beatmap.SetDefault();
SelectedMods.Value = Array.Empty();
@@ -98,14 +101,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestItemAddedIfEmptyOnStart()
{
AddStep("finalise selection", () => songSelect.FinaliseSelection());
- AddAssert("playlist has 1 item", () => Room.Playlist.Count == 1);
+ AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1);
}
[Test]
public void TestItemAddedWhenCreateNewItemClicked()
{
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
- AddAssert("playlist has 1 item", () => Room.Playlist.Count == 1);
+ AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1);
}
[Test]
@@ -113,7 +116,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("finalise selection", () => songSelect.FinaliseSelection());
- AddAssert("playlist has 1 item", () => Room.Playlist.Count == 1);
+ AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1);
}
[Test]
@@ -121,7 +124,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
- AddAssert("playlist has 2 items", () => Room.Playlist.Count == 2);
+ AddAssert("playlist has 2 items", () => SelectedRoom.Value.Playlist.Count == 2);
}
[Test]
@@ -131,13 +134,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("rearrange", () =>
{
- var item = Room.Playlist[0];
- Room.Playlist.RemoveAt(0);
- Room.Playlist.Add(item);
+ var item = SelectedRoom.Value.Playlist[0];
+ SelectedRoom.Value.Playlist.RemoveAt(0);
+ SelectedRoom.Value.Playlist.Add(item);
});
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
- AddAssert("new item has id 2", () => Room.Playlist.Last().ID == 2);
+ AddAssert("new item has id 2", () => SelectedRoom.Value.Playlist.Last().ID == 2);
}
///
@@ -151,8 +154,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("change mod rate", () => ((OsuModDoubleTime)SelectedMods.Value[0]).SpeedChange.Value = 2);
AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem());
- AddAssert("item 1 has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value));
- AddAssert("item 2 has rate 2", () => Precision.AlmostEquals(2, ((OsuModDoubleTime)Room.Playlist.Last().RequiredMods[0]).SpeedChange.Value));
+ AddAssert("item 1 has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0]).SpeedChange.Value));
+ AddAssert("item 2 has rate 2", () => Precision.AlmostEquals(2, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.Last().RequiredMods[0]).SpeedChange.Value));
}
///
@@ -174,7 +177,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2);
- AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value));
+ AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0]).SpeedChange.Value));
}
private class TestPlaylistsSongSelect : PlaylistsSongSelect
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs
index 156d6b744e..5bfb676f81 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@@ -14,6 +15,8 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapListing;
using osu.Game.Rulesets;
+using osu.Game.Scoring;
+using osu.Game.Users;
namespace osu.Game.Tests.Visual.Online
{
@@ -23,6 +26,8 @@ namespace osu.Game.Tests.Visual.Online
private BeatmapListingOverlay overlay;
+ private BeatmapListingSearchControl searchControl => overlay.ChildrenOfType().Single();
+
[BackgroundDependencyLoader]
private void load()
{
@@ -39,6 +44,16 @@ namespace osu.Game.Tests.Visual.Online
return true;
};
+
+ AddStep("initialize dummy", () =>
+ {
+ // non-supporter user
+ ((DummyAPIAccess)API).LocalUser.Value = new User
+ {
+ Username = "TestBot",
+ Id = API.LocalUser.Value.Id + 1,
+ };
+ });
}
[Test]
@@ -58,13 +73,164 @@ namespace osu.Game.Tests.Visual.Online
AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true);
}
+ [Test]
+ public void TestUserWithoutSupporterUsesSupporterOnlyFiltersWithoutResults()
+ {
+ AddStep("fetch for 0 beatmaps", () => fetchFor());
+ AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false);
+
+ // only Rank Achieved filter
+ setRankAchievedFilter(new[] { ScoreRank.XH });
+ supporterRequiredPlaceholderShown();
+
+ setRankAchievedFilter(Array.Empty());
+ notFoundPlaceholderShown();
+
+ // only Played filter
+ setPlayedFilter(SearchPlayed.Played);
+ supporterRequiredPlaceholderShown();
+
+ setPlayedFilter(SearchPlayed.Any);
+ notFoundPlaceholderShown();
+
+ // both RankAchieved and Played filters
+ setRankAchievedFilter(new[] { ScoreRank.XH });
+ setPlayedFilter(SearchPlayed.Played);
+ supporterRequiredPlaceholderShown();
+
+ setRankAchievedFilter(Array.Empty());
+ setPlayedFilter(SearchPlayed.Any);
+ notFoundPlaceholderShown();
+ }
+
+ [Test]
+ public void TestUserWithSupporterUsesSupporterOnlyFiltersWithoutResults()
+ {
+ AddStep("fetch for 0 beatmaps", () => fetchFor());
+ AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true);
+
+ // only Rank Achieved filter
+ setRankAchievedFilter(new[] { ScoreRank.XH });
+ notFoundPlaceholderShown();
+
+ setRankAchievedFilter(Array.Empty());
+ notFoundPlaceholderShown();
+
+ // only Played filter
+ setPlayedFilter(SearchPlayed.Played);
+ notFoundPlaceholderShown();
+
+ setPlayedFilter(SearchPlayed.Any);
+ notFoundPlaceholderShown();
+
+ // both Rank Achieved and Played filters
+ setRankAchievedFilter(new[] { ScoreRank.XH });
+ setPlayedFilter(SearchPlayed.Played);
+ notFoundPlaceholderShown();
+
+ setRankAchievedFilter(Array.Empty());
+ setPlayedFilter(SearchPlayed.Any);
+ notFoundPlaceholderShown();
+ }
+
+ [Test]
+ public void TestUserWithoutSupporterUsesSupporterOnlyFiltersWithResults()
+ {
+ AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet));
+ AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false);
+
+ // only Rank Achieved filter
+ setRankAchievedFilter(new[] { ScoreRank.XH });
+ supporterRequiredPlaceholderShown();
+
+ setRankAchievedFilter(Array.Empty());
+ noPlaceholderShown();
+
+ // only Played filter
+ setPlayedFilter(SearchPlayed.Played);
+ supporterRequiredPlaceholderShown();
+
+ setPlayedFilter(SearchPlayed.Any);
+ noPlaceholderShown();
+
+ // both Rank Achieved and Played filters
+ setRankAchievedFilter(new[] { ScoreRank.XH });
+ setPlayedFilter(SearchPlayed.Played);
+ supporterRequiredPlaceholderShown();
+
+ setRankAchievedFilter(Array.Empty());
+ setPlayedFilter(SearchPlayed.Any);
+ noPlaceholderShown();
+ }
+
+ [Test]
+ public void TestUserWithSupporterUsesSupporterOnlyFiltersWithResults()
+ {
+ AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet));
+ AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true);
+
+ // only Rank Achieved filter
+ setRankAchievedFilter(new[] { ScoreRank.XH });
+ noPlaceholderShown();
+
+ setRankAchievedFilter(Array.Empty());
+ noPlaceholderShown();
+
+ // only Played filter
+ setPlayedFilter(SearchPlayed.Played);
+ noPlaceholderShown();
+
+ setPlayedFilter(SearchPlayed.Any);
+ noPlaceholderShown();
+
+ // both Rank Achieved and Played filters
+ setRankAchievedFilter(new[] { ScoreRank.XH });
+ setPlayedFilter(SearchPlayed.Played);
+ noPlaceholderShown();
+
+ setRankAchievedFilter(Array.Empty());
+ setPlayedFilter(SearchPlayed.Any);
+ noPlaceholderShown();
+ }
+
private void fetchFor(params BeatmapSetInfo[] beatmaps)
{
setsForResponse.Clear();
setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b)));
// trigger arbitrary change for fetching.
- overlay.ChildrenOfType().Single().Query.TriggerChange();
+ searchControl.Query.TriggerChange();
+ }
+
+ private void setRankAchievedFilter(ScoreRank[] ranks)
+ {
+ AddStep($"set Rank Achieved filter to [{string.Join(',', ranks)}]", () =>
+ {
+ searchControl.Ranks.Clear();
+ searchControl.Ranks.AddRange(ranks);
+ });
+ }
+
+ private void setPlayedFilter(SearchPlayed played)
+ {
+ AddStep($"set Played filter to {played}", () => searchControl.Played.Value = played);
+ }
+
+ private void supporterRequiredPlaceholderShown()
+ {
+ AddUntilStep("\"supporter required\" placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true);
+ }
+
+ private void notFoundPlaceholderShown()
+ {
+ AddUntilStep("\"no maps found\" placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true);
+ }
+
+ private void noPlaceholderShown()
+ {
+ AddUntilStep("no placeholder shown", () =>
+ !overlay.ChildrenOfType().Any()
+ && !overlay.ChildrenOfType().Any());
}
private class TestAPIBeatmapSet : APIBeatmapSet
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
index 3971146ff8..a1549dfbce 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
@@ -82,7 +82,8 @@ namespace osu.Game.Tests.Visual.Online
{
switch (req)
{
- case JoinChannelRequest _:
+ case JoinChannelRequest joinChannel:
+ joinChannel.TriggerSuccess();
return true;
}
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
index 618447eae2..b16b61c5c7 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
@@ -3,25 +3,21 @@
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Graphics.Containers;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Playlists;
-using osu.Game.Tests.Visual.Multiplayer;
+using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.Playlists
{
- public class TestScenePlaylistsLoungeSubScreen : RoomManagerTestScene
+ public class TestScenePlaylistsLoungeSubScreen : OnlinePlayTestScene
{
- private LoungeSubScreen loungeScreen;
+ protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager;
- [BackgroundDependencyLoader]
- private void load()
- {
- }
+ private LoungeSubScreen loungeScreen;
public override void SetUpSteps()
{
@@ -37,7 +33,7 @@ namespace osu.Game.Tests.Visual.Playlists
[Test]
public void TestScrollSelectedIntoView()
{
- AddRooms(30);
+ AddStep("add rooms", () => RoomManager.AddRooms(30));
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms.First()));
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs
index 44a79b6598..a320cb240f 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs
@@ -3,7 +3,6 @@
using System;
using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -12,26 +11,28 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Playlists;
+using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.Playlists
{
- public class TestScenePlaylistsMatchSettingsOverlay : RoomTestScene
+ public class TestScenePlaylistsMatchSettingsOverlay : OnlinePlayTestScene
{
- [Cached(Type = typeof(IRoomManager))]
- private TestRoomManager roomManager = new TestRoomManager();
+ protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
private TestRoomSettings settings;
+ protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies();
+
[SetUp]
public new void Setup() => Schedule(() =>
{
- settings = new TestRoomSettings
+ SelectedRoom.Value = new Room();
+
+ Child = settings = new TestRoomSettings
{
RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible }
};
-
- Child = settings;
});
[Test]
@@ -39,19 +40,19 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddStep("clear name and beatmap", () =>
{
- Room.Name.Value = "";
- Room.Playlist.Clear();
+ SelectedRoom.Value.Name.Value = "";
+ SelectedRoom.Value.Playlist.Clear();
});
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
- AddStep("set name", () => Room.Name.Value = "Room name");
+ AddStep("set name", () => SelectedRoom.Value.Name.Value = "Room name");
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
- AddStep("set beatmap", () => Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }));
+ AddStep("set beatmap", () => SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }));
AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value);
- AddStep("clear name", () => Room.Name.Value = "");
+ AddStep("clear name", () => SelectedRoom.Value.Name.Value = "");
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
}
@@ -67,9 +68,9 @@ namespace osu.Game.Tests.Visual.Playlists
{
settings.NameField.Current.Value = expected_name;
settings.DurationField.Current.Value = expectedDuration;
- Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
- roomManager.CreateRequested = r =>
+ RoomManager.CreateRequested = r =>
{
createdRoom = r;
return true;
@@ -88,11 +89,11 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("setup", () =>
{
- Room.Name.Value = "Test Room";
- Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
+ SelectedRoom.Value.Name.Value = "Test Room";
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
fail = true;
- roomManager.CreateRequested = _ => !fail;
+ RoomManager.CreateRequested = _ => !fail;
});
AddAssert("error not displayed", () => !settings.ErrorText.IsPresent);
@@ -119,7 +120,12 @@ namespace osu.Game.Tests.Visual.Playlists
public OsuSpriteText ErrorText => ((MatchSettings)Settings).ErrorText;
}
- private class TestRoomManager : IRoomManager
+ private class TestDependencies : OnlinePlayTestSceneDependencies
+ {
+ protected override IRoomManager CreateRoomManager() => new TestRoomManager();
+ }
+
+ protected class TestRoomManager : IRoomManager
{
public const string FAILED_TEXT = "failed";
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs
index 255f147ec9..76a78c0a3c 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs
@@ -3,21 +3,23 @@
using NUnit.Framework;
using osu.Framework.Graphics;
+using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
+using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Playlists
{
- public class TestScenePlaylistsParticipantsList : RoomTestScene
+ public class TestScenePlaylistsParticipantsList : OnlinePlayTestScene
{
[SetUp]
public new void Setup() => Schedule(() =>
{
- Room.RoomID.Value = 7;
+ SelectedRoom.Value = new Room { RoomID = { Value = 7 } };
for (int i = 0; i < 50; i++)
{
- Room.RecentParticipants.Add(new User
+ SelectedRoom.Value.RecentParticipants.Add(new User
{
Username = "peppy",
Statistics = new UserStatistics { GlobalRank = 1234 },
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs
index a08a91314b..f2bfb80beb 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs
@@ -15,20 +15,17 @@ using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
-using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Users;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Playlists
{
- public class TestScenePlaylistsRoomSubScreen : RoomTestScene
+ public class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene
{
- [Cached(typeof(IRoomManager))]
- private readonly TestRoomManager roomManager = new TestRoomManager();
-
private BeatmapManager manager;
private RulesetStore rulesets;
@@ -40,8 +37,6 @@ namespace osu.Game.Tests.Visual.Playlists
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
- manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait();
-
((DummyAPIAccess)API).HandleRequest = req =>
{
switch (req)
@@ -58,7 +53,9 @@ namespace osu.Game.Tests.Visual.Playlists
[SetUpSteps]
public void SetupSteps()
{
- AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(Room)));
+ AddStep("set room", () => SelectedRoom.Value = new Room());
+ AddStep("ensure has beatmap", () => manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait());
+ AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom.Value)));
AddUntilStep("wait for load", () => match.IsCurrentScreen());
}
@@ -67,12 +64,12 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddStep("set room properties", () =>
{
- Room.RoomID.Value = 1;
- Room.Name.Value = "my awesome room";
- Room.Host.Value = new User { Id = 2, Username = "peppy" };
- Room.RecentParticipants.Add(Room.Host.Value);
- Room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5);
- Room.Playlist.Add(new PlaylistItem
+ SelectedRoom.Value.RoomID.Value = 1;
+ SelectedRoom.Value.Name.Value = "my awesome room";
+ SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" };
+ SelectedRoom.Value.RecentParticipants.Add(SelectedRoom.Value.Host.Value);
+ SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMinutes(5);
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo }
@@ -88,9 +85,9 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddStep("set room properties", () =>
{
- Room.Name.Value = "my awesome room";
- Room.Host.Value = new User { Id = 2, Username = "peppy" };
- Room.Playlist.Add(new PlaylistItem
+ SelectedRoom.Value.Name.Value = "my awesome room";
+ SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" };
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo }
@@ -104,17 +101,34 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("click", () => InputManager.Click(MouseButton.Left));
- AddAssert("first playlist item selected", () => match.SelectedItem.Value == Room.Playlist[0]);
+ AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]);
}
[Test]
public void TestBeatmapUpdatedOnReImport()
{
BeatmapSetInfo importedSet = null;
+ TestBeatmap beatmap = null;
+
+ // this step is required to make sure the further imports actually get online IDs.
+ // all the playlist logic relies on online ID matching.
+ AddStep("remove all matching online IDs", () =>
+ {
+ beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo);
+
+ var existing = manager.QueryBeatmapSets(s => s.OnlineBeatmapSetID == beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID).ToList();
+
+ foreach (var s in existing)
+ {
+ s.OnlineBeatmapSetID = null;
+ foreach (var b in s.Beatmaps)
+ b.OnlineBeatmapID = null;
+ manager.Update(s);
+ }
+ });
AddStep("import altered beatmap", () =>
{
- var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo);
beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1;
importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result;
@@ -122,9 +136,9 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("load room", () =>
{
- Room.Name.Value = "my awesome room";
- Room.Host.Value = new User { Id = 2, Username = "peppy" };
- Room.Playlist.Add(new PlaylistItem
+ SelectedRoom.Value.Name.Value = "my awesome room";
+ SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" };
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = importedSet.Beatmaps[0] },
Ruleset = { Value = new OsuRuleset().RulesetInfo }
@@ -155,30 +169,5 @@ namespace osu.Game.Tests.Visual.Playlists
{
}
}
-
- private class TestRoomManager : IRoomManager
- {
- public event Action RoomsUpdated
- {
- add => throw new NotImplementedException();
- remove => throw new NotImplementedException();
- }
-
- public IBindable InitialRoomsReceived { get; } = new Bindable(true);
-
- public IBindableList Rooms { get; } = new BindableList();
-
- public void CreateRoom(Room room, Action onSuccess = null, Action onError = null)
- {
- room.RoomID.Value = 1;
- onSuccess?.Invoke(room);
- }
-
- public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room);
-
- public void PartRoom()
- {
- }
- }
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs
index c5374d50ab..096bccae9e 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osuTK;
@@ -59,7 +60,7 @@ namespace osu.Game.Tests.Visual.UserInterface
private class Icon : Container, IHasTooltip
{
- public string TooltipText { get; }
+ public LocalisableString TooltipText { get; }
public SpriteIcon SpriteIcon { get; }
diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs
index 46c3b8bc3b..61f8511e3c 100644
--- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs
+++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs
@@ -149,7 +149,8 @@ namespace osu.Game.Tournament.Tests.NonVisual
private TournamentGameBase loadOsu(GameHost host)
{
var osu = new TournamentGameBase();
- Task.Run(() => host.Run(osu));
+ Task.Run(() => host.Run(osu))
+ .ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted);
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
return osu;
}
diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs
index 4c5f5a7a1a..e4eb5a36fb 100644
--- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs
+++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs
@@ -55,7 +55,8 @@ namespace osu.Game.Tournament.Tests.NonVisual
private TournamentGameBase loadOsu(GameHost host)
{
var osu = new TournamentGameBase();
- Task.Run(() => host.Run(osu));
+ Task.Run(() => host.Run(osu))
+ .ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted);
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
return osu;
}
diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs
index 069ddfa4db..27ad6650d1 100644
--- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs
@@ -147,7 +147,7 @@ namespace osu.Game.Tournament.Screens.Editors
[Resolved]
protected IAPIProvider API { get; private set; }
- private readonly Bindable beatmapId = new Bindable();
+ private readonly Bindable beatmapId = new Bindable();
private readonly Bindable mods = new Bindable();
@@ -220,14 +220,12 @@ namespace osu.Game.Tournament.Screens.Editors
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
- beatmapId.Value = Model.ID.ToString();
- beatmapId.BindValueChanged(idString =>
+ beatmapId.Value = Model.ID;
+ beatmapId.BindValueChanged(id =>
{
- int.TryParse(idString.NewValue, out var parsed);
+ Model.ID = id.NewValue ?? 0;
- Model.ID = parsed;
-
- if (idString.NewValue != idString.OldValue)
+ if (id.NewValue != id.OldValue)
Model.BeatmapInfo = null;
if (Model.BeatmapInfo != null)
diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs
index 7bd8d3f6a0..6418bf97da 100644
--- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs
@@ -147,7 +147,7 @@ namespace osu.Game.Tournament.Screens.Editors
[Resolved]
protected IAPIProvider API { get; private set; }
- private readonly Bindable beatmapId = new Bindable();
+ private readonly Bindable beatmapId = new Bindable();
private readonly Bindable score = new Bindable();
@@ -228,16 +228,12 @@ namespace osu.Game.Tournament.Screens.Editors
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
- beatmapId.Value = Model.ID.ToString();
- beatmapId.BindValueChanged(idString =>
+ beatmapId.Value = Model.ID;
+ beatmapId.BindValueChanged(id =>
{
- int parsed;
+ Model.ID = id.NewValue ?? 0;
- int.TryParse(idString.NewValue, out parsed);
-
- Model.ID = parsed;
-
- if (idString.NewValue != idString.OldValue)
+ if (id.NewValue != id.OldValue)
Model.BeatmapInfo = null;
if (Model.BeatmapInfo != null)
diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs
index aa1be143ea..0d2e64f300 100644
--- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs
@@ -214,7 +214,7 @@ namespace osu.Game.Tournament.Screens.Editors
[Resolved]
private TournamentGameBase game { get; set; }
- private readonly Bindable userId = new Bindable();
+ private readonly Bindable userId = new Bindable();
private readonly Container drawableContainer;
@@ -278,14 +278,12 @@ namespace osu.Game.Tournament.Screens.Editors
[BackgroundDependencyLoader]
private void load()
{
- userId.Value = user.Id.ToString();
- userId.BindValueChanged(idString =>
+ userId.Value = user.Id;
+ userId.BindValueChanged(id =>
{
- int.TryParse(idString.NewValue, out var parsed);
+ user.Id = id.NewValue ?? 0;
- user.Id = parsed;
-
- if (idString.NewValue != idString.OldValue)
+ if (id.NewValue != id.OldValue)
user.Username = string.Empty;
if (!string.IsNullOrEmpty(user.Username))
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 00af06703d..14bddb6319 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -181,8 +181,13 @@ namespace osu.Game.Beatmaps
if (existingOnlineId != null)
{
Delete(existingOnlineId);
- beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID);
- LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been purged.");
+
+ // in order to avoid a unique key constraint, immediately remove the online ID from the previous set.
+ existingOnlineId.OnlineBeatmapSetID = null;
+ foreach (var b in existingOnlineId.Beatmaps)
+ b.OnlineBeatmapID = null;
+
+ LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been deleted.");
}
}
}
@@ -191,8 +196,6 @@ namespace osu.Game.Beatmaps
{
var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList();
- LogForModel(beatmapSet, $"Validating online IDs for {beatmapSet.Beatmaps.Count} beatmaps...");
-
// ensure all IDs are unique
if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1))
{
@@ -319,6 +322,14 @@ namespace osu.Game.Beatmaps
/// The first result for the provided query, or null if no results were found.
public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query);
+ protected override bool CanSkipImport(BeatmapSetInfo existing, BeatmapSetInfo import)
+ {
+ if (!base.CanReuseExisting(existing, import))
+ return false;
+
+ return existing.Beatmaps.Any(b => b.OnlineBeatmapID != null);
+ }
+
protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import)
{
if (!base.CanReuseExisting(existing, import))
diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs
index 5dff4fe282..7824205257 100644
--- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs
@@ -48,7 +48,6 @@ namespace osu.Game.Beatmaps
public Task UpdateAsync(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken)
{
- LogForModel(beatmapSet, "Performing online lookups...");
return Task.WhenAll(beatmapSet.Beatmaps.Select(b => UpdateAsync(beatmapSet, b, cancellationToken)).ToArray());
}
diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs
index 55636495df..f373e59417 100644
--- a/osu.Game/Configuration/SettingSourceAttribute.cs
+++ b/osu.Game/Configuration/SettingSourceAttribute.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Configuration
{
public LocalisableString Label { get; }
- public string Description { get; }
+ public LocalisableString Description { get; }
public int? OrderPosition { get; }
diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index 8efd451857..f72a43fa01 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -78,7 +78,7 @@ namespace osu.Game.Database
private readonly Bindable> itemRemoved = new Bindable>();
- public virtual IEnumerable HandledExtensions => new[] { ".zip" };
+ public virtual IEnumerable HandledExtensions => new[] { @".zip" };
protected readonly FileStore Files;
@@ -99,7 +99,7 @@ namespace osu.Game.Database
ModelStore.ItemUpdated += item => handleEvent(() => itemUpdated.Value = new WeakReference(item));
ModelStore.ItemRemoved += item => handleEvent(() => itemRemoved.Value = new WeakReference(item));
- exportStorage = storage.GetStorageForDirectory("exports");
+ exportStorage = storage.GetStorageForDirectory(@"exports");
Files = new FileStore(contextFactory, storage);
@@ -282,7 +282,7 @@ namespace osu.Game.Database
}
catch (Exception e)
{
- LogForModel(model, $"Model creation of {archive.Name} failed.", e);
+ LogForModel(model, @$"Model creation of {archive.Name} failed.", e);
return null;
}
@@ -309,6 +309,12 @@ namespace osu.Game.Database
Logger.Log($"{prefix} {message}", LoggingTarget.Database);
}
+ ///
+ /// Whether the implementation overrides with a custom implementation.
+ /// Custom hash implementations must bypass the early exit in the import flow (see usage).
+ ///
+ protected virtual bool HasCustomHashFunction => false;
+
///
/// Create a SHA-2 hash from the provided archive based on file content of all files matching .
///
@@ -317,7 +323,11 @@ namespace osu.Game.Database
///
protected virtual string ComputeHash(TModel item, ArchiveReader reader = null)
{
- // for now, concatenate all .osu files in the set to create a unique hash.
+ if (reader != null)
+ // fast hashing for cases where the item's files may not be populated.
+ return computeHashFast(reader);
+
+ // for now, concatenate all hashable files in the set to create a unique hash.
MemoryStream hashable = new MemoryStream();
foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(ext => f.Filename.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f.Filename))
@@ -329,9 +339,6 @@ namespace osu.Game.Database
if (hashable.Length > 0)
return hashable.ComputeSHA2Hash();
- if (reader != null)
- return reader.Name.ComputeSHA2Hash();
-
return item.Hash;
}
@@ -348,19 +355,48 @@ namespace osu.Game.Database
delayEvents();
+ bool checkedExisting = false;
+ TModel existing = null;
+
+ if (archive != null && !HasCustomHashFunction)
+ {
+ // this is a fast bail condition to improve large import performance.
+ item.Hash = computeHashFast(archive);
+
+ checkedExisting = true;
+ existing = CheckForExisting(item);
+
+ if (existing != null)
+ {
+ // bare minimum comparisons
+ //
+ // note that this should really be checking filesizes on disk (of existing files) for some degree of sanity.
+ // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files.
+ if (CanSkipImport(existing, item) &&
+ getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f)))
+ {
+ LogForModel(item, @$"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import.");
+ Undelete(existing);
+ return existing;
+ }
+
+ LogForModel(item, @"Found existing (optimised) but failed pre-check.");
+ }
+ }
+
void rollback()
{
if (!Delete(item))
{
// We may have not yet added the model to the underlying table, but should still clean up files.
- LogForModel(item, "Dereferencing files for incomplete import.");
+ LogForModel(item, @"Dereferencing files for incomplete import.");
Files.Dereference(item.Files.Select(f => f.FileInfo).ToArray());
}
}
try
{
- LogForModel(item, "Beginning import...");
+ LogForModel(item, @"Beginning import...");
item.Files = archive != null ? createFileInfos(archive, Files) : new List();
item.Hash = ComputeHash(item, archive);
@@ -371,22 +407,24 @@ namespace osu.Game.Database
{
try
{
- if (!write.IsTransactionLeader) throw new InvalidOperationException($"Ensure there is no parent transaction so errors can correctly be handled by {this}");
+ if (!write.IsTransactionLeader) throw new InvalidOperationException(@$"Ensure there is no parent transaction so errors can correctly be handled by {this}");
- var existing = CheckForExisting(item);
+ if (!checkedExisting)
+ existing = CheckForExisting(item);
if (existing != null)
{
if (CanReuseExisting(existing, item))
{
Undelete(existing);
- LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import.");
+ LogForModel(item, @$"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import.");
// existing item will be used; rollback new import and exit early.
rollback();
flushEvents(true);
return existing;
}
+ LogForModel(item, @"Found existing but failed re-use check.");
Delete(existing);
ModelStore.PurgeDeletable(s => s.ID == existing.ID);
}
@@ -403,12 +441,12 @@ namespace osu.Game.Database
}
}
- LogForModel(item, "Import successfully completed!");
+ LogForModel(item, @"Import successfully completed!");
}
catch (Exception e)
{
if (!(e is TaskCanceledException))
- LogForModel(item, "Database import or population failed and has been rolled back.", e);
+ LogForModel(item, @"Database import or population failed and has been rolled back.", e);
rollback();
flushEvents(false);
@@ -428,7 +466,7 @@ namespace osu.Game.Database
var retrievedItem = ModelStore.ConsumableItems.FirstOrDefault(s => s.ID == item.ID);
if (retrievedItem == null)
- throw new ArgumentException("Specified model could not be found", nameof(item));
+ throw new ArgumentException(@"Specified model could not be found", nameof(item));
using (var outputStream = exportStorage.GetStream($"{getValidFilename(item.ToString())}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create))
ExportModelTo(retrievedItem, outputStream);
@@ -637,6 +675,22 @@ namespace osu.Game.Database
}
}
+ private string computeHashFast(ArchiveReader reader)
+ {
+ MemoryStream hashable = new MemoryStream();
+
+ foreach (var file in reader.Filenames.Where(f => HashableFileTypes.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f))
+ {
+ using (Stream s = reader.GetStream(file))
+ s.CopyTo(hashable);
+ }
+
+ if (hashable.Length > 0)
+ return hashable.ComputeSHA2Hash();
+
+ return reader.Name.ComputeSHA2Hash();
+ }
+
///
/// Create all required s for the provided archive, adding them to the global file store.
///
@@ -644,18 +698,14 @@ namespace osu.Game.Database
{
var fileInfos = new List();
- string prefix = reader.Filenames.GetCommonPrefix();
- if (!(prefix.EndsWith('/') || prefix.EndsWith('\\')))
- prefix = string.Empty;
-
// import files to manager
- foreach (string file in reader.Filenames)
+ foreach (var filenames in getShortenedFilenames(reader))
{
- using (Stream s = reader.GetStream(file))
+ using (Stream s = reader.GetStream(filenames.original))
{
fileInfos.Add(new TFileModel
{
- Filename = file.Substring(prefix.Length).ToStandardisedPath(),
+ Filename = filenames.shortened,
FileInfo = files.Add(s)
});
}
@@ -664,6 +714,17 @@ namespace osu.Game.Database
return fileInfos;
}
+ private IEnumerable<(string original, string shortened)> getShortenedFilenames(ArchiveReader reader)
+ {
+ string prefix = reader.Filenames.GetCommonPrefix();
+ if (!(prefix.EndsWith('/') || prefix.EndsWith('\\')))
+ prefix = string.Empty;
+
+ // import files to manager
+ foreach (string file in reader.Filenames)
+ yield return (file, file.Substring(prefix.Length).ToStandardisedPath());
+ }
+
#region osu-stable import
///
@@ -696,7 +757,7 @@ namespace osu.Game.Database
{
string fullPath = storage.GetFullPath(ImportFromStablePath);
- Logger.Log($"Folder \"{fullPath}\" not available in the target osu!stable installation to import {HumanisedModelName}s.", LoggingTarget.Information, LogLevel.Error);
+ Logger.Log(@$"Folder ""{fullPath}"" not available in the target osu!stable installation to import {HumanisedModelName}s.", LoggingTarget.Information, LogLevel.Error);
return Task.CompletedTask;
}
@@ -727,7 +788,7 @@ namespace osu.Game.Database
/// The model to populate.
/// The archive to use as a reference for population. May be null.
/// An optional cancellation token.
- protected virtual Task Populate(TModel model, [CanBeNull] ArchiveReader archive, CancellationToken cancellationToken = default) => Task.CompletedTask;
+ protected abstract Task Populate(TModel model, [CanBeNull] ArchiveReader archive, CancellationToken cancellationToken = default);
///
/// Perform any final actions before the import to database executes.
@@ -744,6 +805,15 @@ namespace osu.Game.Database
/// An existing model which matches the criteria to skip importing, else null.
protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash);
+ ///
+ /// Whether inport can be skipped after finding an existing import early in the process.
+ /// Only valid when is not overridden.
+ ///
+ /// The existing model.
+ /// The newly imported model.
+ /// Whether to skip this import completely.
+ protected virtual bool CanSkipImport(TModel existing, TModel import) => true;
+
///
/// After an existing is found during an import process, the default behaviour is to use/restore the existing
/// item and skip the import. This method allows changing that behaviour.
@@ -771,7 +841,7 @@ namespace osu.Game.Database
private DbSet queryModel() => ContextFactory.Get().Set();
- protected virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace("Info", "").ToLower()}";
+ protected virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}";
#region Event handling / delaying
diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs
index ed5931dd2b..fb5e2faff8 100644
--- a/osu.Game/Database/RealmContextFactory.cs
+++ b/osu.Game/Database/RealmContextFactory.cs
@@ -26,6 +26,11 @@ namespace osu.Game.Database
///
private readonly object writeLock = new object();
+ ///
+ /// Lock object which is held during sections.
+ ///
+ private readonly SemaphoreSlim blockingLock = new SemaphoreSlim(1);
+
private static readonly GlobalStatistic reads = GlobalStatistics.Get("Realm", "Get (Read)");
private static readonly GlobalStatistic writes = GlobalStatistics.Get("Realm", "Get (Write)");
private static readonly GlobalStatistic refreshes = GlobalStatistics.Get("Realm", "Dirty Refreshes");
@@ -33,17 +38,12 @@ namespace osu.Game.Database
private static readonly GlobalStatistic pending_writes = GlobalStatistics.Get("Realm", "Pending writes");
private static readonly GlobalStatistic active_usages = GlobalStatistics.Get("Realm", "Active usages");
- private readonly ManualResetEventSlim blockingResetEvent = new ManualResetEventSlim(true);
-
private Realm context;
public Realm Context
{
get
{
- if (IsDisposed)
- throw new InvalidOperationException($"Attempted to access {nameof(Context)} on a disposed context factory");
-
if (context == null)
{
context = createContext();
@@ -64,7 +64,7 @@ namespace osu.Game.Database
public RealmUsage GetForRead()
{
reads.Value++;
- return new RealmUsage(this);
+ return new RealmUsage(createContext());
}
public RealmWriteUsage GetForWrite()
@@ -73,8 +73,34 @@ namespace osu.Game.Database
pending_writes.Value++;
Monitor.Enter(writeLock);
+ return new RealmWriteUsage(createContext(), writeComplete);
+ }
- return new RealmWriteUsage(this);
+ ///
+ /// Flush any active contexts and block any further writes.
+ ///
+ ///
+ /// This should be used in places we need to ensure no ongoing reads/writes are occurring with realm.
+ /// ie. to move the realm backing file to a new location.
+ ///
+ /// An which should be disposed to end the blocking section.
+ public IDisposable BlockAllOperations()
+ {
+ if (IsDisposed)
+ throw new ObjectDisposedException(nameof(RealmContextFactory));
+
+ Logger.Log(@"Blocking realm operations.", LoggingTarget.Database);
+
+ blockingLock.Wait();
+ flushContexts();
+
+ return new InvokeOnDisposal(this, endBlockingSection);
+
+ static void endBlockingSection(RealmContextFactory factory)
+ {
+ factory.blockingLock.Release();
+ Logger.Log(@"Restoring realm operations.", LoggingTarget.Database);
+ }
}
protected override void Update()
@@ -87,15 +113,31 @@ namespace osu.Game.Database
private Realm createContext()
{
- blockingResetEvent.Wait();
-
- contexts_created.Value++;
-
- return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true))
+ try
{
- SchemaVersion = schema_version,
- MigrationCallback = onMigration,
- });
+ if (IsDisposed)
+ throw new ObjectDisposedException(nameof(RealmContextFactory));
+
+ blockingLock.Wait();
+
+ contexts_created.Value++;
+
+ return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true))
+ {
+ SchemaVersion = schema_version,
+ MigrationCallback = onMigration,
+ });
+ }
+ finally
+ {
+ blockingLock.Release();
+ }
+ }
+
+ private void writeComplete()
+ {
+ Monitor.Exit(writeLock);
+ pending_writes.Value--;
}
private void onMigration(Migration migration, ulong lastSchemaVersion)
@@ -109,28 +151,10 @@ namespace osu.Game.Database
}
}
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
-
- BlockAllOperations();
- }
-
- public IDisposable BlockAllOperations()
- {
- blockingResetEvent.Reset();
- flushContexts();
-
- return new InvokeOnDisposal(this, r => endBlockingSection());
- }
-
- private void endBlockingSection()
- {
- blockingResetEvent.Set();
- }
-
private void flushContexts()
{
+ Logger.Log(@"Flushing realm contexts...", LoggingTarget.Database);
+
var previousContext = context;
context = null;
@@ -139,6 +163,20 @@ namespace osu.Game.Database
Thread.Sleep(50);
previousContext?.Dispose();
+
+ Logger.Log(@"Realm contexts flushed.", LoggingTarget.Database);
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ if (!IsDisposed)
+ {
+ // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal.
+ BlockAllOperations();
+ blockingLock?.Dispose();
+ }
+
+ base.Dispose(isDisposing);
}
///
@@ -148,13 +186,10 @@ namespace osu.Game.Database
{
public readonly Realm Realm;
- protected readonly RealmContextFactory Factory;
-
- internal RealmUsage(RealmContextFactory factory)
+ internal RealmUsage(Realm context)
{
active_usages.Value++;
- Factory = factory;
- Realm = factory.createContext();
+ Realm = context;
}
///
@@ -172,11 +207,13 @@ namespace osu.Game.Database
///
public class RealmWriteUsage : RealmUsage
{
+ private readonly Action onWriteComplete;
private readonly Transaction transaction;
- internal RealmWriteUsage(RealmContextFactory factory)
- : base(factory)
+ internal RealmWriteUsage(Realm context, Action onWriteComplete)
+ : base(context)
{
+ this.onWriteComplete = onWriteComplete;
transaction = Realm.BeginWrite();
}
@@ -200,8 +237,7 @@ namespace osu.Game.Database
base.Dispose();
- Monitor.Exit(Factory.writeLock);
- pending_writes.Value--;
+ onWriteComplete();
}
}
}
diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs
index 75c73af0ce..ce8a9c8f9f 100644
--- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs
+++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs
@@ -4,12 +4,13 @@
using Markdig.Syntax.Inlines;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Cursor;
+using osu.Framework.Localisation;
namespace osu.Game.Graphics.Containers.Markdown
{
public class OsuMarkdownImage : MarkdownImage, IHasTooltip
{
- public string TooltipText { get; }
+ public LocalisableString TooltipText { get; }
public OsuMarkdownImage(LinkInline linkInline)
: base(linkInline.Url)
diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs
index 60ded8952d..0bc3c876e1 100644
--- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs
@@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
+using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Graphics.Containers
@@ -24,7 +25,7 @@ namespace osu.Game.Graphics.Containers
this.sampleSet = sampleSet;
}
- public virtual string TooltipText { get; set; }
+ public virtual LocalisableString TooltipText { get; set; }
[BackgroundDependencyLoader]
private void load()
diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs
index 57f39bb8c7..81dca99ddd 100644
--- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs
+++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics.Cursor
@@ -32,7 +33,7 @@ namespace osu.Game.Graphics.Cursor
public override bool SetContent(object content)
{
- if (!(content is string contentString))
+ if (!(content is LocalisableString contentString))
return false;
if (contentString == text.Text) return true;
diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs
index 5a1eb53fe1..6ad88eaaba 100644
--- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs
+++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs
@@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
using osu.Framework.Platform;
using osuTK;
using osuTK.Graphics;
@@ -58,6 +59,6 @@ namespace osu.Game.Graphics.UserInterface
return true;
}
- public string TooltipText => "view in browser";
+ public LocalisableString TooltipText => "view in browser";
}
}
diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs
index ac6f5ceb1b..8e82f4a7c1 100644
--- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs
@@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
using osu.Framework.Platform;
namespace osu.Game.Graphics.UserInterface
@@ -105,7 +106,7 @@ namespace osu.Game.Graphics.UserInterface
private class CapsWarning : SpriteIcon, IHasTooltip
{
- public string TooltipText => @"caps lock is active";
+ public LocalisableString TooltipText => "caps lock is active";
public CapsWarning()
{
diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
index f58962f8e1..ae16169123 100644
--- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
+++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
@@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
namespace osu.Game.Graphics.UserInterface
{
@@ -34,7 +35,7 @@ namespace osu.Game.Graphics.UserInterface
private readonly Box rightBox;
private readonly Container nubContainer;
- public virtual string TooltipText { get; private set; }
+ public virtual LocalisableString TooltipText { get; private set; }
///
/// Whether to format the tooltip as a percentage or the actual value.
diff --git a/osu.Game/Online/API/Requests/PostMessageRequest.cs b/osu.Game/Online/API/Requests/PostMessageRequest.cs
index 84ab873acf..5d508a4cdf 100644
--- a/osu.Game/Online/API/Requests/PostMessageRequest.cs
+++ b/osu.Game/Online/API/Requests/PostMessageRequest.cs
@@ -9,11 +9,11 @@ namespace osu.Game.Online.API.Requests
{
public class PostMessageRequest : APIRequest
{
- private readonly Message message;
+ public readonly Message Message;
public PostMessageRequest(Message message)
{
- this.message = message;
+ Message = message;
}
protected override WebRequest CreateWebRequest()
@@ -21,12 +21,12 @@ namespace osu.Game.Online.API.Requests
var req = base.CreateWebRequest();
req.Method = HttpMethod.Post;
- req.AddParameter(@"is_action", message.IsAction.ToString().ToLowerInvariant());
- req.AddParameter(@"message", message.Content);
+ req.AddParameter(@"is_action", Message.IsAction.ToString().ToLowerInvariant());
+ req.AddParameter(@"message", Message.Content);
return req;
}
- protected override string Target => $@"chat/channels/{message.ChannelId}/messages";
+ protected override string Target => $@"chat/channels/{Message.ChannelId}/messages";
}
}
diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs
index 8507887357..3136a3960d 100644
--- a/osu.Game/Online/Chat/ChannelManager.cs
+++ b/osu.Game/Online/Chat/ChannelManager.cs
@@ -225,7 +225,7 @@ namespace osu.Game.Online.Chat
switch (command)
{
case "np":
- AddInternal(new NowPlayingCommand());
+ AddInternal(new NowPlayingCommand(target));
break;
case "me":
@@ -235,7 +235,7 @@ namespace osu.Game.Online.Chat
break;
}
- PostMessage(content, true);
+ PostMessage(content, true, target);
break;
case "join":
diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs
index 926709694b..7756591e03 100644
--- a/osu.Game/Online/Chat/NowPlayingCommand.cs
+++ b/osu.Game/Online/Chat/NowPlayingCommand.cs
@@ -21,6 +21,17 @@ namespace osu.Game.Online.Chat
[Resolved]
private Bindable currentBeatmap { get; set; }
+ private readonly Channel target;
+
+ ///
+ /// Creates a new to post the currently-playing beatmap to a parenting .
+ ///
+ /// The target channel to post to. If null, the currently-selected channel will be posted to.
+ public NowPlayingCommand(Channel target = null)
+ {
+ this.target = target;
+ }
+
protected override void LoadComplete()
{
base.LoadComplete();
@@ -48,7 +59,7 @@ namespace osu.Game.Online.Chat
var beatmapString = beatmap.OnlineBeatmapID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmap.OnlineBeatmapID} {beatmap}]" : beatmap.ToString();
- channelManager.PostMessage($"is {verb} {beatmapString}", true);
+ channelManager.PostMessage($"is {verb} {beatmapString}", true, target);
Expire();
}
}
diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs
index 795540b65d..e35d3d6461 100644
--- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs
+++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs
@@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@@ -295,7 +296,7 @@ namespace osu.Game.Online.Leaderboards
public override bool Contains(Vector2 screenSpacePos) => content.Contains(screenSpacePos);
- public string TooltipText { get; }
+ public LocalisableString TooltipText { get; }
public ScoreComponentLabel(LeaderboardScoreStatistic statistic)
{
@@ -365,7 +366,7 @@ namespace osu.Game.Online.Leaderboards
};
}
- public string TooltipText { get; }
+ public LocalisableString TooltipText { get; }
}
public class LeaderboardScoreStatistic
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 6195d8e1ea..7954eafdca 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -24,6 +24,7 @@ using osu.Framework.Graphics.Performance;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.Logging;
+using osu.Framework.Threading;
using osu.Game.Audio;
using osu.Game.Database;
using osu.Game.Input;
@@ -156,6 +157,8 @@ namespace osu.Game
private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(GLOBAL_TRACK_VOLUME_ADJUST);
+ private IBindable updateThreadState;
+
public OsuGameBase()
{
UseDevelopmentServer = DebugUtils.IsDebugBuild;
@@ -182,6 +185,10 @@ namespace osu.Game
dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage));
dependencies.Cache(realmFactory = new RealmContextFactory(Storage));
+
+ updateThreadState = Host.UpdateThread.State.GetBoundCopy();
+ updateThreadState.BindValueChanged(updateThreadStateChanged);
+
AddInternal(realmFactory);
dependencies.CacheAs(Storage);
@@ -356,6 +363,23 @@ namespace osu.Game
Ruleset.BindValueChanged(onRulesetChanged);
}
+ private IDisposable blocking;
+
+ private void updateThreadStateChanged(ValueChangedEvent state)
+ {
+ switch (state.NewValue)
+ {
+ case GameThreadState.Running:
+ blocking?.Dispose();
+ blocking = null;
+ break;
+
+ case GameThreadState.Paused:
+ blocking = realmFactory.BlockAllOperations();
+ break;
+ }
+ }
+
protected override void LoadComplete()
{
base.LoadComplete();
@@ -398,11 +422,15 @@ namespace osu.Game
public void Migrate(string path)
{
+ Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""...");
+
using (realmFactory.BlockAllOperations())
{
contextFactory.FlushConnections();
(Storage as OsuStorage)?.Migrate(Host.GetStorage(path));
}
+
+ Logger.Log(@"Migration complete!");
}
protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager();
diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs
index 1935a250b7..d80ef075e9 100644
--- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs
+++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs
@@ -10,11 +10,13 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Localisation;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
+using osu.Game.Resources.Localisation.Web;
using osuTK;
using osuTK.Graphics;
@@ -23,9 +25,9 @@ namespace osu.Game.Overlays.BeatmapListing
public class BeatmapListingFilterControl : CompositeDrawable
{
///
- /// Fired when a search finishes. Contains only new items in the case of pagination.
+ /// Fired when a search finishes.
///
- public Action> SearchFinished;
+ public Action SearchFinished;
///
/// Fired when search criteria change.
@@ -212,7 +214,25 @@ namespace osu.Game.Overlays.BeatmapListing
lastResponse = response;
getSetsRequest = null;
- SearchFinished?.Invoke(sets);
+ // check if a non-supporter used supporter-only filters
+ if (!api.LocalUser.Value.IsSupporter)
+ {
+ List filters = new List();
+
+ if (searchControl.Played.Value != SearchPlayed.Any)
+ filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed);
+
+ if (searchControl.Ranks.Any())
+ filters.Add(BeatmapsStrings.ListingSearchFiltersRank);
+
+ if (filters.Any())
+ {
+ SearchFinished?.Invoke(SearchResult.SupporterOnlyFilters(filters));
+ return;
+ }
+ }
+
+ SearchFinished?.Invoke(SearchResult.ResultsReturned(sets));
};
api.Queue(getSetsRequest);
@@ -237,5 +257,53 @@ namespace osu.Game.Overlays.BeatmapListing
base.Dispose(isDisposing);
}
+
+ ///
+ /// Indicates the type of result of a user-requested beatmap search.
+ ///
+ public enum SearchResultType
+ {
+ ///
+ /// Actual results have been returned from API.
+ ///
+ ResultsReturned,
+
+ ///
+ /// The user is not a supporter, but used supporter-only search filters.
+ ///
+ SupporterOnlyFilters
+ }
+
+ ///
+ /// Describes the result of a user-requested beatmap search.
+ ///
+ public struct SearchResult
+ {
+ public SearchResultType Type { get; private set; }
+
+ ///
+ /// Contains the beatmap sets returned from API.
+ /// Valid for read if and only if is .
+ ///
+ public List Results { get; private set; }
+
+ ///
+ /// Contains the names of supporter-only filters requested by the user.
+ /// Valid for read if and only if is .
+ ///
+ public List SupporterOnlyFiltersUsed { get; private set; }
+
+ public static SearchResult ResultsReturned(List results) => new SearchResult
+ {
+ Type = SearchResultType.ResultsReturned,
+ Results = results
+ };
+
+ public static SearchResult SupporterOnlyFilters(List filters) => new SearchResult
+ {
+ Type = SearchResultType.SupporterOnlyFilters,
+ SupporterOnlyFiltersUsed = filters
+ };
+ }
}
}
diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs
index 5e65cd9488..460b4ba4c9 100644
--- a/osu.Game/Overlays/BeatmapListingOverlay.cs
+++ b/osu.Game/Overlays/BeatmapListingOverlay.cs
@@ -7,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Localisation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -15,7 +16,9 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Events;
using osu.Game.Audio;
using osu.Game.Beatmaps;
+using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.Containers;
using osu.Game.Overlays.BeatmapListing;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Resources.Localisation.Web;
@@ -33,6 +36,7 @@ namespace osu.Game.Overlays
private Container panelTarget;
private FillFlowContainer foundContent;
private NotFoundDrawable notFoundContent;
+ private SupporterRequiredDrawable supporterRequiredContent;
private BeatmapListingFilterControl filterControl;
public BeatmapListingOverlay()
@@ -76,6 +80,7 @@ namespace osu.Game.Overlays
{
foundContent = new FillFlowContainer(),
notFoundContent = new NotFoundDrawable(),
+ supporterRequiredContent = new SupporterRequiredDrawable(),
}
}
},
@@ -115,9 +120,16 @@ namespace osu.Game.Overlays
private Task panelLoadDelegate;
- private void onSearchFinished(List beatmaps)
+ private void onSearchFinished(BeatmapListingFilterControl.SearchResult searchResult)
{
- var newPanels = beatmaps.Select(b => new GridBeatmapPanel(b)
+ if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilters)
+ {
+ supporterRequiredContent.UpdateText(searchResult.SupporterOnlyFiltersUsed);
+ addContentToPlaceholder(supporterRequiredContent);
+ return;
+ }
+
+ var newPanels = searchResult.Results.Select(b => new GridBeatmapPanel(b)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
@@ -128,7 +140,7 @@ namespace osu.Game.Overlays
//No matches case
if (!newPanels.Any())
{
- LoadComponentAsync(notFoundContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
+ addContentToPlaceholder(notFoundContent);
return;
}
@@ -170,9 +182,9 @@ namespace osu.Game.Overlays
{
var transform = lastContent.FadeOut(100, Easing.OutQuint);
- if (lastContent == notFoundContent)
+ if (lastContent == notFoundContent || lastContent == supporterRequiredContent)
{
- // not found display may be used multiple times, so don't expire/dispose it.
+ // the placeholders may be used multiple times, so don't expire/dispose them.
transform.Schedule(() => panelTarget.Remove(lastContent));
}
else
@@ -240,6 +252,67 @@ namespace osu.Game.Overlays
}
}
+ // TODO: localisation requires Text/LinkFlowContainer support for localising strings with links inside
+ // (https://github.com/ppy/osu-framework/issues/4530)
+ public class SupporterRequiredDrawable : CompositeDrawable
+ {
+ private LinkFlowContainer supporterRequiredText;
+
+ public SupporterRequiredDrawable()
+ {
+ RelativeSizeAxes = Axes.X;
+ Height = 225;
+ Alpha = 0;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ AddInternal(new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.X,
+ Direction = FillDirection.Horizontal,
+ Children = new Drawable[]
+ {
+ new Sprite
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fit,
+ Texture = textures.Get(@"Online/supporter-required"),
+ },
+ supporterRequiredText = new LinkFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AutoSizeAxes = Axes.Both,
+ Margin = new MarginPadding { Bottom = 10 },
+ },
+ }
+ });
+ }
+
+ public void UpdateText(List