diff --git a/osu.Android.props b/osu.Android.props
index dd11804b90..e7f90af5fd 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -54,6 +54,6 @@
-
+
diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs
index 71b73ec78e..80bb82c769 100644
--- a/osu.Desktop/DiscordRichPresence.cs
+++ b/osu.Desktop/DiscordRichPresence.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Text;
using DiscordRPC;
using DiscordRPC.Message;
using osu.Framework.Allocation;
@@ -81,8 +83,8 @@ namespace osu.Desktop
if (status.Value is UserStatusOnline && activity.Value != null)
{
- presence.State = activity.Value.Status;
- presence.Details = getDetails(activity.Value);
+ presence.State = truncate(activity.Value.Status);
+ presence.Details = truncate(getDetails(activity.Value));
}
else
{
@@ -100,6 +102,27 @@ namespace osu.Desktop
client.SetPresence(presence);
}
+ private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' });
+
+ private string truncate(string str)
+ {
+ if (Encoding.UTF8.GetByteCount(str) <= 128)
+ return str;
+
+ ReadOnlyMemory strMem = str.AsMemory();
+
+ do
+ {
+ strMem = strMem[..^1];
+ } while (Encoding.UTF8.GetByteCount(strMem.Span) + ellipsis_length > 128);
+
+ return string.Create(strMem.Length + 1, strMem, (span, mem) =>
+ {
+ mem.Span.CopyTo(span);
+ span[^1] = '…';
+ });
+ }
+
private string getDetails(UserActivity activity)
{
switch (activity)
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index 036c86ffa3..90a6e609f0 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -4,7 +4,7 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using System.Collections.Generic;
-using System;
+using System.Linq;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Objects;
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
}
- protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
+ public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition);
protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap)
{
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index b329f6cd98..58212e29ef 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -106,6 +106,12 @@ namespace osu.Game.Rulesets.Catch
new CatchModFlashlight(),
};
+ case ModType.Conversion:
+ return new Mod[]
+ {
+ new CatchModDifficultyAdjust(),
+ };
+
case ModType.Automation:
return new Mod[]
{
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
new file mode 100644
index 0000000000..4c0f5d510e
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
@@ -0,0 +1,49 @@
+// 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.Bindables;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Catch.Mods
+{
+ public class CatchModDifficultyAdjust : ModDifficultyAdjust
+ {
+ [SettingSource("Fruit Size", "Override a beatmap's set CS.")]
+ public BindableNumber CircleSize { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ [SettingSource("Approach Rate", "Override a beatmap's set AR.")]
+ public BindableNumber ApproachRate { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ protected override void TransferSettings(BeatmapDifficulty difficulty)
+ {
+ base.TransferSettings(difficulty);
+
+ CircleSize.Value = CircleSize.Default = difficulty.CircleSize;
+ ApproachRate.Value = ApproachRate.Default = difficulty.ApproachRate;
+ }
+
+ protected override void ApplySettings(BeatmapDifficulty difficulty)
+ {
+ base.ApplySettings(difficulty);
+
+ difficulty.CircleSize = CircleSize.Value;
+ difficulty.ApproachRate = ApproachRate.Value;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index aac3f914a4..1a77a4944b 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -24,8 +24,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
///
private const int max_notes_for_density = 7;
- protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
-
public int TargetColumns;
public bool Dual;
public readonly bool IsForCurrentRuleset;
@@ -69,6 +67,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
}
}
+ public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition || h is ManiaHitObject);
+
protected override Beatmap ConvertBeatmap(IBeatmap original)
{
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 28971b11c5..b07e1d8f54 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -151,6 +151,7 @@ namespace osu.Game.Rulesets.Mania
new ManiaModRandom(),
new ManiaModDualStages(),
new ManiaModMirror(),
+ new ManiaModDifficultyAdjust(),
};
case ModType.Automation:
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs
new file mode 100644
index 0000000000..0817f8f9fc
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs
@@ -0,0 +1,11 @@
+// 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.Mods;
+
+namespace osu.Game.Rulesets.Mania.Mods
+{
+ public class ManiaModDifficultyAdjust : ModDifficultyAdjust
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs
new file mode 100644
index 0000000000..4676f14655
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.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.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Graphics;
+using osu.Framework.IO.Stores;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Screens;
+using osu.Game.Screens.Play;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneLegacyBeatmapSkin : OsuTestScene
+ {
+ [Resolved]
+ private AudioManager audio { get; set; }
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestBeatmapComboColours(bool customSkinColoursPresent)
+ {
+ ExposedPlayer player = null;
+
+ AddStep("load coloured beatmap", () => player = loadBeatmap(customSkinColoursPresent, true));
+ AddUntilStep("wait for player", () => player.IsLoaded);
+
+ AddAssert("is beatmap skin colours", () => player.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
+ }
+
+ [Test]
+ public void TestBeatmapNoComboColours()
+ {
+ ExposedPlayer player = null;
+
+ AddStep("load no-colour beatmap", () => player = loadBeatmap(false, false));
+ AddUntilStep("wait for player", () => player.IsLoaded);
+
+ AddAssert("is default user skin colours", () => player.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
+ }
+
+ [Test]
+ public void TestBeatmapNoComboColoursSkinOverride()
+ {
+ ExposedPlayer player = null;
+
+ AddStep("load custom-skin colour", () => player = loadBeatmap(true, false));
+ AddUntilStep("wait for player", () => player.IsLoaded);
+
+ AddAssert("is custom user skin colours", () => player.UsableComboColours.SequenceEqual(TestSkin.Colours));
+ }
+
+ private ExposedPlayer loadBeatmap(bool userHasCustomColours, bool beatmapHasColours)
+ {
+ ExposedPlayer player;
+
+ Beatmap.Value = new CustomSkinWorkingBeatmap(audio, beatmapHasColours);
+ Child = new OsuScreenStack(player = new ExposedPlayer(userHasCustomColours)) { RelativeSizeAxes = Axes.Both };
+
+ return player;
+ }
+
+ private class ExposedPlayer : Player
+ {
+ private readonly bool userHasCustomColours;
+
+ public ExposedPlayer(bool userHasCustomColours)
+ : base(false, false)
+ {
+ this.userHasCustomColours = userHasCustomColours;
+ }
+
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ {
+ var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+ dependencies.CacheAs(new TestSkin(userHasCustomColours));
+ return dependencies;
+ }
+
+ public IReadOnlyList UsableComboColours =>
+ GameplayClockContainer.ChildrenOfType()
+ .First()
+ .GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value;
+ }
+
+ private class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
+ {
+ private readonly bool hasColours;
+
+ public CustomSkinWorkingBeatmap(AudioManager audio, bool hasColours)
+ : base(new Beatmap
+ {
+ BeatmapInfo =
+ {
+ BeatmapSet = new BeatmapSetInfo(),
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ HitObjects = { new HitCircle { Position = new Vector2(256, 192) } }
+ }, null, null, audio)
+ {
+ this.hasColours = hasColours;
+ }
+
+ protected override ISkin GetSkin() => new TestBeatmapSkin(BeatmapInfo, hasColours);
+ }
+
+ private class TestBeatmapSkin : LegacyBeatmapSkin
+ {
+ public static Color4[] Colours { get; } =
+ {
+ new Color4(50, 100, 150, 255),
+ new Color4(40, 80, 120, 255),
+ };
+
+ public TestBeatmapSkin(BeatmapInfo beatmap, bool hasColours)
+ : base(beatmap, new ResourceStore(), null)
+ {
+ if (hasColours)
+ Configuration.AddComboColours(Colours);
+ }
+ }
+
+ private class TestSkin : LegacySkin, ISkinSource
+ {
+ public static Color4[] Colours { get; } =
+ {
+ new Color4(150, 100, 50, 255),
+ new Color4(20, 20, 20, 255),
+ };
+
+ public TestSkin(bool hasCustomColours)
+ : base(new SkinInfo(), null, null, string.Empty)
+ {
+ if (hasCustomColours)
+ Configuration.AddComboColours(Colours);
+ }
+
+ public event Action SourceChanged
+ {
+ add { }
+ remove { }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
index 02ce77e707..bd9d948782 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
@@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Tests
EndTime = 6000,
},
// placeholder object to avoid hitting the results screen
- new HitObject
+ new HitCircle
{
StartTime = 99999,
}
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
index 7bc14c3e41..147d74c929 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
@@ -7,7 +7,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using System.Collections.Generic;
using osu.Game.Rulesets.Objects.Types;
-using System;
+using System.Linq;
using osu.Game.Rulesets.Osu.UI;
using osu.Framework.Extensions.IEnumerableExtensions;
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
{
}
- protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasPosition) };
+ public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasPosition);
protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap)
{
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
new file mode 100644
index 0000000000..0514e2ab34
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
@@ -0,0 +1,49 @@
+// 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.Bindables;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ public class OsuModDifficultyAdjust : ModDifficultyAdjust
+ {
+ [SettingSource("Circle Size", "Override a beatmap's set CS.")]
+ public BindableNumber CircleSize { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ [SettingSource("Approach Rate", "Override a beatmap's set AR.")]
+ public BindableNumber ApproachRate { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ protected override void TransferSettings(BeatmapDifficulty difficulty)
+ {
+ base.TransferSettings(difficulty);
+
+ CircleSize.Value = CircleSize.Default = difficulty.CircleSize;
+ ApproachRate.Value = ApproachRate.Default = difficulty.ApproachRate;
+ }
+
+ protected override void ApplySettings(BeatmapDifficulty difficulty)
+ {
+ base.ApplySettings(difficulty);
+
+ difficulty.CircleSize = CircleSize.Value;
+ difficulty.ApproachRate = ApproachRate.Value;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 57d99df5fb..c8a156dc57 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -130,6 +130,7 @@ namespace osu.Game.Rulesets.Osu
return new Mod[]
{
new OsuModTarget(),
+ new OsuModDifficultyAdjust(),
};
case ModType.Automation:
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index 96e7722e73..cc9d6e4470 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -39,14 +39,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
private readonly bool isForCurrentRuleset;
- protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(HitObject) };
-
public TaikoBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
: base(beatmap, ruleset)
{
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
}
+ public override bool CanConvert() => true;
+
protected override Beatmap ConvertBeatmap(IBeatmap original)
{
// Rewrite the beatmap info to add the slider velocity multiplier
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs
new file mode 100644
index 0000000000..56a73ad7df
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs
@@ -0,0 +1,11 @@
+// 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.Mods;
+
+namespace osu.Game.Rulesets.Taiko.Mods
+{
+ public class TaikoModDifficultyAdjust : ModDifficultyAdjust
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 529dfe765c..5890ed2976 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -105,6 +105,12 @@ namespace osu.Game.Rulesets.Taiko
new TaikoModFlashlight(),
};
+ case ModType.Conversion:
+ return new Mod[]
+ {
+ new TaikoModDifficultyAdjust(),
+ };
+
case ModType.Automation:
return new Mod[]
{
diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
index 6d7159a825..c6d1f9da29 100644
--- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
@@ -130,7 +130,7 @@ namespace osu.Game.Tests.Gameplay
switch (global)
{
case GlobalSkinConfiguration.ComboColours:
- return SkinUtils.As(new Bindable>(ComboColours));
+ return SkinUtils.As(new Bindable>(ComboColours));
}
break;
diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
index f68d49dd3e..cef38bbbb8 100644
--- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
+++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
@@ -13,31 +13,22 @@ namespace osu.Game.Tests.Skins
[TestFixture]
public class LegacySkinDecoderTest
{
- [TestCase(true)]
- [TestCase(false)]
- public void TestDecodeSkinColours(bool hasColours)
+ [Test]
+ public void TestDecodeSkinColours()
{
var decoder = new LegacySkinDecoder();
- using (var resStream = TestResources.OpenResource(hasColours ? "skin.ini" : "skin-empty.ini"))
+ using (var resStream = TestResources.OpenResource("skin.ini"))
using (var stream = new LineBufferedReader(resStream))
{
var comboColors = decoder.Decode(stream).ComboColours;
-
- List expectedColors;
-
- if (hasColours)
+ var expectedColors = new List
{
- expectedColors = new List
- {
- new Color4(142, 199, 255, 255),
- new Color4(255, 128, 128, 255),
- new Color4(128, 255, 255, 255),
- new Color4(100, 100, 100, 100),
- };
- }
- else
- expectedColors = new DefaultSkin().Configuration.ComboColours;
+ new Color4(142, 199, 255, 255),
+ new Color4(255, 128, 128, 255),
+ new Color4(128, 255, 255, 255),
+ new Color4(100, 100, 100, 100),
+ };
Assert.AreEqual(expectedColors.Count, comboColors.Count);
for (int i = 0; i < expectedColors.Count; i++)
@@ -45,6 +36,37 @@ namespace osu.Game.Tests.Skins
}
}
+ [Test]
+ public void TestDecodeEmptySkinColours()
+ {
+ var decoder = new LegacySkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("skin-empty.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var comboColors = decoder.Decode(stream).ComboColours;
+ var expectedColors = SkinConfiguration.DefaultComboColours;
+
+ Assert.AreEqual(expectedColors.Count, comboColors.Count);
+ for (int i = 0; i < expectedColors.Count; i++)
+ Assert.AreEqual(expectedColors[i], comboColors[i]);
+ }
+ }
+
+ [Test]
+ public void TestDecodeEmptySkinColoursNoFallback()
+ {
+ var decoder = new LegacySkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("skin-empty.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var skinConfiguration = decoder.Decode(stream);
+ skinConfiguration.AllowDefaultComboColoursFallback = false;
+ Assert.IsNull(skinConfiguration.ComboColours);
+ }
+ }
+
[Test]
public void TestDecodeGeneral()
{
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
index 8b9c648442..ed54cc982d 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
@@ -21,8 +22,8 @@ namespace osu.Game.Tests.Skins
[HeadlessTest]
public class TestSceneSkinConfigurationLookup : OsuTestScene
{
- private LegacySkin source1;
- private LegacySkin source2;
+ private SkinSource source1;
+ private SkinSource source2;
private SkinRequester requester;
[SetUp]
@@ -94,7 +95,7 @@ namespace osu.Game.Tests.Skins
[Test]
public void TestGlobalLookup()
{
- AddAssert("Check combo colours", () => requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0);
+ AddAssert("Check combo colours", () => requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0);
}
[Test]
@@ -116,6 +117,28 @@ namespace osu.Game.Tests.Skins
});
}
+ [Test]
+ public void TestEmptyComboColours()
+ {
+ AddAssert("Check retrieved combo colours is skin default colours", () =>
+ requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.SequenceEqual(SkinConfiguration.DefaultComboColours) ?? false);
+ }
+
+ [Test]
+ public void TestEmptyComboColoursNoFallback()
+ {
+ AddStep("Add custom combo colours to source1", () => source1.Configuration.AddComboColours(
+ new Color4(100, 150, 200, 255),
+ new Color4(55, 110, 166, 255),
+ new Color4(75, 125, 175, 255)
+ ));
+
+ AddStep("Disallow default colours fallback in source2", () => source2.Configuration.AllowDefaultComboColoursFallback = false);
+
+ AddAssert("Check retrieved combo colours from source1", () =>
+ requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.SequenceEqual(source1.Configuration.ComboColours) ?? false);
+ }
+
[Test]
public void TestLegacyVersionLookup()
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
index dc950e43bd..36235a4418 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
@@ -273,7 +273,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
}
- protected override IEnumerable ValidConversionTypes => new[] { typeof(HitObject) };
+ public override bool CanConvert() => true;
protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap)
{
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs
deleted file mode 100644
index 66144cbfe4..0000000000
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs
+++ /dev/null
@@ -1,237 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using NUnit.Framework;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets;
-using osu.Game.Rulesets.Mods;
-using osu.Game.Screens.Play.HUD;
-using osu.Game.Screens.Select;
-using osu.Game.Tests.Beatmaps;
-using osuTK;
-
-namespace osu.Game.Tests.Visual.SongSelect
-{
- [TestFixture]
- [System.ComponentModel.Description("PlaySongSelect leaderboard/details area")]
- public class TestSceneBeatmapDetailArea : OsuTestScene
- {
- public override IReadOnlyList RequiredTypes => new[] { typeof(BeatmapDetails) };
-
- private ModDisplay modDisplay;
-
- [BackgroundDependencyLoader]
- private void load(OsuGameBase game, RulesetStore rulesets)
- {
- BeatmapDetailArea detailsArea;
- Add(detailsArea = new BeatmapDetailArea
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(550f, 450f),
- });
-
- Add(modDisplay = new ModDisplay
- {
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- AutoSizeAxes = Axes.Both,
- Position = new Vector2(0, 25),
- });
-
- modDisplay.Current.BindTo(SelectedMods);
-
- AddStep("all metrics", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- BeatmapSet = new BeatmapSetInfo
- {
- Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
- },
- Version = "All Metrics",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has all the metrics",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 7,
- DrainRate = 1,
- OverallDifficulty = 5.7f,
- ApproachRate = 3.5f,
- },
- StarDifficulty = 5.3f,
- Metrics = new BeatmapMetrics
- {
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
- },
- }
- }));
-
- AddStep("all except source", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- BeatmapSet = new BeatmapSetInfo
- {
- Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
- },
- Version = "All Metrics",
- Metadata = new BeatmapMetadata
- {
- Tags = "this beatmap has all the metrics",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 7,
- DrainRate = 1,
- OverallDifficulty = 5.7f,
- ApproachRate = 3.5f,
- },
- StarDifficulty = 5.3f,
- Metrics = new BeatmapMetrics
- {
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
- },
- }
- }));
-
- AddStep("ratings", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- BeatmapSet = new BeatmapSetInfo
- {
- Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
- },
- Version = "Only Ratings",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has ratings metrics but not retries or fails",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 6,
- DrainRate = 9,
- OverallDifficulty = 6,
- ApproachRate = 6,
- },
- StarDifficulty = 4.8f
- }
- }));
-
- AddStep("fails+retries", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- Version = "Only Retries and Fails",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has retries and fails but no ratings",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 3.7f,
- DrainRate = 6,
- OverallDifficulty = 6,
- ApproachRate = 7,
- },
- StarDifficulty = 2.91f,
- Metrics = new BeatmapMetrics
- {
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
- },
- }
- }));
-
- AddStep("null metrics", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- Version = "No Metrics",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has no metrics",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 5,
- DrainRate = 5,
- OverallDifficulty = 5.5f,
- ApproachRate = 6.5f,
- },
- StarDifficulty = 1.97f,
- }
- }));
-
- AddStep("null beatmap", () => detailsArea.Beatmap = null);
-
- Ruleset ruleset = rulesets.AvailableRulesets.First().CreateInstance();
-
- AddStep("with EZ mod", () =>
- {
- detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- Version = "Has Easy Mod",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has the easy mod enabled",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 3,
- DrainRate = 3,
- OverallDifficulty = 3,
- ApproachRate = 3,
- },
- StarDifficulty = 1f,
- }
- });
-
- SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModEasy) };
- });
-
- AddStep("with HR mod", () =>
- {
- detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- Version = "Has Hard Rock Mod",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has the hard rock mod enabled",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 3,
- DrainRate = 3,
- OverallDifficulty = 3,
- ApproachRate = 3,
- },
- StarDifficulty = 1f,
- }
- });
-
- SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModHardRock) };
- });
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs
index acf037198f..6aa5a76490 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs
@@ -3,8 +3,14 @@
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Select;
namespace osu.Game.Tests.Visual.SongSelect
@@ -174,5 +180,27 @@ namespace osu.Game.Tests.Visual.SongSelect
OnlineBeatmapID = 162,
});
}
+
+ [Resolved]
+ private RulesetStore rulesets { get; set; }
+
+ [Resolved]
+ private OsuColour colours { get; set; }
+
+ [Test]
+ public void TestModAdjustments()
+ {
+ TestAllMetrics();
+
+ Ruleset ruleset = rulesets.AvailableRulesets.First().CreateInstance();
+
+ AddStep("with EZ mod", () => SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModEasy) });
+
+ AddAssert("first bar coloured blue", () => details.ChildrenOfType().Skip(1).First().AccentColour == colours.BlueDark);
+
+ AddStep("with HR mod", () => SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModHardRock) });
+
+ AddAssert("first bar coloured red", () => details.ChildrenOfType().Skip(1).First().AccentColour == colours.Red);
+ }
}
}
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index e6de1eebcd..99e0bf4e33 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps
///
/// Whether can be converted by this .
///
- public bool CanConvert => !Beatmap.HitObjects.Any() || ValidConversionTypes.All(t => Beatmap.HitObjects.Any(t.IsInstanceOfType));
+ public abstract bool CanConvert();
///
/// Converts .
@@ -93,11 +93,6 @@ namespace osu.Game.Beatmaps
return result;
}
- ///
- /// The types of HitObjects that can be converted to be used for this Beatmap.
- ///
- protected abstract IEnumerable ValidConversionTypes { get; }
-
///
/// Creates the that will be returned by this .
///
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index 46efe38d37..bfcc38e4a9 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -76,7 +76,7 @@ namespace osu.Game.Beatmaps
public IBeatmap Beatmap { get; set; }
- public bool CanConvert => true;
+ public bool CanConvert() => true;
public IBeatmap Convert()
{
diff --git a/osu.Game/Beatmaps/Formats/IHasComboColours.cs b/osu.Game/Beatmaps/Formats/IHasComboColours.cs
index 4c15cb96d1..41c85db063 100644
--- a/osu.Game/Beatmaps/Formats/IHasComboColours.cs
+++ b/osu.Game/Beatmaps/Formats/IHasComboColours.cs
@@ -8,6 +8,14 @@ namespace osu.Game.Beatmaps.Formats
{
public interface IHasComboColours
{
- List ComboColours { get; set; }
+ ///
+ /// Retrieves the list of combo colours for presentation only.
+ ///
+ IReadOnlyList ComboColours { get; }
+
+ ///
+ /// Adds combo colours to the list.
+ ///
+ void AddComboColours(params Color4[] colours);
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index b1585d04c5..f55e24245b 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -77,8 +77,6 @@ namespace osu.Game.Beatmaps.Formats
return line;
}
- private bool hasComboColours;
-
private void handleColours(T output, string line)
{
var pair = SplitKeyVal(line);
@@ -105,14 +103,7 @@ namespace osu.Game.Beatmaps.Formats
{
if (!(output is IHasComboColours tHasComboColours)) return;
- if (!hasComboColours)
- {
- // remove default colours.
- tHasComboColours.ComboColours.Clear();
- hasComboColours = true;
- }
-
- tHasComboColours.ComboColours.Add(colour);
+ tHasComboColours.AddComboColours(colour);
}
else
{
diff --git a/osu.Game/Beatmaps/IBeatmapConverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs
index f2213b85f1..173d5494ba 100644
--- a/osu.Game/Beatmaps/IBeatmapConverter.cs
+++ b/osu.Game/Beatmaps/IBeatmapConverter.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps
///
/// Whether can be converted by this .
///
- bool CanConvert { get; }
+ bool CanConvert();
///
/// Converts .
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 1255665cf0..6aba5257f5 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -108,7 +108,7 @@ namespace osu.Game.Beatmaps
IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance);
// Check if the beatmap can be converted
- if (!converter.CanConvert)
+ if (Beatmap.HitObjects.Count > 0 && !converter.CanConvert())
throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset.InstantiationInfo}, converter: {converter}).");
// Apply conversion mods
diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs
index 1cac4d76ab..f5b7bc3073 100644
--- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs
@@ -12,10 +12,12 @@ using osu.Framework.Input.Events;
namespace osu.Game.Graphics.UserInterface
{
- public class OsuTextBox : TextBox
+ public class OsuTextBox : BasicTextBox
{
protected override float LeftRightPadding => 10;
+ protected override float CaretWidth => 3;
+
protected override SpriteText CreatePlaceholder() => new OsuSpriteText
{
Font = OsuFont.GetFont(italics: true),
@@ -41,6 +43,8 @@ namespace osu.Game.Graphics.UserInterface
BackgroundCommit = BorderColour = colour.Yellow;
}
+ protected override Color4 SelectionColour => new Color4(249, 90, 255, 255);
+
protected override void OnFocus(FocusEvent e)
{
BorderThickness = 3;
diff --git a/osu.Game/Overlays/Settings/ISettingsItem.cs b/osu.Game/Overlays/Settings/ISettingsItem.cs
new file mode 100644
index 0000000000..e7afa48502
--- /dev/null
+++ b/osu.Game/Overlays/Settings/ISettingsItem.cs
@@ -0,0 +1,13 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Graphics;
+
+namespace osu.Game.Overlays.Settings
+{
+ public interface ISettingsItem : IDrawable, IDisposable
+ {
+ event Action SettingChanged;
+ }
+}
diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs
index 31fcb7abd8..35f28ab1b2 100644
--- a/osu.Game/Overlays/Settings/SettingsItem.cs
+++ b/osu.Game/Overlays/Settings/SettingsItem.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 osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -20,7 +21,7 @@ using osuTK;
namespace osu.Game.Overlays.Settings
{
- public abstract class SettingsItem : Container, IFilterable
+ public abstract class SettingsItem : Container, IFilterable, ISettingsItem
{
protected abstract Drawable CreateControl();
@@ -34,8 +35,6 @@ namespace osu.Game.Overlays.Settings
private SpriteText text;
- private readonly RestoreDefaultValueButton restoreDefaultButton;
-
public bool ShowsDefaultIndicator = true;
public virtual string LabelText
@@ -70,8 +69,12 @@ namespace osu.Game.Overlays.Settings
public bool FilteringActive { get; set; }
+ public event Action SettingChanged;
+
protected SettingsItem()
{
+ RestoreDefaultValueButton restoreDefaultButton;
+
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS };
@@ -87,13 +90,12 @@ namespace osu.Game.Overlays.Settings
Child = Control = CreateControl()
},
};
- }
- [BackgroundDependencyLoader]
- private void load()
- {
+ // all bindable logic is in constructor intentionally to support "CreateSettingsControls" being used in a context it is
+ // never loaded, but requires bindable storage.
if (controlWithCurrent != null)
{
+ controlWithCurrent.Current.ValueChanged += _ => SettingChanged?.Invoke();
controlWithCurrent.Current.DisabledChanged += disabled => { Colour = disabled ? Color4.Gray : Color4.White; };
if (ShowsDefaultIndicator)
diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
new file mode 100644
index 0000000000..224fc78508
--- /dev/null
+++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
@@ -0,0 +1,81 @@
+// 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.Beatmaps;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Sprites;
+using System;
+using osu.Game.Configuration;
+
+namespace osu.Game.Rulesets.Mods
+{
+ public abstract class ModDifficultyAdjust : Mod, IApplicableToDifficulty
+ {
+ public override string Name => @"Difficulty Adjust";
+
+ public override string Description => @"Override a beatmap's difficulty settings.";
+
+ public override string Acronym => "DA";
+
+ public override ModType Type => ModType.Conversion;
+
+ public override IconUsage Icon => FontAwesome.Solid.Hammer;
+
+ public override double ScoreMultiplier => 1.0;
+
+ public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModHardRock) };
+
+ [SettingSource("Drain Rate", "Override a beatmap's set HP.")]
+ public BindableNumber DrainRate { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ [SettingSource("Overall Difficulty", "Override a beatmap's set OD.")]
+ public BindableNumber OverallDifficulty { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ private BeatmapDifficulty difficulty;
+
+ public void ApplyToDifficulty(BeatmapDifficulty difficulty)
+ {
+ if (this.difficulty == null || this.difficulty.ID != difficulty.ID)
+ {
+ this.difficulty = difficulty;
+ TransferSettings(difficulty);
+ }
+ else
+ ApplySettings(difficulty);
+ }
+
+ ///
+ /// Transfer initial settings from the beatmap to settings.
+ ///
+ /// The beatmap's initial values.
+ protected virtual void TransferSettings(BeatmapDifficulty difficulty)
+ {
+ DrainRate.Value = DrainRate.Default = difficulty.DrainRate;
+ OverallDifficulty.Value = OverallDifficulty.Default = difficulty.OverallDifficulty;
+ }
+
+ ///
+ /// Apply all custom settings to the provided beatmap.
+ ///
+ /// The beatmap to have settings applied.
+ protected virtual void ApplySettings(BeatmapDifficulty difficulty)
+ {
+ difficulty.DrainRate = DrainRate.Value;
+ difficulty.OverallDifficulty = OverallDifficulty.Value;
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs
index a55ebc51d6..a91e4dfd5c 100644
--- a/osu.Game/Rulesets/Mods/ModEasy.cs
+++ b/osu.Game/Rulesets/Mods/ModEasy.cs
@@ -5,6 +5,7 @@ using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
+using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
@@ -19,9 +20,16 @@ namespace osu.Game.Rulesets.Mods
public override ModType Type => ModType.DifficultyReduction;
public override double ScoreMultiplier => 0.5;
public override bool Ranked => true;
- public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) };
+ public override Type[] IncompatibleMods => new[] { typeof(ModHardRock), typeof(ModDifficultyAdjust) };
- private int retries = 2;
+ [SettingSource("Extra Lives", "Number of extra lives")]
+ public Bindable Retries { get; } = new BindableInt(2)
+ {
+ MinValue = 0,
+ MaxValue = 10
+ };
+
+ private int retries;
private BindableNumber health;
@@ -32,6 +40,8 @@ namespace osu.Game.Rulesets.Mods
difficulty.ApproachRate *= ratio;
difficulty.DrainRate *= ratio;
difficulty.OverallDifficulty *= ratio;
+
+ retries = Retries.Value;
}
public bool AllowFail
diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs
index 2044cbeae2..2bcac3e4a9 100644
--- a/osu.Game/Rulesets/Mods/ModHardRock.cs
+++ b/osu.Game/Rulesets/Mods/ModHardRock.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage Icon => OsuIcon.ModHardrock;
public override ModType Type => ModType.DifficultyIncrease;
public override string Description => "Everything just got a bit harder...";
- public override Type[] IncompatibleMods => new[] { typeof(ModEasy) };
+ public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModDifficultyAdjust) };
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 386805d7e5..a959fee9be 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -356,7 +356,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
{
if (HitObject is IHasComboInformation combo)
{
- var comboColours = CurrentSkin.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value;
+ var comboColours = CurrentSkin.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value;
AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White;
}
}
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index 231115d1e1..b28d572b5c 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Allocation;
@@ -132,6 +133,8 @@ namespace osu.Game.Screens.Menu
private void confirmAndExit()
{
+ if (exitConfirmed) return;
+
exitConfirmed = true;
this.Exit();
}
@@ -244,10 +247,18 @@ namespace osu.Game.Screens.Menu
public override bool OnExiting(IScreen next)
{
- if (!exitConfirmed && dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog))
+ if (!exitConfirmed && dialogOverlay != null)
{
- dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort()));
- return true;
+ if (dialogOverlay.CurrentDialog is ConfirmExitDialog exitDialog)
+ {
+ exitConfirmed = true;
+ exitDialog.Buttons.First().Click();
+ }
+ else
+ {
+ dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort()));
+ return true;
+ }
}
buttons.State = ButtonSystemState.Exit;
diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs
index 9c9c33274f..a147527f6c 100644
--- a/osu.Game/Screens/Select/Details/AdvancedStats.cs
+++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs
@@ -16,6 +16,9 @@ using osu.Framework.Bindables;
using System.Collections.Generic;
using osu.Game.Rulesets.Mods;
using System.Linq;
+using osu.Framework.Threading;
+using osu.Game.Configuration;
+using osu.Game.Overlays.Settings;
namespace osu.Game.Screens.Select.Details
{
@@ -69,7 +72,37 @@ namespace osu.Game.Screens.Select.Details
{
base.LoadComplete();
- mods.BindValueChanged(_ => updateStatistics(), true);
+ mods.BindValueChanged(modsChanged, true);
+ }
+
+ private readonly List references = new List();
+
+ private void modsChanged(ValueChangedEvent> mods)
+ {
+ // TODO: find a more permanent solution for this if/when it is needed in other components.
+ // this is generating drawables for the only purpose of storing bindable references.
+ foreach (var r in references)
+ r.Dispose();
+
+ references.Clear();
+
+ ScheduledDelegate debounce = null;
+
+ foreach (var mod in mods.NewValue.OfType())
+ {
+ foreach (var setting in mod.CreateSettingsControls().OfType())
+ {
+ setting.SettingChanged += () =>
+ {
+ debounce?.Cancel();
+ debounce = Scheduler.AddDelayed(updateStatistics, 100);
+ };
+
+ references.Add(setting);
+ }
+ }
+
+ updateStatistics();
}
private void updateStatistics()
diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs
index 0caf2d19e9..1929a7e5d2 100644
--- a/osu.Game/Skinning/DefaultLegacySkin.cs
+++ b/osu.Game/Skinning/DefaultLegacySkin.cs
@@ -13,13 +13,12 @@ namespace osu.Game.Skinning
: base(Info, storage, audioManager, string.Empty)
{
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
- Configuration.ComboColours.AddRange(new[]
- {
+ Configuration.AddComboColours(
new Color4(255, 192, 0, 255),
new Color4(0, 202, 0, 255),
new Color4(18, 124, 255, 255),
- new Color4(242, 24, 57, 255),
- });
+ new Color4(242, 24, 57, 255)
+ );
Configuration.LegacyVersion = 2.0m;
}
diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs
index 529c1afca5..2a065ea3d7 100644
--- a/osu.Game/Skinning/DefaultSkin.cs
+++ b/osu.Game/Skinning/DefaultSkin.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Skinning
switch (global)
{
case GlobalSkinConfiguration.ComboColours:
- return SkinUtils.As(new Bindable>(Configuration.ComboColours));
+ return SkinUtils.As(new Bindable>(Configuration.ComboColours));
}
break;
diff --git a/osu.Game/Skinning/DefaultSkinConfiguration.cs b/osu.Game/Skinning/DefaultSkinConfiguration.cs
index cd5975edac..5842ee82ee 100644
--- a/osu.Game/Skinning/DefaultSkinConfiguration.cs
+++ b/osu.Game/Skinning/DefaultSkinConfiguration.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osuTK.Graphics;
-
namespace osu.Game.Skinning
{
///
@@ -10,15 +8,5 @@ namespace osu.Game.Skinning
///
public class DefaultSkinConfiguration : SkinConfiguration
{
- public DefaultSkinConfiguration()
- {
- ComboColours.AddRange(new[]
- {
- new Color4(255, 192, 0, 255),
- new Color4(0, 202, 0, 255),
- new Color4(18, 124, 255, 255),
- new Color4(242, 24, 57, 255),
- });
- }
}
}
diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs
index 6770da3c66..fa7e895a28 100644
--- a/osu.Game/Skinning/LegacyBeatmapSkin.cs
+++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs
@@ -12,6 +12,8 @@ namespace osu.Game.Skinning
public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore storage, AudioManager audioManager)
: base(createSkinInfo(beatmap), new LegacySkinResourceStore(beatmap.BeatmapSet, storage), audioManager, beatmap.Path)
{
+ // Disallow default colours fallback on beatmap skins to allow using parent skin combo colours. (via SkinProvidingContainer)
+ Configuration.AllowDefaultComboColoursFallback = false;
}
private static SkinInfo createSkinInfo(BeatmapInfo beatmap) =>
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 868e3921bb..48c520986a 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -72,7 +72,11 @@ namespace osu.Game.Skinning
switch (global)
{
case GlobalSkinConfiguration.ComboColours:
- return SkinUtils.As(new Bindable>(Configuration.ComboColours));
+ var comboColours = Configuration.ComboColours;
+ if (comboColours != null)
+ return SkinUtils.As(new Bindable>(comboColours));
+
+ break;
}
break;
diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs
index b1679bd464..027f5b8883 100644
--- a/osu.Game/Skinning/LegacySkinConfiguration.cs
+++ b/osu.Game/Skinning/LegacySkinConfiguration.cs
@@ -3,7 +3,7 @@
namespace osu.Game.Skinning
{
- public class LegacySkinConfiguration : DefaultSkinConfiguration
+ public class LegacySkinConfiguration : SkinConfiguration
{
public const decimal LATEST_VERSION = 2.7m;
diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs
index 54aac86e3c..a55870aa6d 100644
--- a/osu.Game/Skinning/SkinConfiguration.cs
+++ b/osu.Game/Skinning/SkinConfiguration.cs
@@ -14,7 +14,36 @@ namespace osu.Game.Skinning
{
public readonly SkinInfo SkinInfo = new SkinInfo();
- public List ComboColours { get; set; } = new List();
+ ///
+ /// Whether to allow as a fallback list for when no combo colours are provided.
+ ///
+ internal bool AllowDefaultComboColoursFallback = true;
+
+ public static List DefaultComboColours { get; } = new List
+ {
+ new Color4(255, 192, 0, 255),
+ new Color4(0, 202, 0, 255),
+ new Color4(18, 124, 255, 255),
+ new Color4(242, 24, 57, 255),
+ };
+
+ private readonly List comboColours = new List();
+
+ public IReadOnlyList ComboColours
+ {
+ get
+ {
+ if (comboColours.Count > 0)
+ return comboColours;
+
+ if (AllowDefaultComboColoursFallback)
+ return DefaultComboColours;
+
+ return null;
+ }
+ }
+
+ public void AddComboColours(params Color4[] colours) => comboColours.AddRange(colours);
public Dictionary CustomColours { get; set; } = new Dictionary();
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 757e0e11fa..0c0a58d533 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -23,7 +23,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 0dba92b975..edeeea239e 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -74,7 +74,7 @@
-
+
@@ -82,7 +82,7 @@
-
+