diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml
index 7728d91152..4274d01bab 100644
--- a/.github/workflows/test-diffcalc.yml
+++ b/.github/workflows/test-diffcalc.yml
@@ -23,9 +23,9 @@ jobs:
continue-on-error: true
if: |
- ${{ github.event.issue.pull_request }} &&
+ github.event.issue.pull_request &&
contains(github.event.comment.body, '!pp check') &&
- ${{ github.event.comment.author_association == 'MEMBER' }}
+ (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER')
strategy:
fail-fast: false
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml
index 8fa7608b8e..498a710df9 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml
@@ -1,8 +1,8 @@
-
-
-
+
+
+
@@ -14,7 +14,7 @@
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 8f922f74a7..786ce2589d 100644
--- a/README.md
+++ b/README.md
@@ -31,12 +31,11 @@ If you are looking to install or test osu! without setting up a development envi
**Latest build:**
-| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
+| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
| ------------- | ------------- | ------------- | ------------- | ------------- |
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
-- When running on Windows 7 or 8.1, *[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/windows?tabs=net50&pivots=os-windows#dependencies)** may be required to correctly run .NET 5 applications if your operating system is not up-to-date with the latest service packs.
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
## Developing a custom ruleset
diff --git a/osu.Android.props b/osu.Android.props
index 05367c00f6..d4331a5e65 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,8 +51,8 @@
-
-
+
+
diff --git a/osu.Game.Benchmarks/BenchmarkMod.cs b/osu.Game.Benchmarks/BenchmarkMod.cs
index 050ddf36d5..c5375e9f09 100644
--- a/osu.Game.Benchmarks/BenchmarkMod.cs
+++ b/osu.Game.Benchmarks/BenchmarkMod.cs
@@ -14,9 +14,9 @@ namespace osu.Game.Benchmarks
[Params(1, 10, 100)]
public int Times { get; set; }
- [GlobalSetup]
- public void GlobalSetup()
+ public override void SetUp()
{
+ base.SetUp();
mod = new OsuModDoubleTime();
}
diff --git a/osu.Game.Benchmarks/BenchmarkRuleset.cs b/osu.Game.Benchmarks/BenchmarkRuleset.cs
new file mode 100644
index 0000000000..2835ec9499
--- /dev/null
+++ b/osu.Game.Benchmarks/BenchmarkRuleset.cs
@@ -0,0 +1,62 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Engines;
+using osu.Game.Online.API;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
+
+namespace osu.Game.Benchmarks
+{
+ public class BenchmarkRuleset : BenchmarkTest
+ {
+ private OsuRuleset ruleset;
+ private APIMod apiModDoubleTime;
+ private APIMod apiModDifficultyAdjust;
+
+ public override void SetUp()
+ {
+ base.SetUp();
+ ruleset = new OsuRuleset();
+ apiModDoubleTime = new APIMod { Acronym = "DT" };
+ apiModDifficultyAdjust = new APIMod { Acronym = "DA" };
+ }
+
+ [Benchmark]
+ public void BenchmarkToModDoubleTime()
+ {
+ apiModDoubleTime.ToMod(ruleset);
+ }
+
+ [Benchmark]
+ public void BenchmarkToModDifficultyAdjust()
+ {
+ apiModDifficultyAdjust.ToMod(ruleset);
+ }
+
+ [Benchmark]
+ public void BenchmarkGetAllMods()
+ {
+ ruleset.CreateAllMods().Consume(new Consumer());
+ }
+
+ [Benchmark]
+ public void BenchmarkGetAllModsForReference()
+ {
+ ruleset.AllMods.Consume(new Consumer());
+ }
+
+ [Benchmark]
+ public void BenchmarkGetForAcronym()
+ {
+ ruleset.CreateModFromAcronym("DT");
+ }
+
+ [Benchmark]
+ public void BenchmarkGetForType()
+ {
+ ruleset.CreateMod();
+ }
+ }
+}
diff --git a/osu.Game.Benchmarks/Program.cs b/osu.Game.Benchmarks/Program.cs
index c55075fea6..439ced53ab 100644
--- a/osu.Game.Benchmarks/Program.cs
+++ b/osu.Game.Benchmarks/Program.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 BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
namespace osu.Game.Benchmarks
@@ -11,7 +12,7 @@ namespace osu.Game.Benchmarks
{
BenchmarkSwitcher
.FromAssembly(typeof(Program).Assembly)
- .Run(args);
+ .Run(args, DefaultConfig.Instance.WithOption(ConfigOptions.DisableOptimizationsValidator, true));
}
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs
index b7d7af6b8c..68cf3b67df 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
c.Add(CreateHitObject().With(h =>
{
- h.HitObject.StartTime = START_TIME;
+ h.HitObject.StartTime = Time.Current + 5000;
h.AccentColour.Value = Color4.Orange;
}));
})
@@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
c.Add(CreateHitObject().With(h =>
{
- h.HitObject.StartTime = START_TIME;
+ h.HitObject.StartTime = Time.Current + 5000;
h.AccentColour.Value = Color4.Orange;
}));
})
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs
index 1d84a2dfcb..ddfd057cd8 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs
@@ -19,8 +19,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
///
public abstract class ManiaSkinnableTestScene : SkinnableTestScene
{
- protected const double START_TIME = 1000000000;
-
[Cached(Type = typeof(IScrollingInfo))]
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
@@ -55,27 +53,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
public readonly Bindable Direction = new Bindable();
IBindable IScrollingInfo.Direction => Direction;
- IBindable IScrollingInfo.TimeRange { get; } = new Bindable(1000);
- IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ZeroScrollAlgorithm();
- }
-
- private class ZeroScrollAlgorithm : IScrollAlgorithm
- {
- public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
- => double.MinValue;
-
- public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
- => scrollLength;
-
- public float PositionAt(double time, double currentTime, double timeRange, float scrollLength)
- => (float)((time - START_TIME) / timeRange) * scrollLength;
-
- public double TimeAt(float position, double currentTime, double timeRange, float scrollLength)
- => 0;
-
- public void Reset()
- {
- }
+ IBindable IScrollingInfo.TimeRange { get; } = new Bindable(5000);
+ IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ConstantScrollAlgorithm();
}
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs
index e14ad92842..449a6ff23d 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.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.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Allocation;
@@ -13,6 +14,10 @@ using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Framework.Bindables;
+using osu.Framework.Testing;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Tests
{
@@ -22,14 +27,65 @@ namespace osu.Game.Rulesets.Mania.Tests
[Resolved]
private RulesetConfigCache configCache { get; set; }
- private readonly Bindable configTimingBasedNoteColouring = new Bindable();
+ private Bindable configTimingBasedNoteColouring;
- protected override void LoadComplete()
+ private ManualClock clock;
+ private DrawableManiaRuleset drawableRuleset;
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("setup hierarchy", () => Child = new Container
+ {
+ Clock = new FramedClock(clock = new ManualClock()),
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Children = new[]
+ {
+ drawableRuleset = (DrawableManiaRuleset)Ruleset.Value.CreateInstance().CreateDrawableRulesetWith(createTestBeatmap())
+ }
+ });
+ AddStep("retrieve config bindable", () =>
+ {
+ var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
+ configTimingBasedNoteColouring = config.GetBindable(ManiaRulesetSetting.TimingBasedNoteColouring);
+ });
+ }
+
+ [Test]
+ public void TestSimple()
+ {
+ AddStep("enable", () => configTimingBasedNoteColouring.Value = true);
+ AddStep("disable", () => configTimingBasedNoteColouring.Value = false);
+ }
+
+ [Test]
+ public void TestToggleOffScreen()
+ {
+ AddStep("enable", () => configTimingBasedNoteColouring.Value = true);
+
+ seekTo(10000);
+ AddStep("disable", () => configTimingBasedNoteColouring.Value = false);
+ seekTo(0);
+ AddAssert("all notes not coloured", () => this.ChildrenOfType().All(note => note.Colour == Colour4.White));
+
+ seekTo(10000);
+ AddStep("enable again", () => configTimingBasedNoteColouring.Value = true);
+ seekTo(0);
+ AddAssert("some notes coloured", () => this.ChildrenOfType().Any(note => note.Colour != Colour4.White));
+ }
+
+ private void seekTo(double time)
+ {
+ AddStep($"seek to {time}", () => clock.CurrentTime = time);
+ AddUntilStep("wait for seek", () => Precision.AlmostEquals(drawableRuleset.FrameStableClock.CurrentTime, time, 1));
+ }
+
+ private ManiaBeatmap createTestBeatmap()
{
const double beat_length = 500;
- var ruleset = new ManiaRuleset();
-
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 })
{
HitObjects =
@@ -45,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Tests
new Note { StartTime = beat_length }
},
ControlPointInfo = new ControlPointInfo(),
- BeatmapInfo = { Ruleset = ruleset.RulesetInfo },
+ BeatmapInfo = { Ruleset = Ruleset.Value },
};
foreach (var note in beatmap.HitObjects)
@@ -57,24 +113,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
BeatLength = beat_length
});
-
- Child = new Container
- {
- Clock = new FramedClock(new ManualClock()),
- RelativeSizeAxes = Axes.Both,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Children = new[]
- {
- ruleset.CreateDrawableRulesetWith(beatmap)
- }
- };
-
- var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
- config.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring);
-
- AddStep("Enable", () => configTimingBasedNoteColouring.Value = true);
- AddStep("Disable", () => configTimingBasedNoteColouring.Value = false);
+ return beatmap;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
index 33d872dfb6..d53c28868d 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -66,6 +66,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
StartTimeBindable.BindValueChanged(_ => updateSnapColour(), true);
}
+ protected override void OnApply()
+ {
+ base.OnApply();
+ updateSnapColour();
+ }
+
protected override void OnDirectionChanged(ValueChangedEvent e)
{
base.OnDirectionChanged(e);
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
index 8d8387378e..19881b5c33 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
@@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
- [TestCase(6.7568168283591499d, "diffcalc-test")]
- [TestCase(1.0348244046058293d, "zero-length-sliders")]
+ [TestCase(6.6634445062299665d, "diffcalc-test")]
+ [TestCase(1.0414203870195022d, "zero-length-sliders")]
public void Test(double expected, string name)
=> base.Test(expected, name);
- [TestCase(8.4783236764532557d, "diffcalc-test")]
- [TestCase(1.2708532136987165d, "zero-length-sliders")]
+ [TestCase(8.3858089051603368d, "diffcalc-test")]
+ [TestCase(1.2723279173428435d, "zero-length-sliders")]
public void TestClockRateAdjusted(double expected, string name)
=> Test(expected, name, new OsuModDoubleTime());
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 743494abac..ff55873d69 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -35,7 +35,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
- double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
+
+ double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000;
+ double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000;
+ double basePerformance = Math.Pow(Math.Pow(baseAimPerformance, 1.1) + Math.Pow(baseSpeedPerformance, 1.1), 1 / 1.1);
+ double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 8560a36fb4..a4bf8c92e3 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -64,6 +64,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
Assert.IsFalse(beatmapInfo.SpecialStyle);
Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
+ Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate);
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown);
Assert.AreEqual(0, beatmapInfo.CountdownOffset);
}
diff --git a/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs b/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs
index 7a5789f01a..ce6b3a68a5 100644
--- a/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs
+++ b/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs
@@ -3,6 +3,7 @@
using NUnit.Framework;
using osu.Game.Online.API;
+using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Tests.Mods
@@ -11,26 +12,42 @@ namespace osu.Game.Tests.Mods
public class ModSettingsEqualityComparison
{
[Test]
- public void Test()
+ public void TestAPIMod()
{
+ var apiMod1 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.25 } });
+ var apiMod2 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.26 } });
+ var apiMod3 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.26 } });
+
+ Assert.That(apiMod1, Is.Not.EqualTo(apiMod2));
+ Assert.That(apiMod2, Is.EqualTo(apiMod2));
+ Assert.That(apiMod2, Is.EqualTo(apiMod3));
+ Assert.That(apiMod3, Is.EqualTo(apiMod2));
+ }
+
+ [Test]
+ public void TestMod()
+ {
+ var ruleset = new OsuRuleset();
+
var mod1 = new OsuModDoubleTime { SpeedChange = { Value = 1.25 } };
var mod2 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } };
var mod3 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } };
- var apiMod1 = new APIMod(mod1);
- var apiMod2 = new APIMod(mod2);
- var apiMod3 = new APIMod(mod3);
+
+ var doubleConvertedMod1 = new APIMod(mod1).ToMod(ruleset);
+ var doulbeConvertedMod2 = new APIMod(mod2).ToMod(ruleset);
+ var doulbeConvertedMod3 = new APIMod(mod3).ToMod(ruleset);
Assert.That(mod1, Is.Not.EqualTo(mod2));
- Assert.That(apiMod1, Is.Not.EqualTo(apiMod2));
+ Assert.That(doubleConvertedMod1, Is.Not.EqualTo(doulbeConvertedMod2));
Assert.That(mod2, Is.EqualTo(mod2));
- Assert.That(apiMod2, Is.EqualTo(apiMod2));
+ Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod2));
Assert.That(mod2, Is.EqualTo(mod3));
- Assert.That(apiMod2, Is.EqualTo(apiMod3));
+ Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod3));
Assert.That(mod3, Is.EqualTo(mod2));
- Assert.That(apiMod3, Is.EqualTo(apiMod2));
+ Assert.That(doulbeConvertedMod3, Is.EqualTo(doulbeConvertedMod2));
}
}
}
diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs
index e45b8f7dc5..785f31386d 100644
--- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs
+++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs
@@ -40,10 +40,10 @@ namespace osu.Game.Tests.NonVisual.Skinning
assertPlaybackPosition(0);
AddStep("set start time to 1000", () => animationTimeReference.AnimationStartTime.Value = 1000);
- assertPlaybackPosition(-1000);
+ assertPlaybackPosition(0);
AddStep("set current time to 500", () => animationTimeReference.ManualClock.CurrentTime = 500);
- assertPlaybackPosition(-500);
+ assertPlaybackPosition(0);
}
private void assertPlaybackPosition(double expectedPosition)
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs
new file mode 100644
index 0000000000..c81a1abfbc
--- /dev/null
+++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs
@@ -0,0 +1,177 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Overlays.Dialog;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Edit;
+using osu.Game.Tests.Beatmaps.IO;
+
+namespace osu.Game.Tests.Visual.Editing
+{
+ public class TestSceneDifficultySwitching : EditorTestScene
+ {
+ protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
+
+ protected override bool IsolateSavingFromDatabase => false;
+
+ [Resolved]
+ private OsuGameBase game { get; set; }
+
+ [Resolved]
+ private BeatmapManager beatmaps { get; set; }
+
+ private BeatmapSetInfo importedBeatmapSet;
+
+ public override void SetUpSteps()
+ {
+ AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result);
+ base.SetUpSteps();
+ }
+
+ protected override void LoadEditor()
+ {
+ Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First());
+ base.LoadEditor();
+ }
+
+ [Test]
+ public void TestBasicSwitch()
+ {
+ BeatmapInfo targetDifficulty = null;
+
+ AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo)));
+ switchToDifficulty(() => targetDifficulty);
+ confirmEditingBeatmap(() => targetDifficulty);
+
+ AddStep("exit editor", () => Stack.Exit());
+ // ensure editor loader didn't resume.
+ AddAssert("stack empty", () => Stack.CurrentScreen == null);
+ }
+
+ [Test]
+ public void TestClockPositionPreservedBetweenSwitches()
+ {
+ BeatmapInfo targetDifficulty = null;
+ AddStep("seek editor to 00:05:00", () => EditorClock.Seek(5000));
+
+ AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo)));
+ switchToDifficulty(() => targetDifficulty);
+ confirmEditingBeatmap(() => targetDifficulty);
+ AddAssert("editor clock at 00:05:00", () => EditorClock.CurrentTime == 5000);
+
+ AddStep("exit editor", () => Stack.Exit());
+ // ensure editor loader didn't resume.
+ AddAssert("stack empty", () => Stack.CurrentScreen == null);
+ }
+
+ [Test]
+ public void TestClipboardPreservedAfterSwitch([Values] bool sameRuleset)
+ {
+ BeatmapInfo targetDifficulty = null;
+
+ AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.First()));
+ AddStep("copy object", () => Editor.Copy());
+
+ AddStep("set target difficulty", () =>
+ {
+ targetDifficulty = sameRuleset
+ ? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID == Beatmap.Value.BeatmapInfo.RulesetID)
+ : importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID != Beatmap.Value.BeatmapInfo.RulesetID);
+ });
+ switchToDifficulty(() => targetDifficulty);
+ confirmEditingBeatmap(() => targetDifficulty);
+
+ AddAssert("no objects selected", () => !EditorBeatmap.SelectedHitObjects.Any());
+ AddStep("paste object", () => Editor.Paste());
+
+ if (sameRuleset)
+ AddAssert("object was pasted", () => EditorBeatmap.SelectedHitObjects.Any());
+ else
+ AddAssert("object was not pasted", () => !EditorBeatmap.SelectedHitObjects.Any());
+
+ AddStep("exit editor", () => Stack.Exit());
+
+ if (sameRuleset)
+ {
+ AddUntilStep("prompt for save dialog shown", () => DialogOverlay.CurrentDialog is PromptForSaveDialog);
+ AddStep("discard changes", () => ((PromptForSaveDialog)DialogOverlay.CurrentDialog).PerformOkAction());
+ }
+
+ // ensure editor loader didn't resume.
+ AddAssert("stack empty", () => Stack.CurrentScreen == null);
+ }
+
+ [Test]
+ public void TestPreventSwitchDueToUnsavedChanges()
+ {
+ BeatmapInfo targetDifficulty = null;
+ PromptForSaveDialog saveDialog = null;
+
+ AddStep("remove first hitobject", () => EditorBeatmap.RemoveAt(0));
+
+ AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo)));
+ switchToDifficulty(() => targetDifficulty);
+
+ AddUntilStep("prompt for save dialog shown", () =>
+ {
+ saveDialog = this.ChildrenOfType().Single();
+ return saveDialog != null;
+ });
+ AddStep("continue editing", () =>
+ {
+ var continueButton = saveDialog.ChildrenOfType().Last();
+ continueButton.TriggerClick();
+ });
+
+ confirmEditingBeatmap(() => importedBeatmapSet.Beatmaps.First());
+
+ AddRepeatStep("exit editor forcefully", () => Stack.Exit(), 2);
+ // ensure editor loader didn't resume.
+ AddAssert("stack empty", () => Stack.CurrentScreen == null);
+ }
+
+ [Test]
+ public void TestAllowSwitchAfterDiscardingUnsavedChanges()
+ {
+ BeatmapInfo targetDifficulty = null;
+ PromptForSaveDialog saveDialog = null;
+
+ AddStep("remove first hitobject", () => EditorBeatmap.RemoveAt(0));
+
+ AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo)));
+ switchToDifficulty(() => targetDifficulty);
+
+ AddUntilStep("prompt for save dialog shown", () =>
+ {
+ saveDialog = this.ChildrenOfType().Single();
+ return saveDialog != null;
+ });
+ AddStep("discard changes", () =>
+ {
+ var continueButton = saveDialog.ChildrenOfType().Single();
+ continueButton.TriggerClick();
+ });
+
+ confirmEditingBeatmap(() => targetDifficulty);
+
+ AddStep("exit editor forcefully", () => Stack.Exit());
+ // ensure editor loader didn't resume.
+ AddAssert("stack empty", () => Stack.CurrentScreen == null);
+ }
+
+ private void switchToDifficulty(Func difficulty) => AddStep("switch to difficulty", () => Editor.SwitchToDifficulty(difficulty.Invoke()));
+
+ private void confirmEditingBeatmap(Func targetDifficulty)
+ {
+ AddUntilStep("current beatmap is correct", () => Beatmap.Value.BeatmapInfo.Equals(targetDifficulty.Invoke()));
+ AddUntilStep("current screen is editor", () => Stack.CurrentScreen == Editor && Editor?.IsLoaded == true);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
index b6ae91844a..440d66ff9f 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
@@ -11,6 +11,7 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Setup;
using osu.Game.Tests.Resources;
using SharpCompress.Archives;
@@ -55,6 +56,9 @@ namespace osu.Game.Tests.Visual.Editing
[Test]
public void TestExitWithoutSave()
{
+ EditorBeatmap editorBeatmap = null;
+
+ AddStep("store editor beatmap", () => editorBeatmap = EditorBeatmap);
AddStep("exit without save", () =>
{
Editor.Exit();
@@ -62,7 +66,7 @@ namespace osu.Game.Tests.Visual.Editing
});
AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen());
- AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true);
+ AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == editorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs
index b7dcad3825..00b5c38e20 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Configuration;
@@ -71,7 +70,7 @@ namespace osu.Game.Tests.Visual.Gameplay
var working = CreateWorkingBeatmap(rulesetInfo);
Beatmap.Value = working;
- SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) };
+ SelectedMods.Value = new[] { ruleset.CreateMod() };
Player = CreatePlayer(ruleset);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
index b38f7a998d..2ce0213ea2 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
@@ -54,7 +54,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{
recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
- Recorder = recorder = new TestReplayRecorder(new Score { Replay = replay })
+ Recorder = recorder = new TestReplayRecorder(new Score
+ {
+ Replay = replay,
+ ScoreInfo = { Beatmap = gameplayBeatmap.BeatmapInfo }
+ })
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
},
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
index 6e338b7202..85a2870bf9 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
@@ -45,7 +45,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{
recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
- Recorder = new TestReplayRecorder(new Score { Replay = replay })
+ Recorder = new TestReplayRecorder(new Score
+ {
+ Replay = replay,
+ ScoreInfo = { Beatmap = gameplayBeatmap.BeatmapInfo }
+ })
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos)
},
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
index bb577886cc..d9d0dc6c58 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
@@ -354,7 +354,7 @@ namespace osu.Game.Tests.Visual.Gameplay
internal class TestReplayRecorder : ReplayRecorder
{
public TestReplayRecorder()
- : base(new Score())
+ : base(new Score { ScoreInfo = { Beatmap = new BeatmapInfo() } })
{
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
index 3973dc57b2..b1f5781f6f 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
@@ -130,6 +130,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Type = { Value = MatchType.HeadToHead },
}));
+ AddUntilStep("wait for panel load", () => drawableRoom.ChildrenOfType().Any());
+
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType().Single().Alpha));
AddStep("set password", () => room.Password.Value = "password");
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs
index 99f6ab1ae1..4e2a91af78 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs
@@ -3,6 +3,7 @@
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Screens;
using osu.Framework.Testing;
@@ -51,6 +52,24 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("room join password correct", () => lastJoinedPassword == null);
}
+ [Test]
+ public void TestPopoverHidesOnBackButton()
+ {
+ AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
+ AddStep("select room", () => InputManager.Key(Key.Down));
+ AddStep("attempt join room", () => InputManager.Key(Key.Enter));
+
+ AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType().Any());
+
+ AddAssert("textbox has focus", () => InputManager.FocusedDrawable is OsuPasswordTextBox);
+
+ AddStep("hit escape", () => InputManager.Key(Key.Escape));
+ AddAssert("textbox lost focus", () => InputManager.FocusedDrawable is SearchTextBox);
+
+ AddStep("hit escape", () => InputManager.Key(Key.Escape));
+ AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType().Any());
+ }
+
[Test]
public void TestPopoverHidesOnLeavingScreen()
{
@@ -64,7 +83,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
- public void TestJoinRoomWithPassword()
+ public void TestJoinRoomWithIncorrectPassword()
+ {
+ DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;
+
+ AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
+ AddStep("select room", () => InputManager.Key(Key.Down));
+ AddStep("attempt join room", () => InputManager.Key(Key.Enter));
+ AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null);
+ AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "wrong");
+ AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().TriggerClick());
+
+ AddAssert("room not joined", () => loungeScreen.IsCurrentScreen());
+ AddUntilStep("password prompt still visible", () => passwordEntryPopover.State.Value == Visibility.Visible);
+ }
+
+ [Test]
+ public void TestJoinRoomWithCorrectPassword()
{
DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs
index 4e08ffef17..44a8d7b439 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs
@@ -3,6 +3,7 @@
using NUnit.Framework;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
@@ -15,11 +16,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
SelectedRoom.Value = new Room();
- Child = new MultiplayerMatchFooter
+ Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Height = 50
+ RelativeSizeAxes = Axes.X,
+ Height = 50,
+ Child = new MultiplayerMatchFooter()
};
});
}
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
index b8232837b5..43459408d5 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
@@ -75,7 +75,6 @@ namespace osu.Game.Tests.Visual.Navigation
typeof(FileStore),
typeof(ScoreManager),
typeof(BeatmapManager),
- typeof(SettingsStore),
typeof(RulesetConfigCache),
typeof(OsuColour),
typeof(IBindable),
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
index b536233ff0..cc64d37116 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
@@ -15,6 +15,7 @@ using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Toolbar;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Screens.Menu;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.Play;
@@ -388,6 +389,19 @@ namespace osu.Game.Tests.Visual.Navigation
AddAssert("now playing is hidden", () => nowPlayingOverlay.State.Value == Visibility.Hidden);
}
+ [Test]
+ public void TestExitGameFromSongSelect()
+ {
+ PushAndConfirm(() => new TestPlaySongSelect());
+ exitViaEscapeAndConfirm();
+
+ pushEscape(); // returns to osu! logo
+
+ AddStep("Hold escape", () => InputManager.PressKey(Key.Escape));
+ AddUntilStep("Wait for intro", () => Game.ScreenStack.CurrentScreen is IntroTriangles);
+ AddUntilStep("Wait for game exit", () => Game.ScreenStack.CurrentScreen == null);
+ }
+
private void pushEscape() =>
AddStep("Press escape", () => InputManager.Key(Key.Escape));
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs
index 5bfb676f81..963809ebe1 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs
@@ -5,10 +5,10 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
@@ -17,10 +17,11 @@ using osu.Game.Overlays.BeatmapListing;
using osu.Game.Rulesets;
using osu.Game.Scoring;
using osu.Game.Users;
+using osuTK.Input;
namespace osu.Game.Tests.Visual.Online
{
- public class TestSceneBeatmapListingOverlay : OsuTestScene
+ public class TestSceneBeatmapListingOverlay : OsuManualInputManagerTestScene
{
private readonly List setsForResponse = new List();
@@ -28,27 +29,33 @@ namespace osu.Game.Tests.Visual.Online
private BeatmapListingSearchControl searchControl => overlay.ChildrenOfType().Single();
- [BackgroundDependencyLoader]
- private void load()
+ [SetUpSteps]
+ public void SetUpSteps()
{
- Child = overlay = new BeatmapListingOverlay { State = { Value = Visibility.Visible } };
-
- ((DummyAPIAccess)API).HandleRequest = req =>
+ AddStep("setup overlay", () =>
{
- if (!(req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)) return false;
-
- searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse
- {
- BeatmapSets = setsForResponse,
- });
-
- return true;
- };
+ Child = overlay = new BeatmapListingOverlay { State = { Value = Visibility.Visible } };
+ setsForResponse.Clear();
+ });
AddStep("initialize dummy", () =>
{
+ var api = (DummyAPIAccess)API;
+
+ api.HandleRequest = req =>
+ {
+ if (!(req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)) return false;
+
+ searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse
+ {
+ BeatmapSets = setsForResponse,
+ });
+
+ return true;
+ };
+
// non-supporter user
- ((DummyAPIAccess)API).LocalUser.Value = new User
+ api.LocalUser.Value = new User
{
Username = "TestBot",
Id = API.LocalUser.Value.Id + 1,
@@ -56,6 +63,51 @@ namespace osu.Game.Tests.Visual.Online
});
}
+ [Test]
+ public void TestHideViaBack()
+ {
+ AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
+ AddStep("hide", () => InputManager.Key(Key.Escape));
+ AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden);
+ }
+
+ [Test]
+ public void TestHideViaBackWithSearch()
+ {
+ AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
+
+ AddStep("search something", () => overlay.ChildrenOfType().First().Text = "search");
+
+ AddStep("kill search", () => InputManager.Key(Key.Escape));
+
+ AddAssert("search textbox empty", () => string.IsNullOrEmpty(overlay.ChildrenOfType().First().Text));
+ AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
+
+ AddStep("hide", () => InputManager.Key(Key.Escape));
+ AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden);
+ }
+
+ [Test]
+ public void TestHideViaBackWithScrolledSearch()
+ {
+ AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
+
+ AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet, 100).ToArray()));
+
+ AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType().Any(d => d.IsPresent));
+
+ AddStep("scroll to bottom", () => overlay.ChildrenOfType().First().ScrollToEnd());
+
+ AddStep("kill search", () => InputManager.Key(Key.Escape));
+
+ AddUntilStep("search textbox empty", () => string.IsNullOrEmpty(overlay.ChildrenOfType().First().Text));
+ AddUntilStep("is scrolled to top", () => overlay.ChildrenOfType().First().Current == 0);
+ AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
+
+ AddStep("hide", () => InputManager.Key(Key.Escape));
+ AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden);
+ }
+
[Test]
public void TestNoBeatmapsPlaceholder()
{
@@ -63,7 +115,7 @@ namespace osu.Game.Tests.Visual.Online
AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true);
AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet));
- AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType().Any());
+ AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType().Any(d => d.IsPresent));
AddStep("fetch for 0 beatmaps", () => fetchFor());
AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true);
@@ -193,13 +245,15 @@ namespace osu.Game.Tests.Visual.Online
noPlaceholderShown();
}
+ private static int searchCount;
+
private void fetchFor(params BeatmapSetInfo[] beatmaps)
{
setsForResponse.Clear();
setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b)));
// trigger arbitrary change for fetching.
- searchControl.Query.TriggerChange();
+ searchControl.Query.Value = $"search {searchCount++}";
}
private void setRankAchievedFilter(ScoreRank[] ranks)
@@ -229,8 +283,8 @@ namespace osu.Game.Tests.Visual.Online
private void noPlaceholderShown()
{
AddUntilStep("no placeholder shown", () =>
- !overlay.ChildrenOfType().Any()
- && !overlay.ChildrenOfType().Any());
+ !overlay.ChildrenOfType().Any(d => d.IsPresent)
+ && !overlay.ChildrenOfType().Any(d => d.IsPresent));
}
private class TestAPIBeatmapSet : APIBeatmapSet
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
index edc1696456..f420ad976b 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
@@ -21,6 +21,8 @@ namespace osu.Game.Tests.Visual.Online
protected override bool UseOnlineAPI => true;
+ private int nextBeatmapSetId = 1;
+
public TestSceneBeatmapSetOverlay()
{
Add(overlay = new TestBeatmapSetOverlay());
@@ -240,12 +242,23 @@ namespace osu.Game.Tests.Visual.Online
{
AddStep("show explicit map", () =>
{
- var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
+ var beatmapSet = getBeatmapSet();
beatmapSet.OnlineInfo.HasExplicitContent = true;
overlay.ShowBeatmapSet(beatmapSet);
});
}
+ [Test]
+ public void TestFeaturedBeatmap()
+ {
+ AddStep("show featured map", () =>
+ {
+ var beatmapSet = getBeatmapSet();
+ beatmapSet.OnlineInfo.TrackId = 1;
+ overlay.ShowBeatmapSet(beatmapSet);
+ });
+ }
+
[Test]
public void TestHide()
{
@@ -308,6 +321,14 @@ namespace osu.Game.Tests.Visual.Online
};
}
+ private BeatmapSetInfo getBeatmapSet()
+ {
+ var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
+ // Make sure the overlay is reloaded (see `BeatmapSetInfo.Equals`).
+ beatmapSet.OnlineBeatmapSetID = nextBeatmapSetId++;
+ return beatmapSet;
+ }
+
private void downloadAssert(bool shown)
{
AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.HeaderContent.DownloadButtonsVisible == shown);
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
index 7cfca31167..609e637914 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.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;
@@ -85,6 +86,22 @@ namespace osu.Game.Tests.Visual.Online
case JoinChannelRequest joinChannel:
joinChannel.TriggerSuccess();
return true;
+
+ case GetUserRequest getUser:
+ if (getUser.Lookup.Equals("some body", StringComparison.OrdinalIgnoreCase))
+ {
+ getUser.TriggerSuccess(new User
+ {
+ Username = "some body",
+ Id = 1,
+ });
+ }
+ else
+ {
+ getUser.TriggerFailure(new Exception());
+ }
+
+ return true;
}
return false;
@@ -322,6 +339,27 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("Current channel is channel 1", () => currentChannel == channel1);
}
+ [Test]
+ public void TestChatCommand()
+ {
+ AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
+ AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
+
+ AddStep("Open chat with user", () => channelManager.PostCommand("chat some body"));
+ AddAssert("PM channel is selected", () =>
+ channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single().Username == "some body");
+
+ AddStep("Open chat with non-existent user", () => channelManager.PostCommand("chat nobody"));
+ AddAssert("Last message is error", () => channelManager.CurrentChannel.Value.Messages.Last() is ErrorMessage);
+
+ // Make sure no unnecessary requests are made when the PM channel is already open.
+ AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
+ AddStep("Unregister request handling", () => ((DummyAPIAccess)API).HandleRequest = null);
+ AddStep("Open chat with user", () => channelManager.PostCommand("chat some body"));
+ AddAssert("PM channel is selected", () =>
+ channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single().Username == "some body");
+ }
+
private void pressChannelHotkey(int number)
{
var channelKey = Key.Number0 + number;
diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs
index fd5f306e07..722010ace2 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs
@@ -99,16 +99,23 @@ namespace osu.Game.Tests.Visual.Online
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
- var normal = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
+ var normal = getBeatmapSet();
normal.OnlineInfo.HasVideo = true;
normal.OnlineInfo.HasStoryboard = true;
var undownloadable = getUndownloadableBeatmapSet();
var manyDifficulties = getManyDifficultiesBeatmapSet(rulesets);
- var explicitMap = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
+ var explicitMap = getBeatmapSet();
explicitMap.OnlineInfo.HasExplicitContent = true;
+ var featuredMap = getBeatmapSet();
+ featuredMap.OnlineInfo.TrackId = 1;
+
+ var explicitFeaturedMap = getBeatmapSet();
+ explicitFeaturedMap.OnlineInfo.HasExplicitContent = true;
+ explicitFeaturedMap.OnlineInfo.TrackId = 2;
+
Child = new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
@@ -125,13 +132,19 @@ namespace osu.Game.Tests.Visual.Online
new GridBeatmapPanel(undownloadable),
new GridBeatmapPanel(manyDifficulties),
new GridBeatmapPanel(explicitMap),
+ new GridBeatmapPanel(featuredMap),
+ new GridBeatmapPanel(explicitFeaturedMap),
new ListBeatmapPanel(normal),
new ListBeatmapPanel(undownloadable),
new ListBeatmapPanel(manyDifficulties),
- new ListBeatmapPanel(explicitMap)
+ new ListBeatmapPanel(explicitMap),
+ new ListBeatmapPanel(featuredMap),
+ new ListBeatmapPanel(explicitFeaturedMap)
},
},
};
+
+ BeatmapSetInfo getBeatmapSet() => CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
}
}
}
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
index dafa8300f6..5c248163d7 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
@@ -32,6 +32,12 @@ namespace osu.Game.Tests.Visual.Playlists
private RoomsContainer roomsContainer => loungeScreen.ChildrenOfType().First();
+ [Test]
+ public void TestManyRooms()
+ {
+ AddStep("add rooms", () => RoomManager.AddRooms(500));
+ }
+
[Test]
public void TestScrollByDraggingRooms()
{
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs
index 61d49e4018..4bcc887b9f 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs
@@ -160,11 +160,14 @@ namespace osu.Game.Tests.Visual.Playlists
Ruleset = { Value = new OsuRuleset().RulesetInfo }
}));
});
+
+ AddUntilStep("wait for load", () => resultsScreen.ChildrenOfType().FirstOrDefault()?.AllPanelsVisible == true);
}
private void waitForDisplay()
{
AddUntilStep("wait for request to complete", () => requestComplete);
+ AddUntilStep("wait for panels to be visible", () => resultsScreen.ChildrenOfType().FirstOrDefault()?.AllPanelsVisible == true);
AddWaitStep("wait for display", 5);
}
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
index ba6b6bd529..631455b727 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
@@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Ranking
TestResultsScreen screen = null;
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
- AddUntilStep("wait for loaded", () => screen.IsLoaded);
+ AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible);
AddStep("click expanded panel", () =>
{
@@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Ranking
TestResultsScreen screen = null;
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
- AddUntilStep("wait for loaded", () => screen.IsLoaded);
+ AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible);
AddStep("click expanded panel", () =>
{
@@ -177,7 +177,7 @@ namespace osu.Game.Tests.Visual.Ranking
TestResultsScreen screen = null;
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
- AddUntilStep("wait for loaded", () => screen.IsLoaded);
+ AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible);
ScorePanel expandedPanel = null;
ScorePanel contractedPanel = null;
@@ -223,6 +223,7 @@ namespace osu.Game.Tests.Visual.Ranking
TestResultsScreen screen = null;
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
+ AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible);
AddAssert("download button is disabled", () => !screen.ChildrenOfType().Last().Enabled.Value);
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs
index e65dcb19b1..6f3b3028be 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs
@@ -159,6 +159,9 @@ namespace osu.Game.Tests.Visual.Ranking
var firstScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
var secondScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
+ firstScore.User.Username = "A";
+ secondScore.User.Username = "B";
+
createListStep(() => new ScorePanelList());
AddStep("add scores and select first", () =>
@@ -168,6 +171,8 @@ namespace osu.Game.Tests.Visual.Ranking
list.SelectedScore.Value = firstScore;
});
+ AddUntilStep("wait for load", () => list.AllPanelsVisible);
+
assertScoreState(firstScore, true);
assertScoreState(secondScore, false);
@@ -182,6 +187,87 @@ namespace osu.Game.Tests.Visual.Ranking
assertExpandedPanelCentred();
}
+ [Test]
+ public void TestAddScoreImmediately()
+ {
+ var score = new TestScoreInfo(new OsuRuleset().RulesetInfo);
+
+ createListStep(() =>
+ {
+ var newList = new ScorePanelList { SelectedScore = { Value = score } };
+ newList.AddScore(score);
+ return newList;
+ });
+
+ assertScoreState(score, true);
+ assertExpandedPanelCentred();
+ }
+
+ [Test]
+ public void TestKeyboardNavigation()
+ {
+ var lowestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 100 };
+ var middleScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 200 };
+ var highestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 300 };
+
+ createListStep(() => new ScorePanelList());
+
+ AddStep("add scores and select middle", () =>
+ {
+ // order of addition purposefully scrambled.
+ list.AddScore(middleScore);
+ list.AddScore(lowestScore);
+ list.AddScore(highestScore);
+ list.SelectedScore.Value = middleScore;
+ });
+
+ assertScoreState(highestScore, false);
+ assertScoreState(middleScore, true);
+ assertScoreState(lowestScore, false);
+
+ AddStep("press left", () => InputManager.Key(Key.Left));
+
+ assertScoreState(highestScore, true);
+ assertScoreState(middleScore, false);
+ assertScoreState(lowestScore, false);
+ assertExpandedPanelCentred();
+
+ AddStep("press left at start of list", () => InputManager.Key(Key.Left));
+
+ assertScoreState(highestScore, true);
+ assertScoreState(middleScore, false);
+ assertScoreState(lowestScore, false);
+ assertExpandedPanelCentred();
+
+ AddStep("press right", () => InputManager.Key(Key.Right));
+
+ assertScoreState(highestScore, false);
+ assertScoreState(middleScore, true);
+ assertScoreState(lowestScore, false);
+ assertExpandedPanelCentred();
+
+ AddStep("press right again", () => InputManager.Key(Key.Right));
+
+ assertScoreState(highestScore, false);
+ assertScoreState(middleScore, false);
+ assertScoreState(lowestScore, true);
+ assertExpandedPanelCentred();
+
+ AddStep("press right at end of list", () => InputManager.Key(Key.Right));
+
+ assertScoreState(highestScore, false);
+ assertScoreState(middleScore, false);
+ assertScoreState(lowestScore, true);
+ assertExpandedPanelCentred();
+
+ AddStep("press left", () => InputManager.Key(Key.Left));
+
+ assertScoreState(highestScore, false);
+ assertScoreState(middleScore, true);
+ assertScoreState(lowestScore, false);
+ assertExpandedPanelCentred();
+ }
+
private void createListStep(Func creationFunc)
{
AddStep("create list", () => Child = list = creationFunc().With(d =>
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
index 40b2f66d74..dcc2111ad3 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
@@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select EZ mod", () =>
{
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
- SelectedMods.Value = new[] { ruleset.GetAllMods().OfType().Single() };
+ SelectedMods.Value = new[] { ruleset.CreateMod() };
});
AddAssert("circle size bar is blue", () => barIsBlue(advancedStats.FirstValue));
@@ -106,7 +106,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select HR mod", () =>
{
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
- SelectedMods.Value = new[] { ruleset.GetAllMods().OfType().Single() };
+ SelectedMods.Value = new[] { ruleset.CreateMod() };
});
AddAssert("circle size bar is red", () => barIsRed(advancedStats.FirstValue));
@@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select unchanged Difficulty Adjust mod", () =>
{
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
- var difficultyAdjustMod = ruleset.GetAllMods().OfType().Single();
+ var difficultyAdjustMod = ruleset.CreateMod();
difficultyAdjustMod.ReadFromDifficulty(advancedStats.Beatmap.BaseDifficulty);
SelectedMods.Value = new[] { difficultyAdjustMod };
});
@@ -142,7 +142,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select changed Difficulty Adjust mod", () =>
{
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
- var difficultyAdjustMod = ruleset.GetAllMods().OfType().Single();
+ var difficultyAdjustMod = ruleset.CreateMod();
var originalDifficulty = advancedStats.Beatmap.BaseDifficulty;
difficultyAdjustMod.ReadFromDifficulty(originalDifficulty);
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
index 184a2e59da..29815ce9ff 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.SongSelect
dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default));
- dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory));
+ dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler));
return dependencies;
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs
index 66ac700c51..f91d3f595b 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs
@@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{
AddStep("setup display", () =>
{
- var randomMods = Ruleset.Value.CreateInstance().GetAllMods().OrderBy(_ => RNG.Next()).Take(5).ToList();
+ var randomMods = Ruleset.Value.CreateInstance().CreateAllMods().OrderBy(_ => RNG.Next()).Take(5).ToList();
OsuLogo logo = new OsuLogo { Scale = new Vector2(0.15f) };
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs
index abd1baf0ac..008d91f649 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
+using Humanizer;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -73,7 +74,7 @@ namespace osu.Game.Tests.Visual.UserInterface
};
control.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true);
- control.General.BindCollectionChanged((u, v) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().ToLowerInvariant())) : "")}", true);
+ control.General.BindCollectionChanged((u, v) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().Underscore())) : "")}", true);
control.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true);
control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true);
control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true);
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
index 3f9e0048dd..2e30ed9827 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.UserInterface
private BeatmapManager beatmapManager;
private ScoreManager scoreManager;
- private readonly List scores = new List();
+ private readonly List importedScores = new List();
private BeatmapInfo beatmap;
[Cached]
@@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.UserInterface
dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default));
- dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory));
+ dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler));
beatmap = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Beatmaps[0];
@@ -100,11 +100,9 @@ namespace osu.Game.Tests.Visual.UserInterface
User = new User { Username = "TestUser" },
};
- scores.Add(scoreManager.Import(score).Result);
+ importedScores.Add(scoreManager.Import(score).Result);
}
- scores.Sort(Comparer.Create((s1, s2) => s2.TotalScore.CompareTo(s1.TotalScore)));
-
return dependencies;
}
@@ -134,9 +132,14 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestDeleteViaRightClick()
{
+ ScoreInfo scoreBeingDeleted = null;
AddStep("open menu for top score", () =>
{
- InputManager.MoveMouseTo(leaderboard.ChildrenOfType().First());
+ var leaderboardScore = leaderboard.ChildrenOfType().First();
+
+ scoreBeingDeleted = leaderboardScore.Score;
+
+ InputManager.MoveMouseTo(leaderboardScore);
InputManager.Click(MouseButton.Right);
});
@@ -158,14 +161,14 @@ namespace osu.Game.Tests.Visual.UserInterface
InputManager.Click(MouseButton.Left);
});
- AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scores[0].OnlineScoreID));
+ AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scoreBeingDeleted.OnlineScoreID));
}
[Test]
public void TestDeleteViaDatabase()
{
- AddStep("delete top score", () => scoreManager.Delete(scores[0]));
- AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scores[0].OnlineScoreID));
+ AddStep("delete top score", () => scoreManager.Delete(importedScores[0]));
+ AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != importedScores[0].OnlineScoreID));
}
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModFlowDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModFlowDisplay.cs
index 8f057c663b..10eab148de 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModFlowDisplay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModFlowDisplay.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Width = 200,
Current =
{
- Value = new OsuRuleset().GetAllMods().ToArray(),
+ Value = new OsuRuleset().CreateAllMods().ToArray(),
}
};
});
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs
index e7fa7d9235..513eb2fafc 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using NUnit.Framework;
+using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI;
@@ -17,5 +19,16 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("create mod icon", () => Child = icon = new ModIcon(new OsuModDoubleTime()));
AddStep("change mod", () => icon.Mod = new OsuModEasy());
}
+
+ [Test]
+ public void TestInterfaceModType()
+ {
+ ModIcon icon = null;
+
+ var ruleset = new OsuRuleset();
+
+ AddStep("create mod icon", () => Child = icon = new ModIcon(ruleset.AllMods.First(m => m.Acronym == "DT")));
+ AddStep("change mod", () => icon.Mod = ruleset.AllMods.First(m => m.Acronym == "EZ"));
+ }
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
index 9e253e089d..4f7aec3b67 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
@@ -158,8 +158,8 @@ namespace osu.Game.Tests.Visual.UserInterface
var mania = new ManiaRuleset();
testModsWithSameBaseType(
- mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModFadeIn)),
- mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModHidden)));
+ mania.CreateMod(),
+ mania.CreateMod());
}
[Test]
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs
index b4d9fa4222..47e7ed9b61 100644
--- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs
+++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Tests.Components
private void success(APIBeatmap apiBeatmap)
{
beatmap = apiBeatmap.ToBeatmap(rulesets);
- var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().GetAllMods();
+ var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().AllMods;
foreach (var mod in mods)
{
diff --git a/osu.Game.Tournament/Components/TournamentModIcon.cs b/osu.Game.Tournament/Components/TournamentModIcon.cs
index 43ac92d285..7c4e9c69a2 100644
--- a/osu.Game.Tournament/Components/TournamentModIcon.cs
+++ b/osu.Game.Tournament/Components/TournamentModIcon.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -49,7 +48,7 @@ namespace osu.Game.Tournament.Components
}
var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.ID ?? 0);
- var modIcon = ruleset?.CreateInstance().GetAllMods().FirstOrDefault(mod => mod.Acronym == modAcronym);
+ var modIcon = ruleset?.CreateInstance().CreateModFromAcronym(modAcronym);
if (modIcon == null)
return;
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index 3eb766a667..8cb5da8083 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -93,6 +93,12 @@ namespace osu.Game.Beatmaps
public bool WidescreenStoryboard { get; set; }
public bool EpilepsyWarning { get; set; }
+ ///
+ /// Whether or not sound samples should change rate when playing with speed-changing mods.
+ /// TODO: only read/write supported for now, requires implementation in gameplay.
+ ///
+ public bool SamplesMatchPlaybackRate { get; set; }
+
public CountdownType Countdown { get; set; } = CountdownType.Normal;
///
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 27aa874dc9..bd85017d58 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -129,6 +129,7 @@ namespace osu.Game.Beatmaps
Ruleset = ruleset,
Metadata = metadata,
WidescreenStoryboard = true,
+ SamplesMatchPlaybackRate = true,
}
}
};
diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs
index 48f1f0ce68..3658dbab83 100644
--- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs
@@ -90,6 +90,12 @@ namespace osu.Game.Beatmaps
/// The song language of this beatmap set.
///
public BeatmapSetOnlineLanguage Language { get; set; }
+
+ ///
+ /// The track ID of this beatmap set.
+ /// Non-null only if the track is linked to a featured artist track entry.
+ ///
+ public int? TrackId { get; set; }
}
public class BeatmapSetOnlineGenre
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index accefb2583..4b5eaafa4a 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -180,6 +180,10 @@ namespace osu.Game.Beatmaps.Formats
beatmap.BeatmapInfo.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1;
break;
+ case @"SamplesMatchPlaybackRate":
+ beatmap.BeatmapInfo.SamplesMatchPlaybackRate = Parsing.ParseInt(pair.Value) == 1;
+ break;
+
case @"Countdown":
beatmap.BeatmapInfo.Countdown = (CountdownType)Enum.Parse(typeof(CountdownType), pair.Value);
break;
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
index 75d9a56f3e..aef13b8872 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
@@ -105,8 +105,8 @@ namespace osu.Game.Beatmaps.Formats
if (beatmap.BeatmapInfo.RulesetID == 3)
writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.BeatmapInfo.SpecialStyle ? '1' : '0')}"));
writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}"));
- // if (b.SamplesMatchPlaybackRate)
- // writer.WriteLine(@"SamplesMatchPlaybackRate: 1");
+ if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate)
+ writer.WriteLine(@"SamplesMatchPlaybackRate: 1");
}
private void handleEditor(TextWriter writer)
diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs
deleted file mode 100644
index b3783b45a8..0000000000
--- a/osu.Game/Configuration/DatabasedConfigManager.cs
+++ /dev/null
@@ -1,103 +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 System.Collections.Generic;
-using System.Linq;
-using osu.Framework.Bindables;
-using osu.Framework.Configuration;
-using osu.Game.Rulesets;
-
-namespace osu.Game.Configuration
-{
- public abstract class DatabasedConfigManager : ConfigManager
- where TLookup : struct, Enum
- {
- private readonly SettingsStore settings;
-
- private readonly int? variant;
-
- private List databasedSettings;
-
- private readonly RulesetInfo ruleset;
-
- private bool legacySettingsExist;
-
- protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int? variant = null)
- {
- this.settings = settings;
- this.ruleset = ruleset;
- this.variant = variant;
-
- Load();
-
- InitialiseDefaults();
- }
-
- protected override void PerformLoad()
- {
- databasedSettings = settings.Query(ruleset?.ID, variant);
- legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out _));
- }
-
- protected override bool PerformSave()
- {
- lock (dirtySettings)
- {
- foreach (var setting in dirtySettings)
- settings.Update(setting);
- dirtySettings.Clear();
- }
-
- return true;
- }
-
- private readonly List dirtySettings = new List();
-
- protected override void AddBindable(TLookup lookup, Bindable bindable)
- {
- base.AddBindable(lookup, bindable);
-
- if (legacySettingsExist)
- {
- var legacySetting = databasedSettings.Find(s => s.Key == ((int)(object)lookup).ToString());
-
- if (legacySetting != null)
- {
- bindable.Parse(legacySetting.Value);
- settings.Delete(legacySetting);
- }
- }
-
- var setting = databasedSettings.Find(s => s.Key == lookup.ToString());
-
- if (setting != null)
- {
- bindable.Parse(setting.Value);
- }
- else
- {
- settings.Update(setting = new DatabasedSetting
- {
- Key = lookup.ToString(),
- Value = bindable.Value,
- RulesetID = ruleset?.ID,
- Variant = variant,
- });
-
- databasedSettings.Add(setting);
- }
-
- bindable.ValueChanged += b =>
- {
- setting.Value = b.NewValue;
-
- lock (dirtySettings)
- {
- if (!dirtySettings.Contains(setting))
- dirtySettings.Add(setting);
- }
- };
- }
- }
-}
diff --git a/osu.Game/Configuration/DatabasedSetting.cs b/osu.Game/Configuration/DatabasedSetting.cs
index f5c92b3029..fe1d51d57f 100644
--- a/osu.Game/Configuration/DatabasedSetting.cs
+++ b/osu.Game/Configuration/DatabasedSetting.cs
@@ -7,7 +7,7 @@ using osu.Game.Database;
namespace osu.Game.Configuration
{
[Table("Settings")]
- public class DatabasedSetting : IHasPrimaryKey
+ public class DatabasedSetting : IHasPrimaryKey // can be removed 20220315.
{
public int ID { get; set; }
diff --git a/osu.Game/Configuration/RealmRulesetSetting.cs b/osu.Game/Configuration/RealmRulesetSetting.cs
new file mode 100644
index 0000000000..07e56ad8dd
--- /dev/null
+++ b/osu.Game/Configuration/RealmRulesetSetting.cs
@@ -0,0 +1,32 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Game.Database;
+using Realms;
+
+#nullable enable
+
+namespace osu.Game.Configuration
+{
+ [MapTo(@"RulesetSetting")]
+ public class RealmRulesetSetting : RealmObject, IHasGuidPrimaryKey
+ {
+ [PrimaryKey]
+ public Guid ID { get; set; } = Guid.NewGuid();
+
+ [Indexed]
+ public int RulesetID { get; set; }
+
+ [Indexed]
+ public int Variant { get; set; }
+
+ [Required]
+ public string Key { get; set; } = string.Empty;
+
+ [Required]
+ public string Value { get; set; } = string.Empty;
+
+ public override string ToString() => $"{Key} => {Value}";
+ }
+}
diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs
index f373e59417..5db502804d 100644
--- a/osu.Game/Configuration/SettingSourceAttribute.cs
+++ b/osu.Game/Configuration/SettingSourceAttribute.cs
@@ -4,6 +4,7 @@
#nullable enable
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@@ -167,9 +168,21 @@ namespace osu.Game.Configuration
}
}
+ private static readonly ConcurrentDictionary property_info_cache = new ConcurrentDictionary();
+
public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourceProperties(this object obj)
{
- foreach (var property in obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance))
+ var type = obj.GetType();
+
+ if (!property_info_cache.TryGetValue(type, out var properties))
+ property_info_cache[type] = properties = getSettingsSourceProperties(type).ToArray();
+
+ return properties;
+ }
+
+ private static IEnumerable<(SettingSourceAttribute, PropertyInfo)> getSettingsSourceProperties(Type type)
+ {
+ foreach (var property in type.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance))
{
var attr = property.GetCustomAttribute(true);
diff --git a/osu.Game/Configuration/SettingsStore.cs b/osu.Game/Configuration/SettingsStore.cs
index 86e84b0732..2bba20fb09 100644
--- a/osu.Game/Configuration/SettingsStore.cs
+++ b/osu.Game/Configuration/SettingsStore.cs
@@ -1,46 +1,20 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
-using System.Collections.Generic;
-using System.Linq;
using osu.Game.Database;
namespace osu.Game.Configuration
{
- public class SettingsStore : DatabaseBackedStore
+ public class SettingsStore
{
- public event Action SettingChanged;
+ // this class mostly exists as a wrapper to avoid breaking the ruleset API (see usage in RulesetConfigManager).
+ // it may cease to exist going forward, depending on how the structure of the config data layer changes.
- public SettingsStore(DatabaseContextFactory contextFactory)
- : base(contextFactory)
+ public readonly RealmContextFactory Realm;
+
+ public SettingsStore(RealmContextFactory realmFactory)
{
- }
-
- ///
- /// Retrieve s for a specified ruleset/variant content.
- ///
- /// The ruleset's internal ID.
- /// An optional variant.
- public List Query(int? rulesetId = null, int? variant = null) =>
- ContextFactory.Get().DatabasedSetting.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList();
-
- public void Update(DatabasedSetting setting)
- {
- using (ContextFactory.GetForWrite())
- {
- var newValue = setting.Value;
- Refresh(ref setting);
- setting.Value = newValue;
- }
-
- SettingChanged?.Invoke();
- }
-
- public void Delete(DatabasedSetting setting)
- {
- using (var usage = ContextFactory.GetForWrite())
- usage.Context.Remove(setting);
+ Realm = realmFactory;
}
}
}
diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs
index 68d186c65d..1d8322aadd 100644
--- a/osu.Game/Database/OsuDbContext.cs
+++ b/osu.Game/Database/OsuDbContext.cs
@@ -12,7 +12,6 @@ using osu.Game.Configuration;
using osu.Game.IO;
using osu.Game.Rulesets;
using osu.Game.Scoring;
-using DatabasedKeyBinding = osu.Game.Input.Bindings.DatabasedKeyBinding;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
using osu.Game.Skinning;
@@ -24,14 +23,13 @@ namespace osu.Game.Database
public DbSet BeatmapDifficulty { get; set; }
public DbSet BeatmapMetadata { get; set; }
public DbSet BeatmapSetInfo { get; set; }
- public DbSet DatabasedSetting { get; set; }
public DbSet FileInfo { get; set; }
public DbSet RulesetInfo { get; set; }
public DbSet SkinInfo { get; set; }
public DbSet ScoreInfo { get; set; }
// migrated to realm
- public DbSet DatabasedKeyBinding { get; set; }
+ public DbSet DatabasedSetting { get; set; }
private readonly string connectionString;
@@ -138,11 +136,6 @@ namespace osu.Game.Database
modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique();
modelBuilder.Entity().HasIndex(b => b.DeletePending);
- modelBuilder.Entity().HasIndex(b => new { b.RulesetID, b.Variant });
- modelBuilder.Entity().HasIndex(b => b.IntAction);
- modelBuilder.Entity().Ignore(b => b.KeyCombination);
- modelBuilder.Entity().Ignore(b => b.Action);
-
modelBuilder.Entity().HasIndex(b => new { b.RulesetID, b.Variant });
modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique();
diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs
index 52d8230fb6..03cc345947 100644
--- a/osu.Game/Extensions/DrawableExtensions.cs
+++ b/osu.Game/Extensions/DrawableExtensions.cs
@@ -38,6 +38,33 @@ namespace osu.Game.Extensions
return repeatDelegate;
}
+ ///
+ /// Shakes this drawable.
+ ///
+ /// The target to shake.
+ /// The length of a single shake.
+ /// Pixels of displacement per shake.
+ /// The maximum length the shake should last.
+ public static void Shake(this Drawable target, double shakeDuration = 80, float shakeMagnitude = 8, double? maximumLength = null)
+ {
+ // if we don't have enough time, don't bother shaking.
+ if (maximumLength < shakeDuration * 2)
+ return;
+
+ var sequence = target.MoveToX(shakeMagnitude, shakeDuration / 2, Easing.OutSine).Then()
+ .MoveToX(-shakeMagnitude, shakeDuration, Easing.InOutSine).Then();
+
+ // if we don't have enough time for the second shake, skip it.
+ if (!maximumLength.HasValue || maximumLength >= shakeDuration * 4)
+ {
+ sequence = sequence
+ .MoveToX(shakeMagnitude, shakeDuration, Easing.InOutSine).Then()
+ .MoveToX(-shakeMagnitude, shakeDuration, Easing.InOutSine).Then();
+ }
+
+ sequence.MoveToX(0, shakeDuration / 2, Easing.InSine);
+ }
+
///
/// Accepts a delta vector in screen-space coordinates and converts it to one which can be applied to this drawable's position.
///
diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs
index 85ef779e48..21c1d70d45 100644
--- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs
+++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs
@@ -87,23 +87,25 @@ namespace osu.Game.Graphics.Containers
private void createLink(IEnumerable drawables, LinkDetails link, string tooltipText, Action action = null)
{
- AddInternal(new DrawableLinkCompiler(drawables.OfType().ToList())
+ var linkCompiler = CreateLinkCompiler(drawables.OfType());
+ linkCompiler.RelativeSizeAxes = Axes.Both;
+ linkCompiler.TooltipText = tooltipText;
+ linkCompiler.Action = () =>
{
- RelativeSizeAxes = Axes.Both,
- TooltipText = tooltipText,
- Action = () =>
- {
- if (action != null)
- action();
- else if (game != null)
- game.HandleLink(link);
- // fallback to handle cases where OsuGame is not available, ie. tournament client.
- else if (link.Action == LinkAction.External)
- host.OpenUrlExternally(link.Argument);
- },
- });
+ if (action != null)
+ action();
+ else if (game != null)
+ game.HandleLink(link);
+ // fallback to handle cases where OsuGame is not available, ie. tournament client.
+ else if (link.Action == LinkAction.External)
+ host.OpenUrlExternally(link.Argument);
+ };
+
+ AddInternal(linkCompiler);
}
+ protected virtual DrawableLinkCompiler CreateLinkCompiler(IEnumerable parts) => new DrawableLinkCompiler(parts);
+
// We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used.
// However due to https://github.com/ppy/osu-framework/issues/2073, it's possible for the compilers to be relative size in the flow's auto-size axes - an unsupported operation.
// Since the compilers don't display any content and don't affect the layout, it's simplest to exclude them from the flow.
diff --git a/osu.Game/Graphics/Containers/ShakeContainer.cs b/osu.Game/Graphics/Containers/ShakeContainer.cs
index dca9df1e98..8a0ce287db 100644
--- a/osu.Game/Graphics/Containers/ShakeContainer.cs
+++ b/osu.Game/Graphics/Containers/ShakeContainer.cs
@@ -1,8 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Extensions;
namespace osu.Game.Graphics.Containers
{
@@ -16,40 +16,10 @@ namespace osu.Game.Graphics.Containers
///
public float ShakeDuration = 80;
- ///
- /// Total number of shakes. May be shortened if possible.
- ///
- public float TotalShakes = 4;
-
- ///
- /// Pixels of displacement per shake.
- ///
- public float ShakeMagnitude = 8;
-
///
/// Shake the contents of this container.
///
/// The maximum length the shake should last.
- public void Shake(double? maximumLength = null)
- {
- const float shake_amount = 8;
-
- // if we don't have enough time, don't bother shaking.
- if (maximumLength < ShakeDuration * 2)
- return;
-
- var sequence = this.MoveToX(shake_amount, ShakeDuration / 2, Easing.OutSine).Then()
- .MoveToX(-shake_amount, ShakeDuration, Easing.InOutSine).Then();
-
- // if we don't have enough time for the second shake, skip it.
- if (!maximumLength.HasValue || maximumLength >= ShakeDuration * 4)
- {
- sequence = sequence
- .MoveToX(shake_amount, ShakeDuration, Easing.InOutSine).Then()
- .MoveToX(-shake_amount, ShakeDuration, Easing.InOutSine).Then();
- }
-
- sequence.MoveToX(0, ShakeDuration / 2, Easing.InSine);
- }
+ public void Shake(double? maximumLength = null) => this.Shake(ShakeDuration, maximumLength: maximumLength);
}
}
diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
index ed9f0710b0..6c8238a1b8 100644
--- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
@@ -70,7 +70,7 @@ namespace osu.Game.Graphics.UserInterface
return base.OnKeyDown(e);
}
- public bool OnPressed(GlobalAction action)
+ public virtual bool OnPressed(GlobalAction action)
{
if (!HasFocus) return false;
diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs
index 664f32b083..6807d007bb 100644
--- a/osu.Game/Graphics/UserInterface/Nub.cs
+++ b/osu.Game/Graphics/UserInterface/Nub.cs
@@ -46,7 +46,11 @@ namespace osu.Game.Graphics.UserInterface
},
};
- Current.ValueChanged += filled => fill.FadeTo(filled.NewValue ? 1 : 0, 200, Easing.OutQuint);
+ Current.ValueChanged += filled =>
+ {
+ fill.FadeTo(filled.NewValue ? 1 : 0, 200, Easing.OutQuint);
+ this.TransformTo(nameof(BorderThickness), filled.NewValue ? 8.5f : border_width, 200, Easing.OutQuint);
+ };
}
[BackgroundDependencyLoader]
diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs
index c07a5de1e4..2cb696be0a 100644
--- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs
@@ -4,14 +4,17 @@
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input.Bindings;
+using osu.Game.Input.Bindings;
using osu.Game.Overlays;
using osuTK;
namespace osu.Game.Graphics.UserInterfaceV2
{
- public class OsuPopover : Popover
+ public class OsuPopover : Popover, IKeyBindingHandler
{
private const float fade_duration = 250;
private const double scale_duration = 500;
@@ -51,5 +54,23 @@ namespace osu.Game.Graphics.UserInterfaceV2
this.ScaleTo(0.7f, scale_duration, Easing.OutQuint);
this.FadeOut(fade_duration, Easing.OutQuint);
}
+
+ public bool OnPressed(GlobalAction action)
+ {
+ if (State.Value == Visibility.Hidden)
+ return false;
+
+ if (action == GlobalAction.Back)
+ {
+ Hide();
+ return true;
+ }
+
+ return false;
+ }
+
+ public void OnReleased(GlobalAction action)
+ {
+ }
}
}
diff --git a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs
deleted file mode 100644
index ad3493d0fc..0000000000
--- a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs
+++ /dev/null
@@ -1,39 +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.ComponentModel.DataAnnotations.Schema;
-using osu.Framework.Input.Bindings;
-using osu.Game.Database;
-
-namespace osu.Game.Input.Bindings
-{
- [Table("KeyBinding")]
- public class DatabasedKeyBinding : IKeyBinding, IHasPrimaryKey
- {
- public int ID { get; set; }
-
- public int? RulesetID { get; set; }
-
- public int? Variant { get; set; }
-
- [Column("Keys")]
- public string KeysString { get; set; }
-
- [Column("Action")]
- public int IntAction { get; set; }
-
- [NotMapped]
- public KeyCombination KeyCombination
- {
- get => KeysString;
- set => KeysString = value.ToString();
- }
-
- [NotMapped]
- public object Action
- {
- get => IntAction;
- set => IntAction = (int)value;
- }
- }
-}
diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs
new file mode 100644
index 0000000000..6e53d7fae0
--- /dev/null
+++ b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs
@@ -0,0 +1,515 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using osu.Game.Database;
+
+namespace osu.Game.Migrations
+{
+ [DbContext(typeof(OsuDbContext))]
+ [Migration("20210912144011_AddSamplesMatchPlaybackRate")]
+ partial class AddSamplesMatchPlaybackRate
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "2.2.6-servicing-10079");
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ApproachRate");
+
+ b.Property("CircleSize");
+
+ b.Property("DrainRate");
+
+ b.Property("OverallDifficulty");
+
+ b.Property("SliderMultiplier");
+
+ b.Property("SliderTickRate");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapDifficulty");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("AudioLeadIn");
+
+ b.Property("BPM");
+
+ b.Property("BaseDifficultyID");
+
+ b.Property("BeatDivisor");
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("Countdown");
+
+ b.Property("CountdownOffset");
+
+ b.Property("DistanceSpacing");
+
+ b.Property("EpilepsyWarning");
+
+ b.Property("GridSize");
+
+ b.Property("Hash");
+
+ b.Property("Hidden");
+
+ b.Property("Length");
+
+ b.Property("LetterboxInBreaks");
+
+ b.Property("MD5Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapID");
+
+ b.Property("Path");
+
+ b.Property("RulesetID");
+
+ b.Property("SamplesMatchPlaybackRate");
+
+ b.Property("SpecialStyle");
+
+ b.Property("StackLeniency");
+
+ b.Property("StarDifficulty");
+
+ b.Property("Status");
+
+ b.Property("StoredBookmarks");
+
+ b.Property("TimelineZoom");
+
+ b.Property("Version");
+
+ b.Property("WidescreenStoryboard");
+
+ b.HasKey("ID");
+
+ b.HasIndex("BaseDifficultyID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("Hash");
+
+ b.HasIndex("MD5Hash");
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapID")
+ .IsUnique();
+
+ b.HasIndex("RulesetID");
+
+ b.ToTable("BeatmapInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Artist");
+
+ b.Property("ArtistUnicode");
+
+ b.Property("AudioFile");
+
+ b.Property("AuthorID")
+ .HasColumnName("AuthorID");
+
+ b.Property("AuthorString")
+ .HasColumnName("Author");
+
+ b.Property("BackgroundFile");
+
+ b.Property("PreviewTime");
+
+ b.Property("Source");
+
+ b.Property("Tags");
+
+ b.Property("Title");
+
+ b.Property("TitleUnicode");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapMetadata");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.HasKey("ID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("FileInfoID");
+
+ b.ToTable("BeatmapSetFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("DateAdded");
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapSetID");
+
+ b.Property("Protected");
+
+ b.Property("Status");
+
+ b.HasKey("ID");
+
+ b.HasIndex("DeletePending");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapSetID")
+ .IsUnique();
+
+ b.ToTable("BeatmapSetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Key")
+ .HasColumnName("Key");
+
+ b.Property("RulesetID");
+
+ b.Property("SkinInfoID");
+
+ b.Property("StringValue")
+ .HasColumnName("Value");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("SkinInfoID");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("Settings");
+ });
+
+ modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Hash");
+
+ b.Property("ReferenceCount");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("ReferenceCount");
+
+ b.ToTable("FileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("IntAction")
+ .HasColumnName("Action");
+
+ b.Property("KeysString")
+ .HasColumnName("Keys");
+
+ b.Property("RulesetID");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("IntAction");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("KeyBinding");
+ });
+
+ modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Available");
+
+ b.Property("InstantiationInfo");
+
+ b.Property("Name");
+
+ b.Property("ShortName");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Available");
+
+ b.HasIndex("ShortName")
+ .IsUnique();
+
+ b.ToTable("RulesetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.Property("ScoreInfoID");
+
+ b.HasKey("ID");
+
+ b.HasIndex("FileInfoID");
+
+ b.HasIndex("ScoreInfoID");
+
+ b.ToTable("ScoreFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Accuracy")
+ .HasColumnType("DECIMAL(1,4)");
+
+ b.Property("BeatmapInfoID");
+
+ b.Property("Combo");
+
+ b.Property("Date");
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("MaxCombo");
+
+ b.Property("ModsJson")
+ .HasColumnName("Mods");
+
+ b.Property("OnlineScoreID");
+
+ b.Property("PP");
+
+ b.Property("Rank");
+
+ b.Property("RulesetID");
+
+ b.Property("StatisticsJson")
+ .HasColumnName("Statistics");
+
+ b.Property("TotalScore");
+
+ b.Property("UserID")
+ .HasColumnName("UserID");
+
+ b.Property("UserString")
+ .HasColumnName("User");
+
+ b.HasKey("ID");
+
+ b.HasIndex("BeatmapInfoID");
+
+ b.HasIndex("OnlineScoreID")
+ .IsUnique();
+
+ b.HasIndex("RulesetID");
+
+ b.ToTable("ScoreInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.Property("SkinInfoID");
+
+ b.HasKey("ID");
+
+ b.HasIndex("FileInfoID");
+
+ b.HasIndex("SkinInfoID");
+
+ b.ToTable("SkinFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Creator");
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("InstantiationInfo");
+
+ b.Property("Name");
+
+ b.HasKey("ID");
+
+ b.HasIndex("DeletePending");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.ToTable("SkinInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
+ .WithMany()
+ .HasForeignKey("BaseDifficultyID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
+ .WithMany("Beatmaps")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("Beatmaps")
+ .HasForeignKey("MetadataID");
+
+ b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+ .WithMany()
+ .HasForeignKey("RulesetID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
+ .WithMany("Files")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("BeatmapSets")
+ .HasForeignKey("MetadataID");
+ });
+
+ modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+ {
+ b.HasOne("osu.Game.Skinning.SkinInfo")
+ .WithMany("Settings")
+ .HasForeignKey("SkinInfoID");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
+ {
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Scoring.ScoreInfo")
+ .WithMany("Files")
+ .HasForeignKey("ScoreInfoID");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap")
+ .WithMany("Scores")
+ .HasForeignKey("BeatmapInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+ .WithMany()
+ .HasForeignKey("RulesetID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+ {
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Skinning.SkinInfo")
+ .WithMany("Files")
+ .HasForeignKey("SkinInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs
new file mode 100644
index 0000000000..bf3f855d5f
--- /dev/null
+++ b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs
@@ -0,0 +1,23 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace osu.Game.Migrations
+{
+ public partial class AddSamplesMatchPlaybackRate : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "SamplesMatchPlaybackRate",
+ table: "BeatmapInfo",
+ nullable: false,
+ defaultValue: false);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "SamplesMatchPlaybackRate",
+ table: "BeatmapInfo");
+ }
+ }
+}
diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
index 470907ada6..036c26cb0a 100644
--- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
+++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
@@ -81,6 +81,8 @@ namespace osu.Game.Migrations
b.Property("RulesetID");
+ b.Property("SamplesMatchPlaybackRate");
+
b.Property("SpecialStyle");
b.Property("StackLeniency");
diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs
index 4427c82a8b..62f9976c0f 100644
--- a/osu.Game/Online/API/APIMod.cs
+++ b/osu.Game/Online/API/APIMod.cs
@@ -16,7 +16,7 @@ using osu.Game.Utils;
namespace osu.Game.Online.API
{
[MessagePackObject]
- public class APIMod : IMod, IEquatable
+ public class APIMod : IEquatable
{
[JsonProperty("acronym")]
[Key(0)]
@@ -48,31 +48,31 @@ namespace osu.Game.Online.API
public Mod ToMod(Ruleset ruleset)
{
- Mod resultMod = ruleset.GetAllMods().FirstOrDefault(m => m.Acronym == Acronym);
+ Mod resultMod = ruleset.CreateModFromAcronym(Acronym);
if (resultMod == null)
throw new InvalidOperationException($"There is no mod in the ruleset ({ruleset.ShortName}) matching the acronym {Acronym}.");
- foreach (var (_, property) in resultMod.GetSettingsSourceProperties())
+ if (Settings.Count > 0)
{
- if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue))
- continue;
+ foreach (var (_, property) in resultMod.GetSettingsSourceProperties())
+ {
+ if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue))
+ continue;
- resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue);
+ resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue);
+ }
}
return resultMod;
}
- public bool Equals(IMod other) => other is APIMod them && Equals(them);
-
public bool Equals(APIMod other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
- return Acronym == other.Acronym &&
- Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default);
+ return Acronym == other.Acronym && Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default);
}
public override string ToString()
diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs
index bf3441d2a0..b4e0e44b2c 100644
--- a/osu.Game/Online/API/Requests/GetScoresRequest.cs
+++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs
@@ -18,9 +18,9 @@ namespace osu.Game.Online.API.Requests
private readonly BeatmapInfo beatmap;
private readonly BeatmapLeaderboardScope scope;
private readonly RulesetInfo ruleset;
- private readonly IEnumerable mods;
+ private readonly IEnumerable mods;
- public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null)
+ public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null)
{
if (!beatmap.OnlineBeatmapID.HasValue)
throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}.");
@@ -31,7 +31,7 @@ namespace osu.Game.Online.API.Requests
this.beatmap = beatmap;
this.scope = scope;
this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset));
- this.mods = mods ?? Array.Empty();
+ this.mods = mods ?? Array.Empty();
Success += onSuccess;
}
diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs
index e49c4ab298..730e4e02ed 100644
--- a/osu.Game/Online/API/Requests/GetUserRequest.cs
+++ b/osu.Game/Online/API/Requests/GetUserRequest.cs
@@ -8,7 +8,7 @@ namespace osu.Game.Online.API.Requests
{
public class GetUserRequest : APIRequest
{
- private readonly string lookup;
+ public readonly string Lookup;
public readonly RulesetInfo Ruleset;
private readonly LookupType lookupType;
@@ -26,7 +26,7 @@ namespace osu.Game.Online.API.Requests
/// The ruleset to get the user's info for.
public GetUserRequest(long? userId = null, RulesetInfo ruleset = null)
{
- lookup = userId.ToString();
+ Lookup = userId.ToString();
lookupType = LookupType.Id;
Ruleset = ruleset;
}
@@ -38,12 +38,12 @@ namespace osu.Game.Online.API.Requests
/// The ruleset to get the user's info for.
public GetUserRequest(string username = null, RulesetInfo ruleset = null)
{
- lookup = username;
+ Lookup = username;
lookupType = LookupType.Username;
Ruleset = ruleset;
}
- protected override string Target => lookup != null ? $@"users/{lookup}/{Ruleset?.ShortName}?k={lookupType.ToString().ToLower()}" : $@"me/{Ruleset?.ShortName}";
+ protected override string Target => Lookup != null ? $@"users/{Lookup}/{Ruleset?.ShortName}?key={lookupType.ToString().ToLower()}" : $@"me/{Ruleset?.ShortName}";
private enum LookupType
{
diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs
index 45d9c9405f..f653a654ca 100644
--- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs
@@ -63,6 +63,9 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"ratings")]
private int[] ratings { get; set; }
+ [JsonProperty(@"track_id")]
+ private int? trackId { get; set; }
+
[JsonProperty(@"user_id")]
private int creatorId
{
@@ -106,7 +109,8 @@ namespace osu.Game.Online.API.Requests.Responses
Availability = availability,
HasFavourited = hasFavourited,
Genre = genre,
- Language = language
+ Language = language,
+ TrackId = trackId
},
};
diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs
index 1b394185fd..567df524b1 100644
--- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs
+++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs
@@ -23,10 +23,10 @@ namespace osu.Game.Online.API.Requests.Responses
var rulesetInstance = ruleset.CreateInstance();
- var mods = Mods != null ? rulesetInstance.GetAllMods().Where(mod => Mods.Contains(mod.Acronym)).ToArray() : Array.Empty();
+ var mods = Mods != null ? Mods.Select(acronym => rulesetInstance.CreateModFromAcronym(acronym)).Where(m => m != null).ToArray() : Array.Empty();
// all API scores provided by this class are considered to be legacy.
- mods = mods.Append(rulesetInstance.GetAllMods().OfType().Single()).ToArray();
+ mods = mods.Append(rulesetInstance.CreateMod()).ToArray();
var scoreInfo = new ScoreInfo
{
diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
index 8ce495e274..ae082ca82e 100644
--- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
+++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
+using Humanizer;
using JetBrains.Annotations;
using osu.Framework.IO.Network;
using osu.Game.Extensions;
@@ -83,7 +84,7 @@ namespace osu.Game.Online.API.Requests
req.AddParameter("q", query);
if (General != null && General.Any())
- req.AddParameter("c", string.Join('.', General.Select(e => e.ToString().ToLowerInvariant())));
+ req.AddParameter("c", string.Join('.', General.Select(e => e.ToString().Underscore())));
if (ruleset.ID.HasValue)
req.AddParameter("m", ruleset.ID.Value.ToString());
diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs
index 1937019ef6..47d5955fb0 100644
--- a/osu.Game/Online/Chat/ChannelManager.cs
+++ b/osu.Game/Online/Chat/ChannelManager.cs
@@ -256,8 +256,36 @@ namespace osu.Game.Online.Chat
JoinChannel(channel);
break;
+ case "chat":
+ case "msg":
+ case "query":
+ if (string.IsNullOrWhiteSpace(content))
+ {
+ target.AddNewMessages(new ErrorMessage($"Usage: /{command} [user]"));
+ break;
+ }
+
+ // Check if the user has joined the requested channel already.
+ // This uses the channel name for comparison as the PM user's username is unavailable after a restart.
+ var privateChannel = JoinedChannels.FirstOrDefault(
+ c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Name.Equals(content, StringComparison.OrdinalIgnoreCase));
+
+ if (privateChannel != null)
+ {
+ CurrentChannel.Value = privateChannel;
+ break;
+ }
+
+ var request = new GetUserRequest(content);
+ request.Success += OpenPrivateChannel;
+ request.Failure += e => target.AddNewMessages(
+ new ErrorMessage(e.InnerException?.Message == @"NotFound" ? $"User '{content}' was not found." : $"Could not fetch user '{content}'."));
+
+ api.Queue(request);
+ break;
+
case "help":
- target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /np"));
+ target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /chat [user], /np"));
break;
default:
diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs
index 934b905a1a..26749a23f9 100644
--- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs
+++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs
@@ -34,6 +34,8 @@ namespace osu.Game.Online.Leaderboards
{
public const float HEIGHT = 60;
+ public readonly ScoreInfo Score;
+
private const float corner_radius = 5;
private const float edge_margin = 5;
private const float background_alpha = 0.25f;
@@ -41,7 +43,6 @@ namespace osu.Game.Online.Leaderboards
protected Container RankContainer { get; private set; }
- private readonly ScoreInfo score;
private readonly int? rank;
private readonly bool allowHighlight;
@@ -67,7 +68,8 @@ namespace osu.Game.Online.Leaderboards
public LeaderboardScore(ScoreInfo score, int? rank, bool allowHighlight = true)
{
- this.score = score;
+ Score = score;
+
this.rank = rank;
this.allowHighlight = allowHighlight;
@@ -78,9 +80,9 @@ namespace osu.Game.Online.Leaderboards
[BackgroundDependencyLoader]
private void load(IAPIProvider api, OsuColour colour, ScoreManager scoreManager)
{
- var user = score.User;
+ var user = Score.User;
- statisticsLabels = GetStatistics(score).Select(s => new ScoreComponentLabel(s)).ToList();
+ statisticsLabels = GetStatistics(Score).Select(s => new ScoreComponentLabel(s)).ToList();
ClickableAvatar innerAvatar;
@@ -198,7 +200,7 @@ namespace osu.Game.Online.Leaderboards
{
TextColour = Color4.White,
GlowColour = Color4Extensions.FromHex(@"83ccfa"),
- Current = scoreManager.GetBindableTotalScoreString(score),
+ Current = scoreManager.GetBindableTotalScoreString(Score),
Font = OsuFont.Numeric.With(size: 23),
},
RankContainer = new Container
@@ -206,7 +208,7 @@ namespace osu.Game.Online.Leaderboards
Size = new Vector2(40f, 20f),
Children = new[]
{
- scoreRank = new UpdateableRank(score.Rank)
+ scoreRank = new UpdateableRank(Score.Rank)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -223,7 +225,7 @@ namespace osu.Game.Online.Leaderboards
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(1),
- ChildrenEnumerable = score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) })
+ ChildrenEnumerable = Score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) })
},
},
},
@@ -389,14 +391,14 @@ namespace osu.Game.Online.Leaderboards
{
List