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/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/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 44e8f343d5..f8fa5d4c40 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -45,6 +45,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 74a983fac8..84e88a10be 100644
--- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
@@ -3,11 +3,8 @@
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;
@@ -15,9 +12,8 @@ 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;
///
@@ -59,24 +55,23 @@ 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)
{
@@ -128,23 +123,18 @@ namespace osu.Game.Rulesets.Mania.Skinning
private Drawable getResult(HitResult result)
{
- string filename = GetConfig(
- new ManiaSkinConfigurationLookup(hitresult_mapping[result])
- )?.Value ?? default_hitresult_skin_filenames[result];
+ string filename = this.GetManiaSkinConfig(hitresult_mapping[result])?.Value
+ ?? default_hitresult_skin_filenames[result];
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/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/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
index a97566ba7b..cd3669f160 100644
--- a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs
@@ -3,6 +3,7 @@
using System;
using NUnit.Framework;
+using osu.Framework.Testing;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
@@ -10,6 +11,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Gameplay
{
+ [HeadlessTest]
public class TestSceneGameplayClockContainer : OsuTestScene
{
[Test]
diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs
index acefaa006a..ef6efb7fec 100644
--- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs
@@ -1,52 +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.Skinning;
-using osu.Game.Storyboards;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources;
-using osu.Game.Tests.Visual.Gameplay;
-using osu.Game.Users;
namespace osu.Game.Tests.Gameplay
{
[HeadlessTest]
- public class TestSceneHitObjectSamples : OsuPlayerTestScene
+ 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;
-
- 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.
@@ -56,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);
}
///
@@ -71,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);
}
///
@@ -86,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);
}
///
@@ -102,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);
}
///
@@ -118,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);
}
///
@@ -133,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);
}
///
@@ -148,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);
}
///
@@ -163,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);
}
///
@@ -177,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);
}
///
@@ -192,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 2c85c4809b..552d163b2f 100644
--- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
@@ -87,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/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/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/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/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/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/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index cfcef5155d..c2bb75b8f3 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/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/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/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs
index 6d0fc199c4..cb9ed40b00 100644
--- a/osu.Game/Tests/Visual/OsuTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuTestScene.cs
@@ -22,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;
@@ -38,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;
@@ -66,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();
@@ -153,6 +162,8 @@ namespace osu.Game.Tests.Visual
{
base.Dispose(isDisposing);
+ rulesetDependencies?.Dispose();
+
if (Beatmap?.Value.TrackLoaded == true)
Beatmap.Value.Track.Stop();
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 @@
-
+