Fallback to current skin combo colours if none provided on beat… (#6449)

Fallback to current skin combo colours if none provided on beatmap skin

Co-authored-by: Dean Herbert <pe@ppy.sh>
This commit is contained in:
Dean Herbert 2019-12-25 15:19:24 +09:00 committed by GitHub
commit 1455b9e796
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 275 additions and 54 deletions

View File

@ -0,0 +1,155 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<ISkinSource>(new TestSkin(userHasCustomColours));
return dependencies;
}
public IReadOnlyList<Color4> UsableComboColours =>
GameplayClockContainer.ChildrenOfType<BeatmapSkinProvidingContainer>()
.First()
.GetConfig<GlobalSkinConfiguration, IReadOnlyList<Color4>>(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<byte[]>(), 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 { }
}
}
}
}

View File

@ -130,7 +130,7 @@ namespace osu.Game.Tests.Gameplay
switch (global) switch (global)
{ {
case GlobalSkinConfiguration.ComboColours: case GlobalSkinConfiguration.ComboColours:
return SkinUtils.As<TValue>(new Bindable<List<Color4>>(ComboColours)); return SkinUtils.As<TValue>(new Bindable<IReadOnlyList<Color4>>(ComboColours));
} }
break; break;

View File

@ -13,31 +13,22 @@ namespace osu.Game.Tests.Skins
[TestFixture] [TestFixture]
public class LegacySkinDecoderTest public class LegacySkinDecoderTest
{ {
[TestCase(true)] [Test]
[TestCase(false)] public void TestDecodeSkinColours()
public void TestDecodeSkinColours(bool hasColours)
{ {
var decoder = new LegacySkinDecoder(); 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)) using (var stream = new LineBufferedReader(resStream))
{ {
var comboColors = decoder.Decode(stream).ComboColours; var comboColors = decoder.Decode(stream).ComboColours;
var expectedColors = new List<Color4>
List<Color4> expectedColors;
if (hasColours)
{ {
expectedColors = new List<Color4> new Color4(142, 199, 255, 255),
{ new Color4(255, 128, 128, 255),
new Color4(142, 199, 255, 255), new Color4(128, 255, 255, 255),
new Color4(255, 128, 128, 255), new Color4(100, 100, 100, 100),
new Color4(128, 255, 255, 255), };
new Color4(100, 100, 100, 100),
};
}
else
expectedColors = new DefaultSkin().Configuration.ComboColours;
Assert.AreEqual(expectedColors.Count, comboColors.Count); Assert.AreEqual(expectedColors.Count, comboColors.Count);
for (int i = 0; i < expectedColors.Count; i++) 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] [Test]
public void TestDecodeGeneral() public void TestDecodeGeneral()
{ {

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
@ -21,8 +22,8 @@ namespace osu.Game.Tests.Skins
[HeadlessTest] [HeadlessTest]
public class TestSceneSkinConfigurationLookup : OsuTestScene public class TestSceneSkinConfigurationLookup : OsuTestScene
{ {
private LegacySkin source1; private SkinSource source1;
private LegacySkin source2; private SkinSource source2;
private SkinRequester requester; private SkinRequester requester;
[SetUp] [SetUp]
@ -94,7 +95,7 @@ namespace osu.Game.Tests.Skins
[Test] [Test]
public void TestGlobalLookup() public void TestGlobalLookup()
{ {
AddAssert("Check combo colours", () => requester.GetConfig<GlobalSkinConfiguration, List<Color4>>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0); AddAssert("Check combo colours", () => requester.GetConfig<GlobalSkinConfiguration, IReadOnlyList<Color4>>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0);
} }
[Test] [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, IReadOnlyList<Color4>>(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, IReadOnlyList<Color4>>(GlobalSkinConfiguration.ComboColours)?.Value?.SequenceEqual(source1.Configuration.ComboColours) ?? false);
}
[Test] [Test]
public void TestLegacyVersionLookup() public void TestLegacyVersionLookup()
{ {

View File

@ -8,6 +8,14 @@ namespace osu.Game.Beatmaps.Formats
{ {
public interface IHasComboColours public interface IHasComboColours
{ {
List<Color4> ComboColours { get; set; } /// <summary>
/// Retrieves the list of combo colours for presentation only.
/// </summary>
IReadOnlyList<Color4> ComboColours { get; }
/// <summary>
/// Adds combo colours to the list.
/// </summary>
void AddComboColours(params Color4[] colours);
} }
} }

View File

@ -77,8 +77,6 @@ namespace osu.Game.Beatmaps.Formats
return line; return line;
} }
private bool hasComboColours;
private void handleColours(T output, string line) private void handleColours(T output, string line)
{ {
var pair = SplitKeyVal(line); var pair = SplitKeyVal(line);
@ -105,14 +103,7 @@ namespace osu.Game.Beatmaps.Formats
{ {
if (!(output is IHasComboColours tHasComboColours)) return; if (!(output is IHasComboColours tHasComboColours)) return;
if (!hasComboColours) tHasComboColours.AddComboColours(colour);
{
// remove default colours.
tHasComboColours.ComboColours.Clear();
hasComboColours = true;
}
tHasComboColours.ComboColours.Add(colour);
} }
else else
{ {

View File

@ -356,7 +356,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
{ {
if (HitObject is IHasComboInformation combo) if (HitObject is IHasComboInformation combo)
{ {
var comboColours = CurrentSkin.GetConfig<GlobalSkinConfiguration, List<Color4>>(GlobalSkinConfiguration.ComboColours)?.Value; var comboColours = CurrentSkin.GetConfig<GlobalSkinConfiguration, IReadOnlyList<Color4>>(GlobalSkinConfiguration.ComboColours)?.Value;
AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White; AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White;
} }
} }

View File

@ -13,13 +13,12 @@ namespace osu.Game.Skinning
: base(Info, storage, audioManager, string.Empty) : base(Info, storage, audioManager, string.Empty)
{ {
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255); Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
Configuration.ComboColours.AddRange(new[] Configuration.AddComboColours(
{
new Color4(255, 192, 0, 255), new Color4(255, 192, 0, 255),
new Color4(0, 202, 0, 255), new Color4(0, 202, 0, 255),
new Color4(18, 124, 255, 255), new Color4(18, 124, 255, 255),
new Color4(242, 24, 57, 255), new Color4(242, 24, 57, 255)
}); );
Configuration.LegacyVersion = 2.0m; Configuration.LegacyVersion = 2.0m;
} }

View File

@ -35,7 +35,7 @@ namespace osu.Game.Skinning
switch (global) switch (global)
{ {
case GlobalSkinConfiguration.ComboColours: case GlobalSkinConfiguration.ComboColours:
return SkinUtils.As<TValue>(new Bindable<List<Color4>>(Configuration.ComboColours)); return SkinUtils.As<TValue>(new Bindable<IReadOnlyList<Color4>>(Configuration.ComboColours));
} }
break; break;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osuTK.Graphics;
namespace osu.Game.Skinning namespace osu.Game.Skinning
{ {
/// <summary> /// <summary>
@ -10,15 +8,5 @@ namespace osu.Game.Skinning
/// </summary> /// </summary>
public class DefaultSkinConfiguration : SkinConfiguration 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),
});
}
} }
} }

View File

@ -12,6 +12,8 @@ namespace osu.Game.Skinning
public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore<byte[]> storage, AudioManager audioManager) public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore<byte[]> storage, AudioManager audioManager)
: base(createSkinInfo(beatmap), new LegacySkinResourceStore<BeatmapSetFileInfo>(beatmap.BeatmapSet, storage), audioManager, beatmap.Path) : base(createSkinInfo(beatmap), new LegacySkinResourceStore<BeatmapSetFileInfo>(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) => private static SkinInfo createSkinInfo(BeatmapInfo beatmap) =>

View File

@ -72,7 +72,11 @@ namespace osu.Game.Skinning
switch (global) switch (global)
{ {
case GlobalSkinConfiguration.ComboColours: case GlobalSkinConfiguration.ComboColours:
return SkinUtils.As<TValue>(new Bindable<List<Color4>>(Configuration.ComboColours)); var comboColours = Configuration.ComboColours;
if (comboColours != null)
return SkinUtils.As<TValue>(new Bindable<IReadOnlyList<Color4>>(comboColours));
break;
} }
break; break;

View File

@ -3,7 +3,7 @@
namespace osu.Game.Skinning namespace osu.Game.Skinning
{ {
public class LegacySkinConfiguration : DefaultSkinConfiguration public class LegacySkinConfiguration : SkinConfiguration
{ {
public const decimal LATEST_VERSION = 2.7m; public const decimal LATEST_VERSION = 2.7m;

View File

@ -14,7 +14,36 @@ namespace osu.Game.Skinning
{ {
public readonly SkinInfo SkinInfo = new SkinInfo(); public readonly SkinInfo SkinInfo = new SkinInfo();
public List<Color4> ComboColours { get; set; } = new List<Color4>(); /// <summary>
/// Whether to allow <see cref="DefaultComboColours"/> as a fallback list for when no combo colours are provided.
/// </summary>
internal bool AllowDefaultComboColoursFallback = true;
public static List<Color4> DefaultComboColours { get; } = new List<Color4>
{
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<Color4> comboColours = new List<Color4>();
public IReadOnlyList<Color4> 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<string, Color4> CustomColours { get; set; } = new Dictionary<string, Color4>(); public Dictionary<string, Color4> CustomColours { get; set; } = new Dictionary<string, Color4>();