diff --git a/global.json b/global.json
index bdb90eb0e9..9aa5b6192b 100644
--- a/global.json
+++ b/global.json
@@ -5,6 +5,6 @@
"version": "3.1.100"
},
"msbuild-sdks": {
- "Microsoft.Build.Traversal": "2.0.48"
+ "Microsoft.Build.Traversal": "2.0.50"
}
}
\ No newline at end of file
diff --git a/osu.Android.props b/osu.Android.props
index 596e5bfa8b..119c309675 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs
index 19ed7ffcf5..7542a2b997 100644
--- a/osu.Android/OsuGameAndroid.cs
+++ b/osu.Android/OsuGameAndroid.cs
@@ -3,6 +3,7 @@
using System;
using Android.App;
+using Android.OS;
using osu.Game;
using osu.Game.Updater;
@@ -18,9 +19,32 @@ namespace osu.Android
try
{
- // todo: needs checking before play store redeploy.
- string versionName = packageInfo.VersionName;
- // undo play store version garbling
+ // We store the osu! build number in the "VersionCode" field to better support google play releases.
+ // If we were to use the main build number, it would require a new submission each time (similar to TestFlight).
+ // In order to do this, we should split it up and pad the numbers to still ensure sequential increase over time.
+ //
+ // We also need to be aware that older SDK versions store this as a 32bit int.
+ //
+ // Basic conversion format (as done in Fastfile): 2020.606.0 -> 202006060
+
+ // https://stackoverflow.com/questions/52977079/android-sdk-28-versioncode-in-packageinfo-has-been-deprecated
+ string versionName = string.Empty;
+
+ if (Build.VERSION.SdkInt >= BuildVersionCodes.P)
+ {
+ versionName = packageInfo.LongVersionCode.ToString();
+ // ensure we only read the trailing portion of long (the part we are interested in).
+ versionName = versionName.Substring(versionName.Length - 9);
+ }
+ else
+ {
+#pragma warning disable CS0618 // Type or member is obsolete
+ // this is required else older SDKs will report missing method exception.
+ versionName = packageInfo.VersionCode.ToString();
+#pragma warning restore CS0618 // Type or member is obsolete
+ }
+
+ // undo play store version garbling (as mentioned above).
return new Version(int.Parse(versionName.Substring(0, 4)), int.Parse(versionName.Substring(4, 4)), int.Parse(versionName.Substring(8, 1)));
}
catch
@@ -33,4 +57,4 @@ namespace osu.Android
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
}
-}
\ No newline at end of file
+}
diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs
index dd50b05c75..05c8e835ac 100644
--- a/osu.Desktop/Updater/SquirrelUpdateManager.cs
+++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs
@@ -30,18 +30,16 @@ namespace osu.Desktop.Updater
private static readonly Logger logger = Logger.GetLogger("updater");
[BackgroundDependencyLoader]
- private void load(NotificationOverlay notification, OsuGameBase game)
+ private void load(NotificationOverlay notification)
{
notificationOverlay = notification;
- if (game.IsDeployedBuild)
- {
- Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger));
- Schedule(() => Task.Run(() => checkForUpdateAsync()));
- }
+ Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger));
}
- private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
+ protected override async Task PerformUpdateCheck() => await checkForUpdateAsync();
+
+ private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
{
// should we schedule a retry on completion of this check?
bool scheduleRecheck = true;
@@ -83,7 +81,7 @@ namespace osu.Desktop.Updater
// could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
// try again without deltas.
- checkForUpdateAsync(false, notification);
+ await checkForUpdateAsync(false, notification);
scheduleRecheck = false;
}
else
@@ -102,7 +100,7 @@ namespace osu.Desktop.Updater
if (scheduleRecheck)
{
// check again in 30 minutes.
- Scheduler.AddDelayed(() => checkForUpdateAsync(), 60000 * 30);
+ Scheduler.AddDelayed(async () => await checkForUpdateAsync(), 60000 * 30);
}
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs
index 7deeec527f..b570f090ca 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs
@@ -17,7 +17,8 @@ namespace osu.Game.Rulesets.Catch.Tests
{
var store = new NamespacedResourceStore(new DllResourceStore(GetType().Assembly), "Resources/special-skin");
var rawSkin = new TestLegacySkin(new SkinInfo { Name = "special-skin" }, store);
- var skin = new CatchLegacySkinTransformer(rawSkin);
+ var skinSource = new SkinProvidingContainer(rawSkin);
+ var skin = new CatchLegacySkinTransformer(skinSource);
Assert.AreEqual(new Color4(232, 185, 35, 255), skin.GetConfig(CatchSkinColour.HyperDash)?.Value);
Assert.AreEqual(new Color4(232, 74, 35, 255), skin.GetConfig(CatchSkinColour.HyperDashAfterImage)?.Value);
diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs
index 47e91e50d4..3e06e78dba 100644
--- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs
@@ -13,8 +13,10 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
{
public class TestSceneCatchModPerfect : ModPerfectTestScene
{
+ protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
+
public TestSceneCatchModPerfect()
- : base(new CatchRuleset(), new CatchModPerfect())
+ : base(new CatchModPerfect())
{
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
index ed7bfb9a44..7c2304694f 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
@@ -12,13 +12,8 @@ using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
{
- public class TestSceneAutoJuiceStream : PlayerTestScene
+ public class TestSceneAutoJuiceStream : TestSceneCatchPlayer
{
- public TestSceneAutoJuiceStream()
- : base(new CatchRuleset())
- {
- }
-
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
index 27a2d5bd0a..e89a95ae37 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
@@ -4,18 +4,12 @@
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestSceneBananaShower : PlayerTestScene
+ public class TestSceneBananaShower : TestSceneCatchPlayer
{
- public TestSceneBananaShower()
- : base(new CatchRuleset())
- {
- }
-
[Test]
public void TestBananaShower()
{
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs
index 9836a7811a..31d0831fae 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs
@@ -9,9 +9,6 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestFixture]
public class TestSceneCatchPlayer : PlayerTestScene
{
- public TestSceneCatchPlayer()
- : base(new CatchRuleset())
- {
- }
+ protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
index 9ce46ad6ba..44672b6526 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
@@ -4,18 +4,12 @@
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestSceneCatchStacker : PlayerTestScene
+ public class TestSceneCatchStacker : TestSceneCatchPlayer
{
- public TestSceneCatchStacker()
- : base(new CatchRuleset())
- {
- }
-
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
index 0a142a52f8..a0dcb86d57 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
@@ -9,19 +9,13 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
-using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestSceneHyperDash : PlayerTestScene
+ public class TestSceneHyperDash : TestSceneCatchPlayer
{
- public TestSceneHyperDash()
- : base(new CatchRuleset())
- {
- }
-
protected override bool Autoplay => true;
[Test]
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs
index cbc87459e1..ffcf61a4bf 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs
@@ -7,18 +7,12 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
-using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
{
- public class TestSceneJuiceStream : PlayerTestScene
+ public class TestSceneJuiceStream : TestSceneCatchPlayer
{
- public TestSceneJuiceStream()
- : base(new CatchRuleset())
- {
- }
-
[Test]
public void TestJuiceStreamEndingCombo()
{
diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
index 954f2dfc5f..d929da1a29 100644
--- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
@@ -2,26 +2,21 @@
// See the LICENCE file in the repository root for full licence text.
using Humanizer;
-using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Textures;
-using osu.Game.Audio;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Catch.Skinning
{
- public class CatchLegacySkinTransformer : ISkin
+ public class CatchLegacySkinTransformer : LegacySkinTransformer
{
- private readonly ISkin source;
-
- public CatchLegacySkinTransformer(ISkin source)
+ public CatchLegacySkinTransformer(ISkinSource source)
+ : base(source)
{
- this.source = source;
}
- public Drawable GetDrawableComponent(ISkinComponent component)
+ public override Drawable GetDrawableComponent(ISkinComponent component)
{
if (!(component is CatchSkinComponent catchSkinComponent))
return null;
@@ -61,19 +56,15 @@ namespace osu.Game.Rulesets.Catch.Skinning
return null;
}
- public Texture GetTexture(string componentName) => source.GetTexture(componentName);
-
- public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
-
- public IBindable GetConfig(TLookup lookup)
+ public override IBindable GetConfig(TLookup lookup)
{
switch (lookup)
{
case CatchSkinColour colour:
- return source.GetConfig(new SkinCustomColourLookup(colour));
+ return Source.GetConfig(new SkinCustomColourLookup(colour));
}
- return source.GetConfig(lookup);
+ return Source.GetConfig(lookup);
}
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs
index 607d42a1bb..2e3b21aed7 100644
--- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs
@@ -10,8 +10,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
{
public class TestSceneManiaModPerfect : ModPerfectTestScene
{
+ protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
+
public TestSceneManiaModPerfect()
- : base(new ManiaRuleset(), new ManiaModPerfect())
+ : base(new ManiaModPerfect())
{
}
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit0@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit0@2x.png
new file mode 100644
index 0000000000..2e7b9bc34f
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit0@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit100@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit100@2x.png
new file mode 100644
index 0000000000..27ca7f8b42
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit100@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit200@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit200@2x.png
new file mode 100644
index 0000000000..24ad926375
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit200@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300@2x.png
new file mode 100644
index 0000000000..098561f980
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300g-0@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300g-0@2x.png
new file mode 100644
index 0000000000..7e6501d1be
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300g-0@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300g-1@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300g-1@2x.png
new file mode 100644
index 0000000000..f17b2b1e73
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300g-1@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit50@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit50@2x.png
new file mode 100644
index 0000000000..1afec2f4a9
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit50@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
index 56564776b3..941abac1da 100644
--- a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
+++ b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
@@ -1,6 +1,12 @@
[General]
-Version: 2.4
+Version: 2.5
[Mania]
Keys: 4
-ColumnLineWidth: 3,1,3,1,1
\ No newline at end of file
+ColumnLineWidth: 3,1,3,1,1
+Hit0: mania/hit0
+Hit50: mania/hit50
+Hit100: mania/hit100
+Hit200: mania/hit200
+Hit300: mania/hit300
+Hit300g: mania/hit300g
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
index 497b80950a..a4d4ec50f8 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
@@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
@@ -16,14 +17,19 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public TestSceneDrawableJudgement()
{
+ var hitWindows = new ManiaHitWindows();
+
foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1))
{
- AddStep("Show " + result.GetDescription(), () => SetContents(() =>
- new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- }));
+ if (hitWindows.IsHitResultAllowed(result))
+ {
+ AddStep("Show " + result.GetDescription(), () => SetContents(() =>
+ new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }));
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs
index 7ed886be49..3b9c03b86a 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs
@@ -15,8 +15,9 @@ namespace osu.Game.Rulesets.Mania.Tests
{
private readonly Bindable direction = new Bindable();
+ protected override Ruleset CreateEditorRuleset() => new ManiaRuleset();
+
public TestSceneEditor()
- : base(new ManiaRuleset())
{
AddStep("upwards scroll", () => direction.Value = ManiaScrollingDirection.Up);
AddStep("downwards scroll", () => direction.Value = ManiaScrollingDirection.Down);
diff --git a/osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs
similarity index 62%
rename from osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs
rename to osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs
index cd25d162d0..a399b90585 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs
@@ -5,11 +5,8 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
- public class TestScenePlayer : PlayerTestScene
+ public class TestSceneManiaPlayer : PlayerTestScene
{
- public TestScenePlayer()
- : base(new ManiaRuleset())
- {
- }
+ protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
}
}
diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
index 00b839f8ec..294aab1e4e 100644
--- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
+++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
@@ -7,8 +7,6 @@ namespace osu.Game.Rulesets.Mania.Judgements
{
public class HoldNoteTickJudgement : ManiaJudgement
{
- public override bool AffectsCombo => false;
-
protected override int NumericResultFor(HitResult result) => 20;
protected override double HealthIncreaseFor(HitResult result)
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index a37aaa8cc4..6ddb052585 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -44,6 +44,8 @@ namespace osu.Game.Rulesets.Mania
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
+ public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new DrainingHealthProcessor(drainStartTime, 0.2);
+
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs
index 0c9bc97ba9..a749f80855 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
[BackgroundDependencyLoader]
private void load(ISkinSource skin, IScrollingInfo scrollingInfo, DrawableHitObject drawableObject)
{
- string imageName = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value
+ string imageName = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value
?? $"mania-note{FallbackColumnIndex}L";
sprite = skin.GetAnimation(imageName, true, true).With(d =>
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs
index 1a097405ac..64a7641421 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs
@@ -32,28 +32,28 @@ namespace osu.Game.Rulesets.Mania.Skinning
[BackgroundDependencyLoader]
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
{
- string lightImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LightImage, 0)?.Value
+ string lightImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LightImage)?.Value
?? "mania-stage-light";
- float leftLineWidth = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LeftLineWidth)
+ float leftLineWidth = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LeftLineWidth)
?.Value ?? 1;
- float rightLineWidth = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.RightLineWidth)
+ float rightLineWidth = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.RightLineWidth)
?.Value ?? 1;
bool hasLeftLine = leftLineWidth > 0;
bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m
|| isLastColumn;
- float lightPosition = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LightPosition)?.Value
+ float lightPosition = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LightPosition)?.Value
?? 0;
- Color4 lineColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value
+ Color4 lineColour = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value
?? Color4.White;
- Color4 backgroundColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour)?.Value
+ Color4 backgroundColour = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour)?.Value
?? Color4.Black;
- Color4 lightColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value
+ Color4 lightColour = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value
?? Color4.White;
InternalChildren = new Drawable[]
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs
index ce0b9fe4b6..bc93bb2615 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs
@@ -26,10 +26,10 @@ namespace osu.Game.Rulesets.Mania.Skinning
[BackgroundDependencyLoader]
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
{
- string imageName = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ExplosionImage)?.Value
+ string imageName = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ExplosionImage)?.Value
?? "lightingN";
- float explosionScale = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ExplosionScale)?.Value
+ float explosionScale = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ExplosionScale)?.Value
?? 1;
// Create a temporary animation to retrieve the number of frames, in an effort to calculate the intended frame length.
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs
index 40752d3f4b..d055ef3480 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs
@@ -14,7 +14,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning
{
- public class LegacyHitTarget : LegacyManiaElement
+ public class LegacyHitTarget : CompositeDrawable
{
private readonly IBindable direction = new Bindable();
@@ -28,13 +28,13 @@ namespace osu.Game.Rulesets.Mania.Skinning
[BackgroundDependencyLoader]
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
{
- string targetImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HitTargetImage)?.Value
+ string targetImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitTargetImage)?.Value
?? "mania-stage-hint";
- bool showJudgementLine = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ShowJudgementLine)?.Value
+ bool showJudgementLine = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ShowJudgementLine)?.Value
?? true;
- Color4 lineColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.JudgementLineColour)?.Value
+ Color4 lineColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.JudgementLineColour)?.Value
?? Color4.White;
InternalChild = directionContainer = new Container
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs
index 7c8d1cd303..44f3e7d7b3 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs
@@ -33,10 +33,10 @@ namespace osu.Game.Rulesets.Mania.Skinning
[BackgroundDependencyLoader]
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
{
- string upImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.KeyImage)?.Value
+ string upImage = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.KeyImage)?.Value
?? $"mania-key{FallbackColumnIndex}";
- string downImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.KeyImageDown)?.Value
+ string downImage = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.KeyImageDown)?.Value
?? $"mania-key{FallbackColumnIndex}D";
InternalChild = directionContainer = new Container
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs
index 05b731ec5d..3c0c632c14 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
///
/// A which is placed somewhere within a .
///
- public class LegacyManiaColumnElement : LegacyManiaElement
+ public class LegacyManiaColumnElement : CompositeDrawable
{
[Resolved]
protected Column Column { get; private set; }
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
}
}
- protected override IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null)
- => base.GetManiaSkinConfig(skin, lookup, index ?? Column.Index);
+ protected IBindable GetColumnSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup)
+ => skin.GetManiaSkinConfig(lookup, Column.Index);
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs
index 85523ae3c0..515c941d65 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs
@@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
break;
}
- string noteImage = GetManiaSkinConfig(skin, lookup)?.Value
+ string noteImage = GetColumnSkinConfig(skin, lookup)?.Value
?? $"mania-note{FallbackColumnIndex}{suffix}";
return skin.GetTexture(noteImage);
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs
index f177284399..7f5de601ca 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs
@@ -3,13 +3,14 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Mania.Skinning
{
- public class LegacyStageBackground : LegacyManiaElement
+ public class LegacyStageBackground : CompositeDrawable
{
private Drawable leftSprite;
private Drawable rightSprite;
@@ -22,10 +23,10 @@ namespace osu.Game.Rulesets.Mania.Skinning
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
- string leftImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LeftStageImage)?.Value
+ string leftImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LeftStageImage)?.Value
?? "mania-stage-left";
- string rightImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.RightStageImage)?.Value
+ string rightImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.RightStageImage)?.Value
?? "mania-stage-right";
InternalChildren = new[]
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageForeground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageForeground.cs
index 9719005d54..4609fcc849 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageForeground.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageForeground.cs
@@ -4,13 +4,14 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Mania.Skinning
{
- public class LegacyStageForeground : LegacyManiaElement
+ public class LegacyStageForeground : CompositeDrawable
{
private readonly IBindable direction = new Bindable();
@@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
[BackgroundDependencyLoader]
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
{
- string bottomImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.BottomStageImage)?.Value
+ string bottomImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.BottomStageImage)?.Value
?? "mania-stage-bottom";
sprite = skin.GetAnimation(bottomImage, true, true)?.With(d =>
diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
index e64178083a..84e88a10be 100644
--- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
@@ -3,22 +3,49 @@
using System;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Textures;
-using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Skinning;
+using System.Collections.Generic;
namespace osu.Game.Rulesets.Mania.Skinning
{
- public class ManiaLegacySkinTransformer : ISkin
+ public class ManiaLegacySkinTransformer : LegacySkinTransformer
{
- private readonly ISkin source;
private readonly ManiaBeatmap beatmap;
+ ///
+ /// Mapping of to their corresponding
+ /// value.
+ ///
+ private static readonly IReadOnlyDictionary hitresult_mapping
+ = new Dictionary
+ {
+ { HitResult.Perfect, LegacyManiaSkinConfigurationLookups.Hit300g },
+ { HitResult.Great, LegacyManiaSkinConfigurationLookups.Hit300 },
+ { HitResult.Good, LegacyManiaSkinConfigurationLookups.Hit200 },
+ { HitResult.Ok, LegacyManiaSkinConfigurationLookups.Hit100 },
+ { HitResult.Meh, LegacyManiaSkinConfigurationLookups.Hit50 },
+ { HitResult.Miss, LegacyManiaSkinConfigurationLookups.Hit0 }
+ };
+
+ ///
+ /// Mapping of to their corresponding
+ /// default filenames.
+ ///
+ private static readonly IReadOnlyDictionary default_hitresult_skin_filenames
+ = new Dictionary
+ {
+ { HitResult.Perfect, "mania-hit300g" },
+ { HitResult.Great, "mania-hit300" },
+ { HitResult.Good, "mania-hit200" },
+ { HitResult.Ok, "mania-hit100" },
+ { HitResult.Meh, "mania-hit50" },
+ { HitResult.Miss, "mania-hit0" }
+ };
+
private Lazy isLegacySkin;
///
@@ -28,29 +55,28 @@ namespace osu.Game.Rulesets.Mania.Skinning
private Lazy hasKeyTexture;
public ManiaLegacySkinTransformer(ISkinSource source, IBeatmap beatmap)
+ : base(source)
{
- this.source = source;
this.beatmap = (ManiaBeatmap)beatmap;
- source.SourceChanged += sourceChanged;
+ Source.SourceChanged += sourceChanged;
sourceChanged();
}
private void sourceChanged()
{
- isLegacySkin = new Lazy(() => source.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null);
- hasKeyTexture = new Lazy(() => source.GetAnimation(
- GetConfig(
- new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value
+ isLegacySkin = new Lazy(() => Source.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null);
+ hasKeyTexture = new Lazy(() => Source.GetAnimation(
+ this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value
?? "mania-key1", true, true) != null);
}
- public Drawable GetDrawableComponent(ISkinComponent component)
+ public override Drawable GetDrawableComponent(ISkinComponent component)
{
switch (component)
{
case GameplaySkinComponent resultComponent:
- return getResult(resultComponent);
+ return getResult(resultComponent.Component);
case ManiaSkinComponent maniaComponent:
if (!isLegacySkin.Value || !hasKeyTexture.Value)
@@ -95,42 +121,20 @@ namespace osu.Game.Rulesets.Mania.Skinning
return null;
}
- private Drawable getResult(GameplaySkinComponent resultComponent)
+ private Drawable getResult(HitResult result)
{
- switch (resultComponent.Component)
- {
- case HitResult.Miss:
- return this.GetAnimation("mania-hit0", true, true);
+ string filename = this.GetManiaSkinConfig(hitresult_mapping[result])?.Value
+ ?? default_hitresult_skin_filenames[result];
- case HitResult.Meh:
- return this.GetAnimation("mania-hit50", true, true);
-
- case HitResult.Ok:
- return this.GetAnimation("mania-hit100", true, true);
-
- case HitResult.Good:
- return this.GetAnimation("mania-hit200", true, true);
-
- case HitResult.Great:
- return this.GetAnimation("mania-hit300", true, true);
-
- case HitResult.Perfect:
- return this.GetAnimation("mania-hit300g", true, true);
- }
-
- return null;
+ return this.GetAnimation(filename, true, true);
}
- public Texture GetTexture(string componentName) => source.GetTexture(componentName);
-
- public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
-
- public IBindable GetConfig(TLookup lookup)
+ public override IBindable GetConfig(TLookup lookup)
{
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
- return source.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn));
+ return Source.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn));
- return source.GetConfig(lookup);
+ return Source.GetConfig(lookup);
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs
similarity index 71%
rename from osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs
rename to osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs
index 11fdd663a1..2e17a6bef1 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs
@@ -2,15 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
-using osu.Framework.Graphics.Containers;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Skinning
{
- ///
- /// A mania legacy skin element.
- ///
- public class LegacyManiaElement : CompositeDrawable
+ public static class ManiaSkinConfigExtensions
{
///
/// Retrieve a per-column-count skin configuration.
@@ -18,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
/// The skin from which configuration is retrieved.
/// The value to retrieve.
/// If not null, denotes the index of the column to which the entry applies.
- protected virtual IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null)
+ public static IBindable GetManiaSkinConfig(this ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null)
=> skin.GetConfig(
new ManiaSkinConfigurationLookup(lookup, index));
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs b/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs
new file mode 100644
index 0000000000..7697f46160
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs
@@ -0,0 +1,12 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Osu.Tests.Mods
+{
+ public class OsuModTestScene : ModTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
index 69415b70e3..49c1fe8540 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
@@ -9,17 +9,11 @@ using osu.Framework.Utils;
using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests.Mods
{
- public class TestSceneOsuModDifficultyAdjust : ModTestScene
+ public class TestSceneOsuModDifficultyAdjust : OsuModTestScene
{
- public TestSceneOsuModDifficultyAdjust()
- : base(new OsuRuleset())
- {
- }
-
[Test]
public void TestNoAdjustment() => CreateModTest(new ModTestData
{
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs
index dcf19ad993..335ef31019 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs
@@ -4,17 +4,11 @@
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Rulesets.Osu.Mods;
-using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests.Mods
{
- public class TestSceneOsuModDoubleTime : ModTestScene
+ public class TestSceneOsuModDoubleTime : OsuModTestScene
{
- public TestSceneOsuModDoubleTime()
- : base(new OsuRuleset())
- {
- }
-
[TestCase(0.5)]
[TestCase(1.01)]
[TestCase(1.5)]
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
index 8bd3d3c7cc..40f1c4a52f 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
@@ -8,18 +8,12 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests.Mods
{
- public class TestSceneOsuModHidden : ModTestScene
+ public class TestSceneOsuModHidden : OsuModTestScene
{
- public TestSceneOsuModHidden()
- : base(new OsuRuleset())
- {
- }
-
[Test]
public void TestDefaultBeatmapTest() => CreateModTest(new ModTestData
{
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs
index b03a894085..985baa8cf5 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs
@@ -13,8 +13,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public class TestSceneOsuModPerfect : ModPerfectTestScene
{
+ protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
+
public TestSceneOsuModPerfect()
- : base(new OsuRuleset(), new OsuModPerfect())
+ : base(new OsuModPerfect())
{
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs
index 4aca34bf64..9239034a53 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs
@@ -9,9 +9,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture]
public class TestSceneEditor : EditorTestScene
{
- public TestSceneEditor()
- : base(new OsuRuleset())
- {
- }
+ protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
index b99cd523ff..8cf29ddfbf 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
@@ -4,19 +4,13 @@
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
- public class TestSceneHitCircleLongCombo : PlayerTestScene
+ public class TestSceneHitCircleLongCombo : TestSceneOsuPlayer
{
- public TestSceneHitCircleLongCombo()
- : base(new OsuRuleset())
- {
- }
-
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs
index 5f3596976d..f3221ffe32 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs
@@ -19,10 +19,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneMissHitWindowJudgements : ModTestScene
{
- public TestSceneMissHitWindowJudgements()
- : base(new OsuRuleset())
- {
- }
+ protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
[Test]
public void TestMissViaEarlyHit()
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs
index 0a33b09ba8..f5b28b36c0 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs
@@ -9,9 +9,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture]
public class TestSceneOsuPlayer : PlayerTestScene
{
- public TestSceneOsuPlayer()
- : base(new OsuRuleset())
- {
- }
+ protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
index d39e24fc1f..b357e20ee8 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
@@ -25,13 +25,12 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
- public class TestSceneSkinFallbacks : PlayerTestScene
+ public class TestSceneSkinFallbacks : TestSceneOsuPlayer
{
private readonly TestSource testUserSkin;
private readonly TestSource testBeatmapSkin;
public TestSceneSkinFallbacks()
- : base(new OsuRuleset())
{
testUserSkin = new TestSource("user");
testBeatmapSkin = new TestSource("beatmap");
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
index ba0003b5cd..3e5758ca01 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
@@ -2,20 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Textures;
-using osu.Game.Audio;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Osu.Skinning
{
- public class OsuLegacySkinTransformer : ISkin
+ public class OsuLegacySkinTransformer : LegacySkinTransformer
{
- private readonly ISkin source;
-
private Lazy hasHitCircle;
///
@@ -26,19 +21,18 @@ namespace osu.Game.Rulesets.Osu.Skinning
public const float LEGACY_CIRCLE_RADIUS = 64 - 5;
public OsuLegacySkinTransformer(ISkinSource source)
+ : base(source)
{
- this.source = source;
-
- source.SourceChanged += sourceChanged;
+ Source.SourceChanged += sourceChanged;
sourceChanged();
}
private void sourceChanged()
{
- hasHitCircle = new Lazy(() => source.GetTexture("hitcircle") != null);
+ hasHitCircle = new Lazy(() => Source.GetTexture("hitcircle") != null);
}
- public Drawable GetDrawableComponent(ISkinComponent component)
+ public override Drawable GetDrawableComponent(ISkinComponent component)
{
if (!(component is OsuSkinComponent osuComponent))
return null;
@@ -85,13 +79,13 @@ namespace osu.Game.Rulesets.Osu.Skinning
return null;
case OsuSkinComponents.Cursor:
- if (source.GetTexture("cursor") != null)
+ if (Source.GetTexture("cursor") != null)
return new LegacyCursor();
return null;
case OsuSkinComponents.CursorTrail:
- if (source.GetTexture("cursortrail") != null)
+ if (Source.GetTexture("cursortrail") != null)
return new LegacyCursorTrail();
return null;
@@ -102,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
return !hasFont(font)
? null
- : new LegacySpriteText(source, font)
+ : new LegacySpriteText(Source, font)
{
// stable applies a blanket 0.8x scale to hitcircle fonts
Scale = new Vector2(0.8f),
@@ -113,16 +107,12 @@ namespace osu.Game.Rulesets.Osu.Skinning
return null;
}
- public Texture GetTexture(string componentName) => source.GetTexture(componentName);
-
- public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
-
- public IBindable GetConfig(TLookup lookup)
+ public override IBindable GetConfig(TLookup lookup)
{
switch (lookup)
{
case OsuSkinColour colour:
- return source.GetConfig(new SkinCustomColourLookup(colour));
+ return Source.GetConfig(new SkinCustomColourLookup(colour));
case OsuSkinConfiguration osuLookup:
switch (osuLookup)
@@ -136,16 +126,16 @@ namespace osu.Game.Rulesets.Osu.Skinning
case OsuSkinConfiguration.HitCircleOverlayAboveNumber:
// See https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D
// HitCircleOverlayAboveNumer (with typo) should still be supported for now.
- return source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ??
- source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer);
+ return Source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ??
+ Source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer);
}
break;
}
- return source.GetConfig(lookup);
+ return Source.GetConfig(lookup);
}
- private bool hasFont(string fontName) => source.GetTexture($"{fontName}-0") != null;
+ private bool hasFont(string fontName) => Source.GetTexture($"{fontName}-0") != null;
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs
index 26c90ad295..a83cc16413 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs
@@ -12,8 +12,10 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods
{
public class TestSceneTaikoModPerfect : ModPerfectTestScene
{
+ protected override Ruleset CreatePlayerRuleset() => new TestTaikoRuleset();
+
public TestSceneTaikoModPerfect()
- : base(new TestTaikoRuleset(), new TaikoModPerfect())
+ : base(new TaikoModPerfect())
{
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs
index 089a7ad00b..411fe08bcf 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs
@@ -9,9 +9,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
[TestFixture]
public class TestSceneEditor : EditorTestScene
{
- public TestSceneEditor()
- : base(new TaikoRuleset())
- {
- }
+ protected override Ruleset CreateEditorRuleset() => new TaikoRuleset();
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs
index d541aa8de8..4ba9c447fb 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs
@@ -6,7 +6,6 @@ using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
-using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
@@ -14,13 +13,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
/// Taiko has some interesting rules for legacy mappings.
///
[HeadlessTest]
- public class TestSceneSampleOutput : PlayerTestScene
+ public class TestSceneSampleOutput : TestSceneTaikoPlayer
{
- public TestSceneSampleOutput()
- : base(new TaikoRuleset())
- {
- }
-
public override void SetUpSteps()
{
base.SetUpSteps();
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs
index 923e28a45e..75049b7467 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs
@@ -5,17 +5,11 @@ using System.Linq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects;
-using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
- public class TestSceneSwellJudgements : PlayerTestScene
+ public class TestSceneSwellJudgements : TestSceneTaikoPlayer
{
- public TestSceneSwellJudgements()
- : base(new TaikoRuleset())
- {
- }
-
[Test]
public void TestZeroTickTimeOffsets()
{
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs
new file mode 100644
index 0000000000..cd7511241a
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs
@@ -0,0 +1,12 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ public class TestSceneTaikoPlayer : PlayerTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
index 2ab041e191..aaa634648a 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
@@ -11,13 +11,8 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
- public class TestSceneTaikoSuddenDeath : PlayerTestScene
+ public class TestSceneTaikoSuddenDeath : TestSceneTaikoPlayer
{
- public TestSceneTaikoSuddenDeath()
- : base(new TaikoRuleset())
- {
- }
-
protected override bool AllowFail => true;
protected override TestPlayer CreatePlayer(Ruleset ruleset)
diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
index 6e9a37eb93..23d675cfb0 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
@@ -6,23 +6,20 @@ using System.Collections.Generic;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.Skinning
{
- public class TaikoLegacySkinTransformer : ISkin
+ public class TaikoLegacySkinTransformer : LegacySkinTransformer
{
- private readonly ISkinSource source;
-
public TaikoLegacySkinTransformer(ISkinSource source)
+ : base(source)
{
- this.source = source;
}
- public Drawable GetDrawableComponent(ISkinComponent component)
+ public override Drawable GetDrawableComponent(ISkinComponent component)
{
if (!(component is TaikoSkinComponent taikoComponent))
return null;
@@ -100,7 +97,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning
return null;
}
- return source.GetDrawableComponent(component);
+ return Source.GetDrawableComponent(component);
}
private string getHitName(TaikoSkinComponents component)
@@ -120,11 +117,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning
throw new ArgumentOutOfRangeException(nameof(component), "Invalid result type");
}
- public Texture GetTexture(string componentName) => source.GetTexture(componentName);
+ public override SampleChannel GetSample(ISampleInfo sampleInfo) => Source.GetSample(new LegacyTaikoSampleInfo(sampleInfo));
- public SampleChannel GetSample(ISampleInfo sampleInfo) => source.GetSample(new LegacyTaikoSampleInfo(sampleInfo));
-
- public IBindable GetConfig(TLookup lookup) => source.GetConfig(lookup);
+ public override IBindable GetConfig(TLookup lookup) => Source.GetConfig(lookup);
private class LegacyTaikoSampleInfo : ISampleInfo
{
diff --git a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs
new file mode 100644
index 0000000000..cd3669f160
--- /dev/null
+++ b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs
@@ -0,0 +1,27 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Framework.Testing;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Gameplay
+{
+ [HeadlessTest]
+ public class TestSceneGameplayClockContainer : OsuTestScene
+ {
+ [Test]
+ public void TestStartThenElapsedTime()
+ {
+ GameplayClockContainer gcc = null;
+
+ AddStep("create container", () => Add(gcc = new GameplayClockContainer(CreateWorkingBeatmap(new OsuRuleset().RulesetInfo), Array.Empty(), 0)));
+ AddStep("start track", () => gcc.Start());
+ AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs
index f611f2717e..ef6efb7fec 100644
--- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs
@@ -1,58 +1,21 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
using NUnit.Framework;
-using osu.Framework.Allocation;
-using osu.Framework.Audio;
using osu.Framework.IO.Stores;
using osu.Framework.Testing;
-using osu.Framework.Timing;
-using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.Formats;
-using osu.Game.IO;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
-using osu.Game.Skinning;
-using osu.Game.Storyboards;
+using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources;
-using osu.Game.Tests.Visual;
-using osu.Game.Users;
namespace osu.Game.Tests.Gameplay
{
[HeadlessTest]
- public class TestSceneHitObjectSamples : PlayerTestScene
+ public class TestSceneHitObjectSamples : HitObjectSampleTest
{
- private readonly SkinInfo userSkinInfo = new SkinInfo();
-
- private readonly BeatmapInfo beatmapInfo = new BeatmapInfo
- {
- BeatmapSet = new BeatmapSetInfo(),
- Metadata = new BeatmapMetadata
- {
- Author = User.SYSTEM_USER
- }
- };
-
- private readonly TestResourceStore userSkinResourceStore = new TestResourceStore();
- private readonly TestResourceStore beatmapSkinResourceStore = new TestResourceStore();
-
- protected override bool HasCustomSteps => true;
-
- public TestSceneHitObjectSamples()
- : base(new OsuRuleset())
- {
- }
-
- private SkinSourceDependencyContainer dependencies;
-
- protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
- => new DependencyContainer(dependencies = new SkinSourceDependencyContainer(base.CreateChildDependencies(parent)));
+ protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
+ protected override IResourceStore Resources => TestResources.GetStore();
///
/// Tests that a hitobject which provides no custom sample set retrieves samples from the user skin.
@@ -62,11 +25,11 @@ namespace osu.Game.Tests.Gameplay
{
const string expected_sample = "normal-hitnormal";
- setupSkins(expected_sample, expected_sample);
+ SetupSkins(expected_sample, expected_sample);
- createTestWithBeatmap("hitobject-skin-sample.osu");
+ CreateTestWithBeatmap("hitobject-skin-sample.osu");
- assertUserLookup(expected_sample);
+ AssertUserLookup(expected_sample);
}
///
@@ -77,11 +40,11 @@ namespace osu.Game.Tests.Gameplay
{
const string expected_sample = "normal-hitnormal";
- setupSkins(expected_sample, expected_sample);
+ SetupSkins(expected_sample, expected_sample);
- createTestWithBeatmap("hitobject-beatmap-sample.osu");
+ CreateTestWithBeatmap("hitobject-beatmap-sample.osu");
- assertBeatmapLookup(expected_sample);
+ AssertBeatmapLookup(expected_sample);
}
///
@@ -92,11 +55,11 @@ namespace osu.Game.Tests.Gameplay
{
const string expected_sample = "normal-hitnormal";
- setupSkins(null, expected_sample);
+ SetupSkins(null, expected_sample);
- createTestWithBeatmap("hitobject-beatmap-sample.osu");
+ CreateTestWithBeatmap("hitobject-beatmap-sample.osu");
- assertUserLookup(expected_sample);
+ AssertUserLookup(expected_sample);
}
///
@@ -108,11 +71,11 @@ namespace osu.Game.Tests.Gameplay
[TestCase("normal-hitnormal")]
public void TestDefaultCustomSampleFromBeatmap(string expectedSample)
{
- setupSkins(expectedSample, expectedSample);
+ SetupSkins(expectedSample, expectedSample);
- createTestWithBeatmap("hitobject-beatmap-custom-sample.osu");
+ CreateTestWithBeatmap("hitobject-beatmap-custom-sample.osu");
- assertBeatmapLookup(expectedSample);
+ AssertBeatmapLookup(expectedSample);
}
///
@@ -124,11 +87,11 @@ namespace osu.Game.Tests.Gameplay
[TestCase("normal-hitnormal")]
public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample)
{
- setupSkins(string.Empty, expectedSample);
+ SetupSkins(string.Empty, expectedSample);
- createTestWithBeatmap("hitobject-beatmap-custom-sample.osu");
+ CreateTestWithBeatmap("hitobject-beatmap-custom-sample.osu");
- assertUserLookup(expectedSample);
+ AssertUserLookup(expectedSample);
}
///
@@ -139,11 +102,11 @@ namespace osu.Game.Tests.Gameplay
{
const string expected_sample = "hit_1.wav";
- setupSkins(expected_sample, expected_sample);
+ SetupSkins(expected_sample, expected_sample);
- createTestWithBeatmap("file-beatmap-sample.osu");
+ CreateTestWithBeatmap("file-beatmap-sample.osu");
- assertBeatmapLookup(expected_sample);
+ AssertBeatmapLookup(expected_sample);
}
///
@@ -154,11 +117,11 @@ namespace osu.Game.Tests.Gameplay
{
const string expected_sample = "normal-hitnormal";
- setupSkins(expected_sample, expected_sample);
+ SetupSkins(expected_sample, expected_sample);
- createTestWithBeatmap("controlpoint-skin-sample.osu");
+ CreateTestWithBeatmap("controlpoint-skin-sample.osu");
- assertUserLookup(expected_sample);
+ AssertUserLookup(expected_sample);
}
///
@@ -169,11 +132,11 @@ namespace osu.Game.Tests.Gameplay
{
const string expected_sample = "normal-hitnormal";
- setupSkins(expected_sample, expected_sample);
+ SetupSkins(expected_sample, expected_sample);
- createTestWithBeatmap("controlpoint-beatmap-sample.osu");
+ CreateTestWithBeatmap("controlpoint-beatmap-sample.osu");
- assertBeatmapLookup(expected_sample);
+ AssertBeatmapLookup(expected_sample);
}
///
@@ -183,11 +146,11 @@ namespace osu.Game.Tests.Gameplay
[TestCase("normal-hitnormal")]
public void TestControlPointCustomSampleFromBeatmap(string sampleName)
{
- setupSkins(sampleName, sampleName);
+ SetupSkins(sampleName, sampleName);
- createTestWithBeatmap("controlpoint-beatmap-custom-sample.osu");
+ CreateTestWithBeatmap("controlpoint-beatmap-custom-sample.osu");
- assertBeatmapLookup(sampleName);
+ AssertBeatmapLookup(sampleName);
}
///
@@ -198,149 +161,11 @@ namespace osu.Game.Tests.Gameplay
{
const string expected_sample = "normal-hitnormal3";
- setupSkins(expected_sample, expected_sample);
+ SetupSkins(expected_sample, expected_sample);
- createTestWithBeatmap("hitobject-beatmap-custom-sample-override.osu");
+ CreateTestWithBeatmap("hitobject-beatmap-custom-sample-override.osu");
- assertBeatmapLookup(expected_sample);
- }
-
- protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestBeatmap;
-
- protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
- => new TestWorkingBeatmap(beatmapInfo, beatmapSkinResourceStore, beatmap, storyboard, Clock, Audio);
-
- private IBeatmap currentTestBeatmap;
-
- private void createTestWithBeatmap(string filename)
- {
- CreateTest(() =>
- {
- AddStep("clear performed lookups", () =>
- {
- userSkinResourceStore.PerformedLookups.Clear();
- beatmapSkinResourceStore.PerformedLookups.Clear();
- });
-
- AddStep($"load {filename}", () =>
- {
- using (var reader = new LineBufferedReader(TestResources.OpenResource($"SampleLookups/{filename}")))
- currentTestBeatmap = Decoder.GetDecoder(reader).Decode(reader);
- });
- });
- }
-
- private void setupSkins(string beatmapFile, string userFile)
- {
- AddStep("setup skins", () =>
- {
- userSkinInfo.Files = new List
- {
- new SkinFileInfo
- {
- Filename = userFile,
- FileInfo = new IO.FileInfo { Hash = userFile }
- }
- };
-
- beatmapInfo.BeatmapSet.Files = new List
- {
- new BeatmapSetFileInfo
- {
- Filename = beatmapFile,
- FileInfo = new IO.FileInfo { Hash = beatmapFile }
- }
- };
-
- // Need to refresh the cached skin source to refresh the skin resource store.
- dependencies.SkinSource = new SkinProvidingContainer(new LegacySkin(userSkinInfo, userSkinResourceStore, Audio));
- });
- }
-
- private void assertBeatmapLookup(string name) => AddAssert($"\"{name}\" looked up from beatmap skin",
- () => !userSkinResourceStore.PerformedLookups.Contains(name) && beatmapSkinResourceStore.PerformedLookups.Contains(name));
-
- private void assertUserLookup(string name) => AddAssert($"\"{name}\" looked up from user skin",
- () => !beatmapSkinResourceStore.PerformedLookups.Contains(name) && userSkinResourceStore.PerformedLookups.Contains(name));
-
- private class SkinSourceDependencyContainer : IReadOnlyDependencyContainer
- {
- public ISkinSource SkinSource;
-
- private readonly IReadOnlyDependencyContainer fallback;
-
- public SkinSourceDependencyContainer(IReadOnlyDependencyContainer fallback)
- {
- this.fallback = fallback;
- }
-
- public object Get(Type type)
- {
- if (type == typeof(ISkinSource))
- return SkinSource;
-
- return fallback.Get(type);
- }
-
- public object Get(Type type, CacheInfo info)
- {
- if (type == typeof(ISkinSource))
- return SkinSource;
-
- return fallback.Get(type, info);
- }
-
- public void Inject(T instance) where T : class
- {
- // Never used directly
- }
- }
-
- private class TestResourceStore : IResourceStore
- {
- public readonly List PerformedLookups = new List();
-
- public byte[] Get(string name)
- {
- markLookup(name);
- return Array.Empty();
- }
-
- public Task GetAsync(string name)
- {
- markLookup(name);
- return Task.FromResult(Array.Empty());
- }
-
- public Stream GetStream(string name)
- {
- markLookup(name);
- return new MemoryStream();
- }
-
- private void markLookup(string name) => PerformedLookups.Add(name.Substring(name.LastIndexOf(Path.DirectorySeparatorChar) + 1));
-
- public IEnumerable GetAvailableResources() => Enumerable.Empty();
-
- public void Dispose()
- {
- }
- }
-
- private class TestWorkingBeatmap : ClockBackedTestWorkingBeatmap
- {
- private readonly BeatmapInfo skinBeatmapInfo;
- private readonly IResourceStore resourceStore;
-
- public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore resourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio,
- double length = 60000)
- : base(beatmap, storyboard, referenceClock, audio, length)
- {
- this.skinBeatmapInfo = skinBeatmapInfo;
- this.resourceStore = resourceStore;
- }
-
- protected override ISkin GetSkin() => new LegacyBeatmapSkin(skinBeatmapInfo, resourceStore, AudioManager);
+ AssertBeatmapLookup(expected_sample);
}
}
}
diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
index 84506739ab..552d163b2f 100644
--- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.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.IO;
using System.Threading.Tasks;
@@ -10,7 +11,12 @@ using osu.Framework.Audio.Sample;
using osu.Framework.IO.Stores;
using osu.Framework.Testing;
using osu.Game.Audio;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Play;
using osu.Game.Skinning;
+using osu.Game.Storyboards;
+using osu.Game.Storyboards.Drawables;
using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual;
@@ -43,6 +49,27 @@ namespace osu.Game.Tests.Gameplay
AddAssert("sample is non-null", () => channel != null);
}
+ [Test]
+ public void TestSamplePlaybackAtZero()
+ {
+ GameplayClockContainer gameplayContainer = null;
+ DrawableStoryboardSample sample = null;
+
+ AddStep("create container", () =>
+ {
+ Add(gameplayContainer = new GameplayClockContainer(CreateWorkingBeatmap(new OsuRuleset().RulesetInfo), Array.Empty(), 0));
+
+ gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1))
+ {
+ Clock = gameplayContainer.GameplayClock
+ });
+ });
+
+ AddStep("start time", () => gameplayContainer.Start());
+
+ AddUntilStep("sample playback succeeded", () => sample.LifetimeEnd < double.MaxValue);
+ }
+
private class TestSkin : LegacySkin
{
public TestSkin(string resourceName, AudioManager audioManager)
@@ -60,11 +87,11 @@ namespace osu.Game.Tests.Gameplay
this.resourceName = resourceName;
}
- public byte[] Get(string name) => name == resourceName ? TestResources.GetStore().Get("Resources/test-sample.mp3") : null;
+ public byte[] Get(string name) => name == resourceName ? TestResources.GetStore().Get("Resources/Samples/test-sample.mp3") : null;
- public Task GetAsync(string name) => name == resourceName ? TestResources.GetStore().GetAsync("Resources/test-sample.mp3") : null;
+ public Task GetAsync(string name) => name == resourceName ? TestResources.GetStore().GetAsync("Resources/Samples/test-sample.mp3") : null;
- public Stream GetStream(string name) => name == resourceName ? TestResources.GetStore().GetStream("Resources/test-sample.mp3") : null;
+ public Stream GetStream(string name) => name == resourceName ? TestResources.GetStore().GetStream("Resources/Samples/test-sample.mp3") : null;
public IEnumerable GetAvailableResources() => new[] { resourceName };
diff --git a/osu.Game.Tests/Resources/test-sample.mp3 b/osu.Game.Tests/Resources/Samples/test-sample.mp3
similarity index 100%
rename from osu.Game.Tests/Resources/test-sample.mp3
rename to osu.Game.Tests/Resources/Samples/test-sample.mp3
diff --git a/osu.Game.Tests/Resources/Textures/test-image.png b/osu.Game.Tests/Resources/Textures/test-image.png
new file mode 100644
index 0000000000..5d0092edc8
Binary files /dev/null and b/osu.Game.Tests/Resources/Textures/test-image.png differ
diff --git a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs
new file mode 100644
index 0000000000..80f1b02794
--- /dev/null
+++ b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs
@@ -0,0 +1,95 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio.Track;
+using osu.Framework.Configuration.Tracking;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.IO.Stores;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Configuration;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.UI;
+using osu.Game.Tests.Resources;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Testing
+{
+ ///
+ /// A test scene ensuring the dependencies for the
+ /// provided ruleset below are cached at the base implementation.
+ ///
+ [HeadlessTest]
+ public class TestSceneRulesetDependencies : OsuTestScene
+ {
+ protected override Ruleset CreateRuleset() => new TestRuleset();
+
+ [Test]
+ public void TestRetrieveTexture()
+ {
+ AddAssert("ruleset texture retrieved", () =>
+ Dependencies.Get().Get(@"test-image") != null);
+ }
+
+ [Test]
+ public void TestRetrieveSample()
+ {
+ AddAssert("ruleset sample retrieved", () =>
+ Dependencies.Get().Get(@"test-sample") != null);
+ }
+
+ [Test]
+ public void TestResolveConfigManager()
+ {
+ AddAssert("ruleset config resolved", () =>
+ Dependencies.Get() != null);
+ }
+
+ private class TestRuleset : Ruleset
+ {
+ public override string Description => string.Empty;
+ public override string ShortName => string.Empty;
+
+ public TestRuleset()
+ {
+ // temporary ID to let RulesetConfigCache pass our
+ // config manager to the ruleset dependencies.
+ RulesetInfo.ID = -1;
+ }
+
+ public override IResourceStore CreateResourceStore() => new NamespacedResourceStore(TestResources.GetStore(), @"Resources");
+ public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new TestRulesetConfigManager();
+
+ public override IEnumerable GetModsFor(ModType type) => Array.Empty();
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => null;
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null;
+ public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => null;
+ }
+
+ private class TestRulesetConfigManager : IRulesetConfigManager
+ {
+ public void Load()
+ {
+ }
+
+ public bool Save() => true;
+
+ public TrackedSettings CreateTrackedSettings() => new TrackedSettings();
+
+ public void LoadInto(TrackedSettings settings)
+ {
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs
index 20862e9cac..293a6e6869 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs
@@ -4,6 +4,7 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
+using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
@@ -13,13 +14,10 @@ namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneEditorChangeStates : EditorTestScene
{
- public TestSceneEditorChangeStates()
- : base(new OsuRuleset())
- {
- }
-
private EditorBeatmap editorBeatmap;
+ protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
+
public override void SetUpSteps()
{
base.SetUpSteps();
diff --git a/osu.Game.Tests/Visual/Gameplay/OsuPlayerTestScene.cs b/osu.Game.Tests/Visual/Gameplay/OsuPlayerTestScene.cs
new file mode 100644
index 0000000000..cbf8515567
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/OsuPlayerTestScene.cs
@@ -0,0 +1,16 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Osu;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ ///
+ /// A with an arbitrary ruleset value to test with.
+ ///
+ public abstract class OsuPlayerTestScene : PlayerTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs
index 512584bd42..79275d70a7 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs
@@ -10,14 +10,13 @@ using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
-using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Storyboards;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
- public class TestSceneCompletionCancellation : PlayerTestScene
+ public class TestSceneCompletionCancellation : OsuPlayerTestScene
{
private Track track;
@@ -29,11 +28,6 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override bool AllowFail => false;
- public TestSceneCompletionCancellation()
- : base(new OsuRuleset())
- {
- }
-
[SetUpSteps]
public override void SetUpSteps()
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
index 310746d179..2a119f5199 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
@@ -10,23 +10,17 @@ using osu.Framework.Utils;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
-using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Storyboards;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
- public class TestSceneGameplayRewinding : PlayerTestScene
+ public class TestSceneGameplayRewinding : OsuPlayerTestScene
{
[Resolved]
private AudioManager audioManager { get; set; }
- public TestSceneGameplayRewinding()
- : base(new OsuRuleset())
- {
- }
-
private Track track;
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
index 944e6ca6be..387ac42f67 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
@@ -10,14 +10,13 @@ using osu.Framework.Testing;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Cursor;
using osu.Game.Rulesets;
-using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
- public class TestScenePause : PlayerTestScene
+ public class TestScenePause : OsuPlayerTestScene
{
protected new PausePlayer Player => (PausePlayer)base.Player;
@@ -26,7 +25,6 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Container Content => content;
public TestScenePause()
- : base(new OsuRuleset())
{
base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both });
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs
index a83320048b..e43e5ba3ce 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs
@@ -8,12 +8,11 @@ using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
-using osu.Game.Rulesets.Osu;
namespace osu.Game.Tests.Visual.Gameplay
{
[HeadlessTest] // we alter unsafe properties on the game host to test inactive window state.
- public class TestScenePauseWhenInactive : PlayerTestScene
+ public class TestScenePauseWhenInactive : OsuPlayerTestScene
{
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
@@ -27,11 +26,6 @@ namespace osu.Game.Tests.Visual.Gameplay
[Resolved]
private GameHost host { get; set; }
- public TestScenePauseWhenInactive()
- : base(new OsuRuleset())
- {
- }
-
[Test]
public void TestDoesntPauseDuringIntro()
{
diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
index 31afce86ae..c4acf4f7da 100644
--- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
+++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
@@ -17,6 +17,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Rulesets;
+using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osuTK.Graphics;
@@ -100,6 +101,8 @@ namespace osu.Game.Tests.Visual.Navigation
public new BeatmapManager BeatmapManager => base.BeatmapManager;
+ public new ScoreManager ScoreManager => base.ScoreManager;
+
public new SettingsPanel Settings => base.Settings;
public new MusicController MusicController => base.MusicController;
diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs
new file mode 100644
index 0000000000..b2e18849c9
--- /dev/null
+++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs
@@ -0,0 +1,155 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Screens;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mania;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Scoring;
+using osu.Game.Screens.Menu;
+using osu.Game.Screens.Play;
+using osu.Game.Screens.Ranking;
+
+namespace osu.Game.Tests.Visual.Navigation
+{
+ public class TestScenePresentScore : OsuGameTestScene
+ {
+ private BeatmapSetInfo beatmap;
+
+ [SetUpSteps]
+ public new void SetUpSteps()
+ {
+ AddStep("import beatmap", () =>
+ {
+ var difficulty = new BeatmapDifficulty();
+ var metadata = new BeatmapMetadata
+ {
+ Artist = "SomeArtist",
+ AuthorString = "SomeAuthor",
+ Title = "import"
+ };
+
+ beatmap = Game.BeatmapManager.Import(new BeatmapSetInfo
+ {
+ Hash = Guid.NewGuid().ToString(),
+ OnlineBeatmapSetID = 1,
+ Metadata = metadata,
+ Beatmaps = new List
+ {
+ new BeatmapInfo
+ {
+ OnlineBeatmapID = 1 * 1024,
+ Metadata = metadata,
+ BaseDifficulty = difficulty,
+ Ruleset = new OsuRuleset().RulesetInfo
+ },
+ new BeatmapInfo
+ {
+ OnlineBeatmapID = 1 * 2048,
+ Metadata = metadata,
+ BaseDifficulty = difficulty,
+ Ruleset = new OsuRuleset().RulesetInfo
+ },
+ }
+ }).Result;
+ });
+ }
+
+ [Test]
+ public void TestFromMainMenu([Values] ScorePresentType type)
+ {
+ var firstImport = importScore(1);
+ var secondimport = importScore(3);
+
+ presentAndConfirm(firstImport, type);
+ returnToMenu();
+ presentAndConfirm(secondimport, type);
+ returnToMenu();
+ returnToMenu();
+ }
+
+ [Test]
+ public void TestFromMainMenuDifferentRuleset([Values] ScorePresentType type)
+ {
+ var firstImport = importScore(1);
+ var secondimport = importScore(3, new ManiaRuleset().RulesetInfo);
+
+ presentAndConfirm(firstImport, type);
+ returnToMenu();
+ presentAndConfirm(secondimport, type);
+ returnToMenu();
+ returnToMenu();
+ }
+
+ [Test]
+ public void TestFromSongSelect([Values] ScorePresentType type)
+ {
+ var firstImport = importScore(1);
+ presentAndConfirm(firstImport, type);
+
+ var secondimport = importScore(3);
+ presentAndConfirm(secondimport, type);
+ }
+
+ [Test]
+ public void TestFromSongSelectDifferentRuleset([Values] ScorePresentType type)
+ {
+ var firstImport = importScore(1);
+ presentAndConfirm(firstImport, type);
+
+ var secondimport = importScore(3, new ManiaRuleset().RulesetInfo);
+ presentAndConfirm(secondimport, type);
+ }
+
+ private void returnToMenu()
+ {
+ AddStep("return to menu", () => Game.ScreenStack.CurrentScreen.Exit());
+ AddUntilStep("wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
+ }
+
+ private Func importScore(int i, RulesetInfo ruleset = null)
+ {
+ ScoreInfo imported = null;
+ AddStep($"import score {i}", () =>
+ {
+ imported = Game.ScoreManager.Import(new ScoreInfo
+ {
+ Hash = Guid.NewGuid().ToString(),
+ OnlineScoreID = i,
+ Beatmap = beatmap.Beatmaps.First(),
+ Ruleset = ruleset ?? new OsuRuleset().RulesetInfo
+ }).Result;
+ });
+
+ AddAssert($"import {i} succeeded", () => imported != null);
+
+ return () => imported;
+ }
+
+ private void presentAndConfirm(Func getImport, ScorePresentType type)
+ {
+ AddStep("present score", () => Game.PresentScore(getImport(), type));
+
+ switch (type)
+ {
+ case ScorePresentType.Results:
+ AddUntilStep("wait for results", () => Game.ScreenStack.CurrentScreen is ResultsScreen);
+ AddUntilStep("correct score displayed", () => ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score.ID == getImport().ID);
+ AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Ruleset.ID);
+ break;
+
+ case ScorePresentType.Gameplay:
+ AddUntilStep("wait for player loader", () => Game.ScreenStack.CurrentScreen is ReplayPlayerLoader);
+ AddUntilStep("correct score displayed", () => ((ReplayPlayerLoader)Game.ScreenStack.CurrentScreen).Score.ID == getImport().ID);
+ AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Ruleset.ID);
+ break;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs
index 42e6b9087c..08130e60db 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs
@@ -1,52 +1,128 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
-using osu.Game.Online.API.Requests;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics;
-using osu.Game.Overlays.Comments;
using osu.Game.Overlays;
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osu.Game.Users;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Overlays.Comments;
namespace osu.Game.Tests.Visual.Online
{
[TestFixture]
public class TestSceneCommentsContainer : OsuTestScene
{
- protected override bool UseOnlineAPI => true;
-
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
- public TestSceneCommentsContainer()
- {
- BasicScrollContainer scroll;
- TestCommentsContainer comments;
+ private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
- Add(scroll = new BasicScrollContainer
+ private CommentsContainer commentsContainer;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ Child = new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
- Child = comments = new TestCommentsContainer()
+ Child = commentsContainer = new CommentsContainer()
});
- AddStep("Big Black comments", () => comments.ShowComments(CommentableType.Beatmapset, 41823));
- AddStep("Airman comments", () => comments.ShowComments(CommentableType.Beatmapset, 24313));
- AddStep("Lazer build comments", () => comments.ShowComments(CommentableType.Build, 4772));
- AddStep("News comments", () => comments.ShowComments(CommentableType.NewsPost, 715));
- AddStep("Trigger user change", comments.User.TriggerChange);
- AddStep("Idle state", () =>
- {
- scroll.Clear();
- scroll.Add(comments = new TestCommentsContainer());
- });
- }
-
- private class TestCommentsContainer : CommentsContainer
+ [Test]
+ public void TestIdleState()
{
- public new Bindable User => base.User;
+ AddUntilStep("loading spinner shown",
+ () => commentsContainer.ChildrenOfType().Single().IsLoading);
}
+
+ [Test]
+ public void TestSingleCommentsPage()
+ {
+ setUpCommentsResponse(exampleComments);
+ AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
+ AddUntilStep("show more button hidden",
+ () => commentsContainer.ChildrenOfType().Single().Alpha == 0);
+ }
+
+ [Test]
+ public void TestMultipleCommentPages()
+ {
+ var comments = exampleComments;
+ comments.HasMore = true;
+ comments.TopLevelCount = 10;
+
+ setUpCommentsResponse(comments);
+ AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
+ AddUntilStep("show more button visible",
+ () => commentsContainer.ChildrenOfType().Single().Alpha == 1);
+ }
+
+ [Test]
+ public void TestMultipleLoads()
+ {
+ var comments = exampleComments;
+ int topLevelCommentCount = exampleComments.Comments.Count(comment => comment.IsTopLevel);
+
+ AddStep("hide container", () => commentsContainer.Hide());
+ setUpCommentsResponse(comments);
+ AddRepeatStep("show comments multiple times",
+ () => commentsContainer.ShowComments(CommentableType.Beatmapset, 456), 2);
+ AddStep("show container", () => commentsContainer.Show());
+ AddUntilStep("comment count is correct",
+ () => commentsContainer.ChildrenOfType().Count() == topLevelCommentCount);
+ }
+
+ private void setUpCommentsResponse(CommentBundle commentBundle)
+ => AddStep("set up response", () =>
+ {
+ dummyAPI.HandleRequest = request =>
+ {
+ if (!(request is GetCommentsRequest getCommentsRequest))
+ return;
+
+ getCommentsRequest.TriggerSuccess(commentBundle);
+ };
+ });
+
+ private CommentBundle exampleComments => new CommentBundle
+ {
+ Comments = new List
+ {
+ new Comment
+ {
+ Id = 1,
+ Message = "This is a comment",
+ LegacyName = "FirstUser",
+ CreatedAt = DateTimeOffset.Now,
+ VotesCount = 19,
+ RepliesCount = 1
+ },
+ new Comment
+ {
+ Id = 5,
+ ParentId = 1,
+ Message = "This is a child comment",
+ LegacyName = "SecondUser",
+ CreatedAt = DateTimeOffset.Now,
+ VotesCount = 4,
+ },
+ new Comment
+ {
+ Id = 10,
+ Message = "This is another comment",
+ LegacyName = "ThirdUser",
+ CreatedAt = DateTimeOffset.Now,
+ VotesCount = 0
+ },
+ },
+ IncludedComments = new List(),
+ };
}
}
diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs
index e86fd890c1..fc7fcef892 100644
--- a/osu.Game.Tournament/Components/SongBar.cs
+++ b/osu.Game.Tournament/Components/SongBar.cs
@@ -77,6 +77,8 @@ namespace osu.Game.Tournament.Components
flow = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
+ // Todo: This is a hack for https://github.com/ppy/osu-framework/issues/3617 since this container is at the very edge of the screen and potentially initially masked away.
+ Height = 1,
AutoSizeAxes = Axes.Y,
LayoutDuration = 500,
LayoutEasing = Easing.OutQuint,
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index 71771abede..618798a6d8 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -39,6 +39,8 @@ namespace osu.Game.Input.Bindings
new KeyBinding(InputKey.Escape, GlobalAction.Back),
new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back),
+ new KeyBinding(new[] { InputKey.Alt, InputKey.Home }, GlobalAction.Home),
+
new KeyBinding(InputKey.Up, GlobalAction.SelectPrevious),
new KeyBinding(InputKey.Down, GlobalAction.SelectNext),
@@ -152,5 +154,8 @@ namespace osu.Game.Input.Bindings
[Description("Next Selection")]
SelectNext,
+
+ [Description("Home")]
+ Home,
}
}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 7ecd7851d7..b0d7b14d34 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -35,7 +35,6 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input;
using osu.Game.Overlays.Notifications;
-using osu.Game.Screens.Play;
using osu.Game.Input.Bindings;
using osu.Game.Online.Chat;
using osu.Game.Skinning;
@@ -43,6 +42,8 @@ using osuTK.Graphics;
using osu.Game.Overlays.Volume;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
+using osu.Game.Screens.Play;
+using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
using osu.Game.Updater;
using osu.Game.Utils;
@@ -360,7 +361,7 @@ namespace osu.Game
/// Present a score's replay immediately.
/// The user should have already requested this interactively.
///
- public void PresentScore(ScoreInfo score)
+ public void PresentScore(ScoreInfo score, ScorePresentType presentType = ScorePresentType.Results)
{
// The given ScoreInfo may have missing properties if it was retrieved from online data. Re-retrieve it from the database
// to ensure all the required data for presenting a replay are present.
@@ -392,9 +393,19 @@ namespace osu.Game
PerformFromScreen(screen =>
{
+ Ruleset.Value = databasedScore.ScoreInfo.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
- screen.Push(new ReplayPlayerLoader(databasedScore));
+ switch (presentType)
+ {
+ case ScorePresentType.Gameplay:
+ screen.Push(new ReplayPlayerLoader(databasedScore));
+ break;
+
+ case ScorePresentType.Results:
+ screen.Push(new SoloResultsScreen(databasedScore.ScoreInfo));
+ break;
+ }
}, validScreens: new[] { typeof(PlaySongSelect) });
}
@@ -611,6 +622,9 @@ namespace osu.Game
loadComponentSingleFile(screenshotManager, Add);
+ // dependency on notification overlay, dependent by settings overlay
+ loadComponentSingleFile(CreateUpdateManager(), Add, true);
+
// overlay elements
loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true);
loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true);
@@ -643,7 +657,6 @@ namespace osu.Game
chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible;
Add(externalLinkOpener = new ExternalLinkOpener());
- Add(CreateUpdateManager()); // dependency on notification overlay
// side overlays which cancel each other.
var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications };
@@ -1000,4 +1013,10 @@ namespace osu.Game
Exit();
}
}
+
+ public enum ScorePresentType
+ {
+ Results,
+ Gameplay
+ }
}
diff --git a/osu.Game/Overlays/BeatmapListing/SearchGenre.cs b/osu.Game/Overlays/BeatmapListing/SearchGenre.cs
index b12bba6249..de437fac3e 100644
--- a/osu.Game/Overlays/BeatmapListing/SearchGenre.cs
+++ b/osu.Game/Overlays/BeatmapListing/SearchGenre.cs
@@ -20,6 +20,10 @@ namespace osu.Game.Overlays.BeatmapListing
[Description("Hip Hop")]
HipHop = 9,
- Electronic = 10
+ Electronic = 10,
+ Metal = 11,
+ Classical = 12,
+ Folk = 13,
+ Jazz = 14
}
}
diff --git a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs
index dac7e4f1a2..eee5d8f7e1 100644
--- a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs
+++ b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs
@@ -11,8 +11,8 @@ namespace osu.Game.Overlays.BeatmapListing
[Order(0)]
Any,
- [Order(11)]
- Other,
+ [Order(14)]
+ Unspecified,
[Order(1)]
English,
@@ -23,7 +23,7 @@ namespace osu.Game.Overlays.BeatmapListing
[Order(2)]
Chinese,
- [Order(10)]
+ [Order(12)]
Instrumental,
[Order(7)]
@@ -42,6 +42,15 @@ namespace osu.Game.Overlays.BeatmapListing
Spanish,
[Order(5)]
- Italian
+ Italian,
+
+ [Order(10)]
+ Russian,
+
+ [Order(11)]
+ Polish,
+
+ [Order(13)]
+ Other
}
}
diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs
index cb6abb7cc6..19c6f437b6 100644
--- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs
+++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs
@@ -78,19 +78,10 @@ namespace osu.Game.Overlays.Chat.Tabs
/// The channel that is going to be removed.
public void RemoveChannel(Channel channel)
{
- if (Current.Value == channel)
- {
- var allChannels = TabContainer.AllTabItems.Select(tab => tab.Value).ToList();
- var isNextTabSelector = allChannels[allChannels.IndexOf(channel) + 1] == selectorTab.Value;
-
- // selectorTab is not switchable, so we have to explicitly select it if it's the only tab left
- if (isNextTabSelector && allChannels.Count == 2)
- SelectTab(selectorTab);
- else
- SwitchTab(isNextTabSelector ? -1 : 1);
- }
-
RemoveItem(channel);
+
+ if (SelectedTab == null)
+ SelectTab(selectorTab);
}
protected override void SelectTab(TabItem tab)
diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs
index e7bfeaf968..f71808ba89 100644
--- a/osu.Game/Overlays/Comments/CommentsContainer.cs
+++ b/osu.Game/Overlays/Comments/CommentsContainer.cs
@@ -12,6 +12,7 @@ using osu.Game.Online.API.Requests.Responses;
using System.Threading;
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Threading;
using osu.Game.Users;
namespace osu.Game.Overlays.Comments
@@ -30,6 +31,7 @@ namespace osu.Game.Overlays.Comments
private IAPIProvider api { get; set; }
private GetCommentsRequest request;
+ private ScheduledDelegate scheduledCommentsLoad;
private CancellationTokenSource loadCancellation;
private int currentPage;
@@ -152,8 +154,9 @@ namespace osu.Game.Overlays.Comments
request?.Cancel();
loadCancellation?.Cancel();
+ scheduledCommentsLoad?.Cancel();
request = new GetCommentsRequest(id.Value, type.Value, Sort.Value, currentPage++, 0);
- request.Success += res => Schedule(() => onSuccess(res));
+ request.Success += res => scheduledCommentsLoad = Schedule(() => onSuccess(res));
api.PerformAsync(request);
}
diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
index 95a1868392..9c7d0b0be4 100644
--- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
@@ -1,19 +1,26 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Threading.Tasks;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Overlays.Settings.Sections.Maintenance;
+using osu.Game.Updater;
namespace osu.Game.Overlays.Settings.Sections.General
{
public class UpdateSettings : SettingsSubsection
{
+ [Resolved(CanBeNull = true)]
+ private UpdateManager updateManager { get; set; }
+
protected override string Header => "Updates";
+ private SettingsButton checkForUpdatesButton;
+
[BackgroundDependencyLoader(true)]
private void load(Storage storage, OsuConfigManager config, OsuGame game)
{
@@ -23,6 +30,19 @@ namespace osu.Game.Overlays.Settings.Sections.General
Bindable = config.GetBindable(OsuSetting.ReleaseStream),
});
+ if (updateManager?.CanCheckForUpdate == true)
+ {
+ Add(checkForUpdatesButton = new SettingsButton
+ {
+ Text = "Check for updates",
+ Action = () =>
+ {
+ checkForUpdatesButton.Enabled.Value = false;
+ Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(t => Schedule(() => checkForUpdatesButton.Enabled.Value = true));
+ }
+ });
+ }
+
if (RuntimeInfo.IsDesktop)
{
Add(new SettingsButton
diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs
index 3d66d3c28e..86a3f5d8aa 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs
@@ -62,6 +62,7 @@ namespace osu.Game.Overlays.Toolbar
protected ConstrainedIconContainer IconContainer;
protected SpriteText DrawableText;
protected Box HoverBackground;
+ private readonly Box flashBackground;
private readonly FillFlowContainer tooltipContainer;
private readonly SpriteText tooltip1;
private readonly SpriteText tooltip2;
@@ -82,6 +83,13 @@ namespace osu.Game.Overlays.Toolbar
Blending = BlendingParameters.Additive,
Alpha = 0,
},
+ flashBackground = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ Colour = Color4.White.Opacity(100),
+ Blending = BlendingParameters.Additive,
+ },
Flow = new FillFlowContainer
{
Direction = FillDirection.Horizontal,
@@ -139,7 +147,7 @@ namespace osu.Game.Overlays.Toolbar
protected override bool OnClick(ClickEvent e)
{
- HoverBackground.FlashColour(Color4.White.Opacity(100), 500, Easing.OutQuint);
+ flashBackground.FadeOutFromOne(800, Easing.OutQuint);
tooltipContainer.FadeOut(100);
return base.OnClick(e);
}
diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs
index 6f5e703a66..e642f0c453 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs
@@ -2,10 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Bindings;
+using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar
{
- public class ToolbarHomeButton : ToolbarButton
+ public class ToolbarHomeButton : ToolbarButton, IKeyBindingHandler
{
public ToolbarHomeButton()
{
@@ -13,5 +15,20 @@ namespace osu.Game.Overlays.Toolbar
TooltipMain = "Home";
TooltipSub = "Return to the main menu";
}
+
+ public bool OnPressed(GlobalAction action)
+ {
+ if (action == GlobalAction.Home)
+ {
+ Click();
+ return true;
+ }
+
+ return false;
+ }
+
+ public void OnReleased(GlobalAction action)
+ {
+ }
}
}
diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
index c1f3e357a1..df059eef7d 100644
--- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs
+++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
@@ -26,6 +26,9 @@ namespace osu.Game.Rulesets.Mods
[SettingSource("Final rate", "The final speed to ramp to")]
public abstract BindableNumber FinalRate { get; }
+ [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
+ public abstract BindableBool AdjustPitch { get; }
+
public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x";
private double finalRateTime;
@@ -43,15 +46,16 @@ namespace osu.Game.Rulesets.Mods
protected ModTimeRamp()
{
// for preview purpose at song select. eventually we'll want to be able to update every frame.
- FinalRate.BindValueChanged(val => applyAdjustment(1), true);
+ FinalRate.BindValueChanged(val => applyRateAdjustment(1), true);
+ AdjustPitch.BindValueChanged(applyPitchAdjustment);
}
public void ApplyToTrack(Track track)
{
this.track = track;
- track.AddAdjustment(AdjustableProperty.Frequency, SpeedChange);
FinalRate.TriggerChange();
+ AdjustPitch.TriggerChange();
}
public virtual void ApplyToBeatmap(IBeatmap beatmap)
@@ -66,14 +70,25 @@ namespace osu.Game.Rulesets.Mods
public virtual void Update(Playfield playfield)
{
- applyAdjustment((track.CurrentTime - beginRampTime) / finalRateTime);
+ applyRateAdjustment((track.CurrentTime - beginRampTime) / finalRateTime);
}
///
/// Adjust the rate along the specified ramp
///
/// The amount of adjustment to apply (from 0..1).
- private void applyAdjustment(double amount) =>
+ private void applyRateAdjustment(double amount) =>
SpeedChange.Value = InitialRate.Value + (FinalRate.Value - InitialRate.Value) * Math.Clamp(amount, 0, 1);
+
+ private void applyPitchAdjustment(ValueChangedEvent adjustPitchSetting)
+ {
+ // remove existing old adjustment
+ track.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange);
+
+ track.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange);
+ }
+
+ private AdjustableProperty adjustmentForPitchSetting(bool adjustPitchSettingValue)
+ => adjustPitchSettingValue ? AdjustableProperty.Frequency : AdjustableProperty.Tempo;
}
}
diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs
index 5e634ac434..679b50057b 100644
--- a/osu.Game/Rulesets/Mods/ModWindDown.cs
+++ b/osu.Game/Rulesets/Mods/ModWindDown.cs
@@ -37,6 +37,13 @@ namespace osu.Game.Rulesets.Mods
Precision = 0.01,
};
+ [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
+ public override BindableBool AdjustPitch { get; } = new BindableBool
+ {
+ Default = true,
+ Value = true
+ };
+
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray();
}
}
diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs
index 74c6fc22d3..b733bf423e 100644
--- a/osu.Game/Rulesets/Mods/ModWindUp.cs
+++ b/osu.Game/Rulesets/Mods/ModWindUp.cs
@@ -37,6 +37,13 @@ namespace osu.Game.Rulesets.Mods
Precision = 0.01,
};
+ [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
+ public override BindableBool AdjustPitch { get; } = new BindableBool
+ {
+ Default = true,
+ Value = true
+ };
+
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray();
}
}
diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs
index 982f527517..ef341575fa 100644
--- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs
@@ -44,6 +44,7 @@ namespace osu.Game.Rulesets.Scoring
private double gameplayEndTime;
private readonly double drainStartTime;
+ private readonly double drainLenience;
private readonly List<(double time, double health)> healthIncreases = new List<(double, double)>();
private double targetMinimumHealth;
@@ -55,9 +56,14 @@ namespace osu.Game.Rulesets.Scoring
/// Creates a new .
///
/// The time after which draining should begin.
- public DrainingHealthProcessor(double drainStartTime)
+ /// A lenience to apply to the default drain rate.
+ /// A value of 0 uses the default drain rate.
+ /// A value of 0.5 halves the drain rate.
+ /// A value of 1 completely removes drain.
+ public DrainingHealthProcessor(double drainStartTime, double drainLenience = 0)
{
this.drainStartTime = drainStartTime;
+ this.drainLenience = drainLenience;
}
protected override void Update()
@@ -96,6 +102,12 @@ namespace osu.Game.Rulesets.Scoring
targetMinimumHealth = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, min_health_target, mid_health_target, max_health_target);
+ // Add back a portion of the amount of HP to be drained, depending on the lenience requested.
+ targetMinimumHealth += drainLenience * (1 - targetMinimumHealth);
+
+ // Ensure the target HP is within an acceptable range.
+ targetMinimumHealth = Math.Clamp(targetMinimumHealth, 0, 1);
+
base.ApplyBeatmap(beatmap);
}
diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs
index 9a10b7d1b2..fbb9acfe90 100644
--- a/osu.Game/Rulesets/UI/DrawableRuleset.cs
+++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs
@@ -11,20 +11,13 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Threading;
-using System.Threading.Tasks;
using JetBrains.Annotations;
-using osu.Framework.Audio;
-using osu.Framework.Audio.Sample;
-using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Cursor;
-using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.Input.Events;
-using osu.Framework.IO.Stores;
using osu.Game.Configuration;
using osu.Game.Graphics.Cursor;
using osu.Game.Input.Handlers;
@@ -63,10 +56,6 @@ namespace osu.Game.Rulesets.UI
private readonly Lazy playfield;
- private TextureStore textureStore;
-
- private ISampleStore localSampleStore;
-
///
/// The playfield.
///
@@ -113,6 +102,8 @@ namespace osu.Game.Rulesets.UI
private OnScreenDisplay onScreenDisplay;
+ private DrawableRulesetDependencies dependencies;
+
///
/// Creates a ruleset visualisation for the provided ruleset and beatmap.
///
@@ -147,30 +138,13 @@ namespace osu.Game.Rulesets.UI
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
- var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+ dependencies = new DrawableRulesetDependencies(Ruleset, base.CreateChildDependencies(parent));
- var resources = Ruleset.CreateResourceStore();
-
- if (resources != null)
- {
- textureStore = new TextureStore(new TextureLoaderStore(new NamespacedResourceStore(resources, "Textures")));
- textureStore.AddStore(dependencies.Get());
- dependencies.Cache(textureStore);
-
- localSampleStore = dependencies.Get().GetSampleStore(new NamespacedResourceStore(resources, "Samples"));
- localSampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY;
- dependencies.CacheAs(new FallbackSampleStore(localSampleStore, dependencies.Get()));
- }
+ Config = dependencies.RulesetConfigManager;
onScreenDisplay = dependencies.Get();
-
- Config = dependencies.Get().GetConfigFor(Ruleset);
-
if (Config != null)
- {
- dependencies.Cache(Config);
onScreenDisplay?.BeginTracking(this, Config);
- }
return dependencies;
}
@@ -362,13 +336,14 @@ namespace osu.Game.Rulesets.UI
{
base.Dispose(isDisposing);
- localSampleStore?.Dispose();
-
if (Config != null)
{
onScreenDisplay?.StopTracking(this, Config);
Config = null;
}
+
+ // Dispose the components created by this dependency container.
+ dependencies?.Dispose();
}
}
@@ -524,62 +499,4 @@ namespace osu.Game.Rulesets.UI
{
}
}
-
- ///
- /// A sample store which adds a fallback source.
- ///
- ///
- /// This is a temporary implementation to workaround ISampleStore limitations.
- ///
- public class FallbackSampleStore : ISampleStore
- {
- private readonly ISampleStore primary;
- private readonly ISampleStore secondary;
-
- public FallbackSampleStore(ISampleStore primary, ISampleStore secondary)
- {
- this.primary = primary;
- this.secondary = secondary;
- }
-
- public SampleChannel Get(string name) => primary.Get(name) ?? secondary.Get(name);
-
- public Task GetAsync(string name) => primary.GetAsync(name) ?? secondary.GetAsync(name);
-
- public Stream GetStream(string name) => primary.GetStream(name) ?? secondary.GetStream(name);
-
- public IEnumerable GetAvailableResources() => throw new NotSupportedException();
-
- public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException();
-
- public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException();
-
- public BindableNumber Volume => throw new NotSupportedException();
-
- public BindableNumber Balance => throw new NotSupportedException();
-
- public BindableNumber Frequency => throw new NotSupportedException();
-
- public BindableNumber Tempo => throw new NotSupportedException();
-
- public IBindable GetAggregate(AdjustableProperty type) => throw new NotSupportedException();
-
- public IBindable AggregateVolume => throw new NotSupportedException();
-
- public IBindable AggregateBalance => throw new NotSupportedException();
-
- public IBindable AggregateFrequency => throw new NotSupportedException();
-
- public IBindable AggregateTempo => throw new NotSupportedException();
-
- public int PlaybackConcurrency
- {
- get => throw new NotSupportedException();
- set => throw new NotSupportedException();
- }
-
- public void Dispose()
- {
- }
- }
}
diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs
new file mode 100644
index 0000000000..168e937256
--- /dev/null
+++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs
@@ -0,0 +1,148 @@
+// 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.IO;
+using System.Threading.Tasks;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Audio.Track;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.IO.Stores;
+using osu.Game.Rulesets.Configuration;
+
+namespace osu.Game.Rulesets.UI
+{
+ public class DrawableRulesetDependencies : DependencyContainer, IDisposable
+ {
+ ///
+ /// The texture store to be used for the ruleset.
+ ///
+ public TextureStore TextureStore { get; }
+
+ ///
+ /// The sample store to be used for the ruleset.
+ ///
+ ///
+ /// This is the local sample store pointing to the ruleset sample resources,
+ /// the cached sample store () retrieves from
+ /// this store and falls back to the parent store if this store doesn't have the requested sample.
+ ///
+ public ISampleStore SampleStore { get; }
+
+ ///
+ /// The ruleset config manager.
+ ///
+ public IRulesetConfigManager RulesetConfigManager { get; private set; }
+
+ public DrawableRulesetDependencies(Ruleset ruleset, IReadOnlyDependencyContainer parent)
+ : base(parent)
+ {
+ var resources = ruleset.CreateResourceStore();
+
+ if (resources != null)
+ {
+ TextureStore = new TextureStore(new TextureLoaderStore(new NamespacedResourceStore(resources, @"Textures")));
+ TextureStore.AddStore(parent.Get());
+ Cache(TextureStore);
+
+ SampleStore = parent.Get().GetSampleStore(new NamespacedResourceStore(resources, @"Samples"));
+ SampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY;
+ CacheAs(new FallbackSampleStore(SampleStore, parent.Get()));
+ }
+
+ RulesetConfigManager = parent.Get().GetConfigFor(ruleset);
+ if (RulesetConfigManager != null)
+ Cache(RulesetConfigManager);
+ }
+
+ #region Disposal
+
+ ~DrawableRulesetDependencies()
+ {
+ Dispose(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private bool isDisposed;
+
+ protected void Dispose(bool disposing)
+ {
+ if (isDisposed)
+ return;
+
+ isDisposed = true;
+
+ SampleStore?.Dispose();
+ RulesetConfigManager = null;
+ }
+
+ #endregion
+ }
+
+ ///
+ /// A sample store which adds a fallback source.
+ ///
+ ///
+ /// This is a temporary implementation to workaround ISampleStore limitations.
+ ///
+ public class FallbackSampleStore : ISampleStore
+ {
+ private readonly ISampleStore primary;
+ private readonly ISampleStore secondary;
+
+ public FallbackSampleStore(ISampleStore primary, ISampleStore secondary)
+ {
+ this.primary = primary;
+ this.secondary = secondary;
+ }
+
+ public SampleChannel Get(string name) => primary.Get(name) ?? secondary.Get(name);
+
+ public Task GetAsync(string name) => primary.GetAsync(name) ?? secondary.GetAsync(name);
+
+ public Stream GetStream(string name) => primary.GetStream(name) ?? secondary.GetStream(name);
+
+ public IEnumerable GetAvailableResources() => throw new NotSupportedException();
+
+ public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException();
+
+ public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException();
+
+ public BindableNumber Volume => throw new NotSupportedException();
+
+ public BindableNumber Balance => throw new NotSupportedException();
+
+ public BindableNumber Frequency => throw new NotSupportedException();
+
+ public BindableNumber Tempo => throw new NotSupportedException();
+
+ public IBindable GetAggregate(AdjustableProperty type) => throw new NotSupportedException();
+
+ public IBindable AggregateVolume => throw new NotSupportedException();
+
+ public IBindable AggregateBalance => throw new NotSupportedException();
+
+ public IBindable AggregateFrequency => throw new NotSupportedException();
+
+ public IBindable AggregateTempo => throw new NotSupportedException();
+
+ public int PlaybackConcurrency
+ {
+ get => throw new NotSupportedException();
+ set => throw new NotSupportedException();
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+}
diff --git a/osu.Game/Scoring/LegacyDatabasedScore.cs b/osu.Game/Scoring/LegacyDatabasedScore.cs
index bd673eaa29..8908775472 100644
--- a/osu.Game/Scoring/LegacyDatabasedScore.cs
+++ b/osu.Game/Scoring/LegacyDatabasedScore.cs
@@ -16,7 +16,10 @@ namespace osu.Game.Scoring
{
ScoreInfo = score;
- var replayFilename = score.Files.First(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath;
+ var replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath;
+
+ if (replayFilename == null)
+ return;
using (var stream = store.GetStream(replayFilename))
Replay = new DatabasedLegacyScoreDecoder(rulesets, beatmaps).Parse(stream).Replay;
diff --git a/osu.Game/Scoring/ScoreStore.cs b/osu.Game/Scoring/ScoreStore.cs
index 9627481f4d..f5c5cd5dad 100644
--- a/osu.Game/Scoring/ScoreStore.cs
+++ b/osu.Game/Scoring/ScoreStore.cs
@@ -18,6 +18,8 @@ namespace osu.Game.Scoring
protected override IQueryable AddIncludesForConsumption(IQueryable query)
=> base.AddIncludesForConsumption(query)
.Include(s => s.Beatmap)
+ .Include(s => s.Beatmap).ThenInclude(b => b.Metadata)
+ .Include(s => s.Beatmap).ThenInclude(b => b.BeatmapSet).ThenInclude(s => s.Metadata)
.Include(s => s.Ruleset);
}
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
index d07cffff0c..cc417bbb10 100644
--- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
@@ -44,8 +44,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
private readonly BindableList selectedHitObjects = new BindableList();
- public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
-
[Resolved(canBeNull: true)]
private IPositionSnapProvider snapProvider { get; set; }
diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
index 0b5d8262fd..e1f311f1b8 100644
--- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
@@ -11,6 +11,7 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
+using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components
{
@@ -26,6 +27,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
private readonly Container placementBlueprintContainer;
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
+
private InputManager inputManager;
private readonly IEnumerable drawableHitObjects;
diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs
index 2f85d6ad1e..0653373c91 100644
--- a/osu.Game/Screens/Play/GameplayClockContainer.cs
+++ b/osu.Game/Screens/Play/GameplayClockContainer.cs
@@ -251,8 +251,9 @@ namespace osu.Game.Screens.Play
private class HardwareCorrectionOffsetClock : FramedOffsetClock
{
- // we always want to apply the same real-time offset, so it should be adjusted by the playback rate to achieve this.
- public override double CurrentTime => SourceTime + Offset * Rate;
+ // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this.
+ // base implementation already adds offset at 1.0 rate, so we only add the difference from that here.
+ public override double CurrentTime => base.CurrentTime + Offset * (Rate - 1);
public HardwareCorrectionOffsetClock(IClock source, bool processSource = true)
: base(source, processSource)
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 83991ad027..d3b88e56ae 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -125,6 +125,8 @@ namespace osu.Game.Screens.Play
private GameplayBeatmap gameplayBeatmap;
+ private ScreenSuspensionHandler screenSuspension;
+
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
@@ -179,6 +181,7 @@ namespace osu.Game.Screens.Play
InternalChild = GameplayClockContainer = new GameplayClockContainer(Beatmap.Value, Mods.Value, DrawableRuleset.GameplayStartTime);
AddInternal(gameplayBeatmap = new GameplayBeatmap(playableBeatmap));
+ AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer));
dependencies.CacheAs(gameplayBeatmap);
@@ -628,12 +631,16 @@ namespace osu.Game.Screens.Play
public override void OnSuspending(IScreen next)
{
+ screenSuspension?.Expire();
+
fadeOut();
base.OnSuspending(next);
}
public override bool OnExiting(IScreen next)
{
+ screenSuspension?.Expire();
+
if (completionProgressDelegate != null && !completionProgressDelegate.Cancelled && !completionProgressDelegate.Completed)
{
// proceed to result screen if beatmap already finished playing
diff --git a/osu.Game/Screens/Play/ReplayPlayerLoader.cs b/osu.Game/Screens/Play/ReplayPlayerLoader.cs
index 4572570437..9eff4cb8fc 100644
--- a/osu.Game/Screens/Play/ReplayPlayerLoader.cs
+++ b/osu.Game/Screens/Play/ReplayPlayerLoader.cs
@@ -9,7 +9,7 @@ namespace osu.Game.Screens.Play
{
public class ReplayPlayerLoader : PlayerLoader
{
- private readonly ScoreInfo scoreInfo;
+ public readonly ScoreInfo Score;
public ReplayPlayerLoader(Score score)
: base(() => new ReplayPlayer(score))
@@ -17,14 +17,14 @@ namespace osu.Game.Screens.Play
if (score.Replay == null)
throw new ArgumentException($"{nameof(score)} must have a non-null {nameof(score.Replay)}.", nameof(score));
- scoreInfo = score.ScoreInfo;
+ Score = score.ScoreInfo;
}
public override void OnEntering(IScreen last)
{
// these will be reverted thanks to PlayerLoader's lease.
- Mods.Value = scoreInfo.Mods;
- Ruleset.Value = scoreInfo.Ruleset;
+ Mods.Value = Score.Mods;
+ Ruleset.Value = Score.Ruleset;
base.OnEntering(last);
}
diff --git a/osu.Game/Screens/Play/ScreenSuspensionHandler.cs b/osu.Game/Screens/Play/ScreenSuspensionHandler.cs
new file mode 100644
index 0000000000..8585a5c309
--- /dev/null
+++ b/osu.Game/Screens/Play/ScreenSuspensionHandler.cs
@@ -0,0 +1,52 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Diagnostics;
+using JetBrains.Annotations;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Platform;
+
+namespace osu.Game.Screens.Play
+{
+ ///
+ /// Ensures screen is not suspended / dimmed while gameplay is active.
+ ///
+ public class ScreenSuspensionHandler : Component
+ {
+ private readonly GameplayClockContainer gameplayClockContainer;
+ private Bindable isPaused;
+
+ [Resolved]
+ private GameHost host { get; set; }
+
+ public ScreenSuspensionHandler([NotNull] GameplayClockContainer gameplayClockContainer)
+ {
+ this.gameplayClockContainer = gameplayClockContainer ?? throw new ArgumentNullException(nameof(gameplayClockContainer));
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ // This is the only usage game-wide of suspension changes.
+ // Assert to ensure we don't accidentally forget this in the future.
+ Debug.Assert(host.AllowScreenSuspension.Value);
+
+ isPaused = gameplayClockContainer.IsPaused.GetBoundCopy();
+ isPaused.BindValueChanged(paused => host.AllowScreenSuspension.Value = paused.NewValue, true);
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ isPaused?.UnbindAll();
+
+ if (host != null)
+ host.AllowScreenSuspension.Value = true;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs
index 9d4e3af230..d0142e57fe 100644
--- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs
+++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Screens.Ranking
switch (State.Value)
{
case DownloadState.LocallyAvailable:
- game?.PresentScore(Model.Value);
+ game?.PresentScore(Model.Value, ScorePresentType.Gameplay);
break;
case DownloadState.NotDownloaded:
diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
index c76d5c8784..4990ca8e60 100644
--- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
@@ -43,6 +43,12 @@ namespace osu.Game.Skinning
MinimumColumnWidth,
LeftStageImage,
RightStageImage,
- BottomStageImage
+ BottomStageImage,
+ Hit300g,
+ Hit300,
+ Hit200,
+ Hit100,
+ Hit50,
+ Hit0,
}
}
diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
index a988bd589f..0806676fde 100644
--- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
@@ -111,11 +111,10 @@ namespace osu.Game.Skinning
HandleColours(currentConfig, line);
break;
+ // Custom sprite paths
case string _ when pair.Key.StartsWith("NoteImage"):
- currentConfig.ImageLookups[pair.Key] = pair.Value;
- break;
-
case string _ when pair.Key.StartsWith("KeyImage"):
+ case string _ when pair.Key.StartsWith("Hit"):
currentConfig.ImageLookups[pair.Key] = pair.Value;
break;
}
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 003fa24d5b..0b2b723440 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -257,6 +257,14 @@ namespace osu.Game.Skinning
case LegacyManiaSkinConfigurationLookups.RightLineWidth:
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As(new Bindable(existing.ColumnLineWidth[maniaLookup.TargetColumn.Value + 1]));
+
+ case LegacyManiaSkinConfigurationLookups.Hit0:
+ case LegacyManiaSkinConfigurationLookups.Hit50:
+ case LegacyManiaSkinConfigurationLookups.Hit100:
+ case LegacyManiaSkinConfigurationLookups.Hit200:
+ case LegacyManiaSkinConfigurationLookups.Hit300:
+ case LegacyManiaSkinConfigurationLookups.Hit300g:
+ return SkinUtils.As(getManiaImage(existing, maniaLookup.Lookup.ToString()));
}
return null;
diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs
new file mode 100644
index 0000000000..1131c93288
--- /dev/null
+++ b/osu.Game/Skinning/LegacySkinTransformer.cs
@@ -0,0 +1,35 @@
+// 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.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
+
+namespace osu.Game.Skinning
+{
+ ///
+ /// Transformer used to handle support of legacy features for individual rulesets.
+ ///
+ public abstract class LegacySkinTransformer : ISkin
+ {
+ ///
+ /// Source of the which is being transformed.
+ ///
+ protected ISkinSource Source { get; }
+
+ protected LegacySkinTransformer(ISkinSource source)
+ {
+ Source = source;
+ }
+
+ public abstract Drawable GetDrawableComponent(ISkinComponent component);
+
+ public Texture GetTexture(string componentName) => Source.GetTexture(componentName);
+
+ public virtual SampleChannel GetSample(ISampleInfo sampleInfo) => Source.GetSample(sampleInfo);
+
+ public abstract IBindable GetConfig(TLookup lookup);
+ }
+}
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
index f3f8308964..8292b02068 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Storyboards.Drawables
LifetimeStart = sampleInfo.StartTime;
LifetimeEnd = double.MaxValue;
}
- else if (Time.Current - Time.Elapsed < sampleInfo.StartTime)
+ else if (Time.Current - Time.Elapsed <= sampleInfo.StartTime)
{
// We've passed the start time of the sample. We only play the sample if we're within an allowable range
// from the sample's start, to reduce layering if we've been fast-forwarded far into the future
diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs
new file mode 100644
index 0000000000..b4ce322165
--- /dev/null
+++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs
@@ -0,0 +1,184 @@
+// 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.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.IO.Stores;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Formats;
+using osu.Game.IO;
+using osu.Game.Rulesets;
+using osu.Game.Skinning;
+using osu.Game.Storyboards;
+using osu.Game.Tests.Visual;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Beatmaps
+{
+ public abstract class HitObjectSampleTest : PlayerTestScene
+ {
+ protected abstract IResourceStore Resources { get; }
+
+ private readonly SkinInfo userSkinInfo = new SkinInfo();
+
+ private readonly BeatmapInfo beatmapInfo = new BeatmapInfo
+ {
+ BeatmapSet = new BeatmapSetInfo(),
+ Metadata = new BeatmapMetadata
+ {
+ Author = User.SYSTEM_USER
+ }
+ };
+
+ private readonly TestResourceStore userSkinResourceStore = new TestResourceStore();
+ private readonly TestResourceStore beatmapSkinResourceStore = new TestResourceStore();
+ private SkinSourceDependencyContainer dependencies;
+ private IBeatmap currentTestBeatmap;
+ protected sealed override bool HasCustomSteps => true;
+
+ protected sealed override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ => new DependencyContainer(dependencies = new SkinSourceDependencyContainer(base.CreateChildDependencies(parent)));
+
+ protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestBeatmap;
+
+ protected sealed override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
+ => new TestWorkingBeatmap(beatmapInfo, beatmapSkinResourceStore, beatmap, storyboard, Clock, Audio);
+
+ protected void CreateTestWithBeatmap(string filename)
+ {
+ CreateTest(() =>
+ {
+ AddStep("clear performed lookups", () =>
+ {
+ userSkinResourceStore.PerformedLookups.Clear();
+ beatmapSkinResourceStore.PerformedLookups.Clear();
+ });
+
+ AddStep($"load {filename}", () =>
+ {
+ using (var reader = new LineBufferedReader(Resources.GetStream($"Resources/SampleLookups/{filename}")))
+ currentTestBeatmap = Decoder.GetDecoder(reader).Decode(reader);
+ });
+ });
+ }
+
+ protected void SetupSkins(string beatmapFile, string userFile)
+ {
+ AddStep("setup skins", () =>
+ {
+ userSkinInfo.Files = new List
+ {
+ new SkinFileInfo
+ {
+ Filename = userFile,
+ FileInfo = new IO.FileInfo { Hash = userFile }
+ }
+ };
+
+ beatmapInfo.BeatmapSet.Files = new List
+ {
+ new BeatmapSetFileInfo
+ {
+ Filename = beatmapFile,
+ FileInfo = new IO.FileInfo { Hash = beatmapFile }
+ }
+ };
+
+ // Need to refresh the cached skin source to refresh the skin resource store.
+ dependencies.SkinSource = new SkinProvidingContainer(new LegacySkin(userSkinInfo, userSkinResourceStore, Audio));
+ });
+ }
+
+ protected void AssertBeatmapLookup(string name) => AddAssert($"\"{name}\" looked up from beatmap skin",
+ () => !userSkinResourceStore.PerformedLookups.Contains(name) && beatmapSkinResourceStore.PerformedLookups.Contains(name));
+
+ protected void AssertUserLookup(string name) => AddAssert($"\"{name}\" looked up from user skin",
+ () => !beatmapSkinResourceStore.PerformedLookups.Contains(name) && userSkinResourceStore.PerformedLookups.Contains(name));
+
+ private class SkinSourceDependencyContainer : IReadOnlyDependencyContainer
+ {
+ public ISkinSource SkinSource;
+
+ private readonly IReadOnlyDependencyContainer fallback;
+
+ public SkinSourceDependencyContainer(IReadOnlyDependencyContainer fallback)
+ {
+ this.fallback = fallback;
+ }
+
+ public object Get(Type type)
+ {
+ if (type == typeof(ISkinSource))
+ return SkinSource;
+
+ return fallback.Get(type);
+ }
+
+ public object Get(Type type, CacheInfo info)
+ {
+ if (type == typeof(ISkinSource))
+ return SkinSource;
+
+ return fallback.Get(type, info);
+ }
+
+ public void Inject(T instance) where T : class
+ {
+ // Never used directly
+ }
+ }
+
+ private class TestResourceStore : IResourceStore
+ {
+ public readonly List PerformedLookups = new List();
+
+ public byte[] Get(string name)
+ {
+ markLookup(name);
+ return Array.Empty();
+ }
+
+ public Task GetAsync(string name)
+ {
+ markLookup(name);
+ return Task.FromResult(Array.Empty());
+ }
+
+ public Stream GetStream(string name)
+ {
+ markLookup(name);
+ return new MemoryStream();
+ }
+
+ private void markLookup(string name) => PerformedLookups.Add(name.Substring(name.LastIndexOf(Path.DirectorySeparatorChar) + 1));
+
+ public IEnumerable GetAvailableResources() => Enumerable.Empty();
+
+ public void Dispose()
+ {
+ }
+ }
+
+ private class TestWorkingBeatmap : ClockBackedTestWorkingBeatmap
+ {
+ private readonly BeatmapInfo skinBeatmapInfo;
+ private readonly IResourceStore resourceStore;
+
+ public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore resourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio,
+ double length = 60000)
+ : base(beatmap, storyboard, referenceClock, audio, length)
+ {
+ this.skinBeatmapInfo = skinBeatmapInfo;
+ this.resourceStore = resourceStore;
+ }
+
+ protected override ISkin GetSkin() => new LegacyBeatmapSkin(skinBeatmapInfo, resourceStore, AudioManager);
+ }
+ }
+}
diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs
index 2f6e6fb599..cd08f4712a 100644
--- a/osu.Game/Tests/Visual/EditorTestScene.cs
+++ b/osu.Game/Tests/Visual/EditorTestScene.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Testing;
using osu.Game.Rulesets;
@@ -15,17 +16,10 @@ namespace osu.Game.Tests.Visual
{
protected Editor Editor { get; private set; }
- private readonly Ruleset ruleset;
-
- protected EditorTestScene(Ruleset ruleset)
- {
- this.ruleset = ruleset;
- }
-
[BackgroundDependencyLoader]
private void load()
{
- Beatmap.Value = CreateWorkingBeatmap(ruleset.RulesetInfo);
+ Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
}
public override void SetUpSteps()
@@ -37,6 +31,14 @@ namespace osu.Game.Tests.Visual
&& Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
}
+ ///
+ /// Creates the ruleset for providing a corresponding beatmap to load the editor on.
+ ///
+ [NotNull]
+ protected abstract Ruleset CreateEditorRuleset();
+
+ protected sealed override Ruleset CreateRuleset() => CreateEditorRuleset();
+
protected virtual Editor CreateEditor() => new Editor();
}
}
diff --git a/osu.Game/Tests/Visual/ModPerfectTestScene.cs b/osu.Game/Tests/Visual/ModPerfectTestScene.cs
index 95a62bbf65..93b38a149c 100644
--- a/osu.Game/Tests/Visual/ModPerfectTestScene.cs
+++ b/osu.Game/Tests/Visual/ModPerfectTestScene.cs
@@ -10,13 +10,10 @@ namespace osu.Game.Tests.Visual
{
public abstract class ModPerfectTestScene : ModTestScene
{
- private readonly Ruleset ruleset;
private readonly ModPerfect mod;
- protected ModPerfectTestScene(Ruleset ruleset, ModPerfect mod)
- : base(ruleset)
+ protected ModPerfectTestScene(ModPerfect mod)
{
- this.ruleset = ruleset;
this.mod = mod;
}
@@ -25,7 +22,7 @@ namespace osu.Game.Tests.Visual
Mod = mod,
Beatmap = new Beatmap
{
- BeatmapInfo = { Ruleset = ruleset.RulesetInfo },
+ BeatmapInfo = { Ruleset = CreatePlayerRuleset().RulesetInfo },
HitObjects = { testData.HitObject }
},
Autoplay = !shouldMiss,
diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs
index d36a7d8bad..23b5ad0bd8 100644
--- a/osu.Game/Tests/Visual/ModTestScene.cs
+++ b/osu.Game/Tests/Visual/ModTestScene.cs
@@ -14,11 +14,6 @@ namespace osu.Game.Tests.Visual
{
protected sealed override bool HasCustomSteps => true;
- protected ModTestScene(Ruleset ruleset)
- : base(ruleset)
- {
- }
-
private ModTestData currentTestData;
protected void CreateModTest(ModTestData testData) => CreateTest(() =>
diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs
index 632d668a01..cb9ed40b00 100644
--- a/osu.Game/Tests/Visual/OsuTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuTestScene.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
+using JetBrains.Annotations;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@@ -21,6 +22,7 @@ using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.UI;
using osu.Game.Screens;
using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps;
@@ -37,6 +39,8 @@ namespace osu.Game.Tests.Visual
protected new OsuScreenDependencies Dependencies { get; private set; }
+ private DrawableRulesetDependencies rulesetDependencies;
+
private Lazy localStorage;
protected Storage LocalStorage => localStorage.Value;
@@ -65,7 +69,13 @@ namespace osu.Game.Tests.Visual
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
- Dependencies = new OsuScreenDependencies(false, base.CreateChildDependencies(parent));
+ var baseDependencies = base.CreateChildDependencies(parent);
+
+ var providedRuleset = CreateRuleset();
+ if (providedRuleset != null)
+ baseDependencies = rulesetDependencies = new DrawableRulesetDependencies(providedRuleset, baseDependencies);
+
+ Dependencies = new OsuScreenDependencies(false, baseDependencies);
Beatmap = Dependencies.Beatmap;
Beatmap.SetDefault();
@@ -125,6 +135,15 @@ namespace osu.Game.Tests.Visual
[Resolved]
protected AudioManager Audio { get; private set; }
+ ///
+ /// Creates the ruleset to be used for this test scene.
+ ///
+ ///
+ /// When testing against ruleset-specific components, this method must be overriden to their corresponding ruleset.
+ ///
+ [CanBeNull]
+ protected virtual Ruleset CreateRuleset() => null;
+
protected virtual IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset);
protected WorkingBeatmap CreateWorkingBeatmap(RulesetInfo ruleset) =>
@@ -136,13 +155,15 @@ namespace osu.Game.Tests.Visual
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
- Ruleset.Value = rulesets.AvailableRulesets.First();
+ Ruleset.Value = CreateRuleset()?.RulesetInfo ?? rulesets.AvailableRulesets.First();
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
+ rulesetDependencies?.Dispose();
+
if (Beatmap?.Value.TrackLoaded == true)
Beatmap.Value.Track.Stop();
diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs
index 9e852719e0..2c46e7f6d3 100644
--- a/osu.Game/Tests/Visual/PlayerTestScene.cs
+++ b/osu.Game/Tests/Visual/PlayerTestScene.cs
@@ -3,6 +3,7 @@
using System;
using System.Linq;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Testing;
@@ -19,15 +20,8 @@ namespace osu.Game.Tests.Visual
///
protected virtual bool HasCustomSteps { get; } = false;
- private readonly Ruleset ruleset;
-
protected TestPlayer Player;
- protected PlayerTestScene(Ruleset ruleset)
- {
- this.ruleset = ruleset;
- }
-
protected OsuConfigManager LocalConfig;
[BackgroundDependencyLoader]
@@ -53,7 +47,7 @@ namespace osu.Game.Tests.Visual
action?.Invoke();
- AddStep(ruleset.RulesetInfo.Name, LoadPlayer);
+ AddStep(CreatePlayerRuleset().Description, LoadPlayer);
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
}
@@ -63,11 +57,10 @@ namespace osu.Game.Tests.Visual
protected void LoadPlayer()
{
+ var ruleset = Ruleset.Value.CreateInstance();
var beatmap = CreateBeatmap(ruleset.RulesetInfo);
Beatmap.Value = CreateWorkingBeatmap(beatmap);
- Ruleset.Value = ruleset.RulesetInfo;
-
SelectedMods.Value = Array.Empty();
if (!AllowFail)
@@ -88,6 +81,14 @@ namespace osu.Game.Tests.Visual
LoadScreen(Player);
}
+ ///
+ /// Creates the ruleset for setting up the component.
+ ///
+ [NotNull]
+ protected abstract Ruleset CreatePlayerRuleset();
+
+ protected sealed override Ruleset CreateRuleset() => CreatePlayerRuleset();
+
protected virtual TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false);
}
}
diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs
index ace24c0d7e..ea7cdaaac6 100644
--- a/osu.Game/Tests/Visual/SkinnableTestScene.cs
+++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics;
@@ -32,9 +33,6 @@ namespace osu.Game.Tests.Visual
{
}
- // Required to be part of the per-ruleset implementation to construct the newer version of the Ruleset.
- protected abstract Ruleset CreateRulesetForSkinProvider();
-
[BackgroundDependencyLoader]
private void load(AudioManager audio, SkinManager skinManager)
{
@@ -107,7 +105,7 @@ namespace osu.Game.Tests.Visual
{
new OutlineBox { Alpha = autoSize ? 1 : 0 },
mainProvider.WithChild(
- new SkinProvidingContainer(CreateRulesetForSkinProvider().CreateLegacySkinProvider(mainProvider, beatmap))
+ new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider, beatmap))
{
Child = created,
RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None,
@@ -120,6 +118,14 @@ namespace osu.Game.Tests.Visual
};
}
+ ///
+ /// Creates the ruleset for adding the corresponding skin transforming component.
+ ///
+ [NotNull]
+ protected abstract Ruleset CreateRulesetForSkinProvider();
+
+ protected sealed override Ruleset CreateRuleset() => CreateRulesetForSkinProvider();
+
protected virtual IBeatmap CreateBeatmapForSkinProvider() => CreateWorkingBeatmap(Ruleset.Value).GetPlayableBeatmap(Ruleset.Value);
private class OutlineBox : CompositeDrawable
diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs
index 1e8a96444f..ebb9995c66 100644
--- a/osu.Game/Updater/SimpleUpdateManager.cs
+++ b/osu.Game/Updater/SimpleUpdateManager.cs
@@ -28,12 +28,9 @@ namespace osu.Game.Updater
private void load(OsuGameBase game)
{
version = game.Version;
-
- if (game.IsDeployedBuild)
- Schedule(() => Task.Run(checkForUpdateAsync));
}
- private async void checkForUpdateAsync()
+ protected override async Task PerformUpdateCheck()
{
try
{
diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs
index 28a295215f..61775a26b7 100644
--- a/osu.Game/Updater/UpdateManager.cs
+++ b/osu.Game/Updater/UpdateManager.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.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
@@ -16,6 +17,13 @@ namespace osu.Game.Updater
///
public class UpdateManager : CompositeDrawable
{
+ ///
+ /// Whether this UpdateManager should be or is capable of checking for updates.
+ ///
+ public bool CanCheckForUpdate => game.IsDeployedBuild &&
+ // only implementations will actually check for updates.
+ GetType() != typeof(UpdateManager);
+
[Resolved]
private OsuConfigManager config { get; set; }
@@ -29,7 +37,10 @@ namespace osu.Game.Updater
{
base.LoadComplete();
+ Schedule(() => Task.Run(CheckForUpdateAsync));
+
var version = game.Version;
+
var lastVersion = config.Get(OsuSetting.Version);
if (game.IsDeployedBuild && version != lastVersion)
@@ -44,6 +55,28 @@ namespace osu.Game.Updater
config.Set(OsuSetting.Version, version);
}
+ private readonly object updateTaskLock = new object();
+
+ private Task updateCheckTask;
+
+ public async Task CheckForUpdateAsync()
+ {
+ if (!CanCheckForUpdate)
+ return;
+
+ Task waitTask;
+
+ lock (updateTaskLock)
+ waitTask = (updateCheckTask ??= PerformUpdateCheck());
+
+ await waitTask;
+
+ lock (updateTaskLock)
+ updateCheckTask = null;
+ }
+
+ protected virtual Task PerformUpdateCheck() => Task.CompletedTask;
+
private class UpdateCompleteNotification : SimpleNotification
{
private readonly string version;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 1d3bafbfd6..bec3bc9d39 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -24,7 +24,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index ad7850599b..de5130b66a 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -80,7 +80,7 @@
-
+