1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 21:02:54 +08:00

Merge branch 'master' into more-mania-colours

This commit is contained in:
Dean Herbert 2020-04-07 23:36:42 +09:00 committed by GitHub
commit 2062087155
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 290 additions and 89 deletions

View File

@ -52,6 +52,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.403.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.403.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.406.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2020.407.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,21 @@
// 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 osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
public abstract class CatchSkinnableTestScene : SkinnableTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(CatchRuleset),
typeof(CatchLegacySkinTransformer),
};
protected override Ruleset CreateRulesetForSkinProvider() => new CatchRuleset();
}
}

View File

@ -4,21 +4,21 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Tests.Visual;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
[TestFixture] [TestFixture]
public class TestSceneCatcher : SkinnableTestScene public class TestSceneCatcher : CatchSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{ {
typeof(CatcherArea), typeof(CatcherArea),
typeof(CatcherSprite) typeof(CatcherSprite)
}; }).ToList();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()

View File

@ -17,12 +17,11 @@ using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
[TestFixture] [TestFixture]
public class TestSceneCatcherArea : SkinnableTestScene public class TestSceneCatcherArea : CatchSkinnableTestScene
{ {
private RulesetInfo catchRuleset; private RulesetInfo catchRuleset;

View File

@ -3,20 +3,20 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
using osu.Game.Tests.Visual;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
[TestFixture] [TestFixture]
public class TestSceneFruitObjects : SkinnableTestScene public class TestSceneFruitObjects : CatchSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{ {
typeof(CatchHitObject), typeof(CatchHitObject),
typeof(Fruit), typeof(Fruit),
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Tests
typeof(DrawableBanana), typeof(DrawableBanana),
typeof(DrawableBananaShower), typeof(DrawableBananaShower),
typeof(Pulp), typeof(Pulp),
}; }).ToList();
protected override void LoadComplete() protected override void LoadComplete()
{ {

View File

@ -1,12 +1,15 @@
// 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 System;
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Rulesets.UI.Scrolling.Algorithms; using osu.Game.Rulesets.UI.Scrolling.Algorithms;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
@ -24,6 +27,14 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
[Cached(Type = typeof(IScrollingInfo))] [Cached(Type = typeof(IScrollingInfo))]
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ManiaRuleset),
typeof(ManiaLegacySkinTransformer),
};
protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset();
protected ManiaSkinnableTestScene() protected ManiaSkinnableTestScene()
{ {
scrollingInfo.Direction.Value = ScrollingDirection.Down; scrollingInfo.Direction.Value = ScrollingDirection.Down;

View File

@ -10,11 +10,10 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests.Skinning namespace osu.Game.Rulesets.Mania.Tests.Skinning
{ {
public class TestSceneDrawableJudgement : SkinnableTestScene public class TestSceneDrawableJudgement : ManiaSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {

View File

@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Mania.Skinning
private Container directionContainer; private Container directionContainer;
private Sprite noteSprite; private Sprite noteSprite;
private float? minimumColumnWidth;
public LegacyNotePiece() public LegacyNotePiece()
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
@ -29,6 +31,8 @@ namespace osu.Game.Rulesets.Mania.Skinning
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(ISkinSource skin, IScrollingInfo scrollingInfo) private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
{ {
minimumColumnWidth = skin.GetConfig<ManiaSkinConfigurationLookup, float>(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.MinimumColumnWidth))?.Value;
InternalChild = directionContainer = new Container InternalChild = directionContainer = new Container
{ {
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
@ -47,8 +51,10 @@ namespace osu.Game.Rulesets.Mania.Skinning
if (noteSprite.Texture != null) if (noteSprite.Texture != null)
{ {
var scale = DrawWidth / noteSprite.Texture.DisplayWidth; // The height is scaled to the minimum column width, if provided.
noteSprite.Scale = new Vector2(scale); float minimumWidth = minimumColumnWidth ?? DrawWidth;
noteSprite.Scale = Vector2.Divide(new Vector2(DrawWidth, minimumWidth), noteSprite.Texture.DisplayWidth);
} }
} }

View File

@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
{ {
isLegacySkin = new Lazy<bool>(() => source.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version) != null); isLegacySkin = new Lazy<bool>(() => source.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version) != null);
hasKeyTexture = new Lazy<bool>(() => source.GetAnimation( hasKeyTexture = new Lazy<bool>(() => source.GetAnimation(
source.GetConfig<ManiaSkinConfigurationLookup, string>( GetConfig<ManiaSkinConfigurationLookup, string>(
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value
?? "mania-key1", true, true) != null); ?? "mania-key1", true, true) != null);
} }

View File

@ -64,6 +64,7 @@ namespace osu.Game.Rulesets.Mania.UI
{ {
// Mania doesn't care about global velocity // Mania doesn't care about global velocity
p.Velocity = 1; p.Velocity = 1;
p.BaseBeatLength *= Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier;
// For non-mania beatmap, speed changes should only happen through timing points // For non-mania beatmap, speed changes should only happen through timing points
if (!isForCurrentRuleset) if (!isForCurrentRuleset)

View File

@ -0,0 +1,21 @@
// 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 osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
public abstract class OsuSkinnableTestScene : SkinnableTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(OsuRuleset),
typeof(OsuLegacySkinTransformer),
};
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
}
}

View File

@ -10,17 +10,16 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
{ {
public class TestSceneDrawableJudgement : SkinnableTestScene public class TestSceneDrawableJudgement : OsuSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{ {
typeof(DrawableJudgement), typeof(DrawableJudgement),
typeof(DrawableOsuJudgement) typeof(DrawableOsuJudgement)
}; }).ToList();
public TestSceneDrawableJudgement() public TestSceneDrawableJudgement()
{ {

View File

@ -3,26 +3,32 @@
using System; using System;
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.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing.Input; using osu.Framework.Testing.Input;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Tests.Visual;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
{ {
[TestFixture] [TestFixture]
public class TestSceneGameplayCursor : SkinnableTestScene public class TestSceneGameplayCursor : OsuSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{ {
typeof(GameplayCursorContainer),
typeof(OsuCursorContainer), typeof(OsuCursorContainer),
typeof(OsuCursor),
typeof(LegacyCursor),
typeof(LegacyCursorTrail),
typeof(CursorTrail) typeof(CursorTrail)
}; }).ToList();
[Cached] [Cached]
private GameplayBeatmap gameplayBeatmap; private GameplayBeatmap gameplayBeatmap;

View File

@ -14,12 +14,11 @@ using osu.Game.Rulesets.Mods;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
{ {
[TestFixture] [TestFixture]
public class TestSceneHitCircle : SkinnableTestScene public class TestSceneHitCircle : OsuSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {

View File

@ -22,12 +22,11 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
{ {
[TestFixture] [TestFixture]
public class TestSceneSlider : SkinnableTestScene public class TestSceneSlider : OsuSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {

View File

@ -0,0 +1,21 @@
// 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 osu.Game.Rulesets.Taiko.Skinning;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
public abstract class TaikoSkinnableTestScene : SkinnableTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(TaikoRuleset),
typeof(TaikoLegacySkinTransformer),
};
protected override Ruleset CreateRulesetForSkinProvider() => new TaikoRuleset();
}
}

View File

@ -3,24 +3,26 @@
using System; using System;
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 osuTK; using osuTK;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.Skinning;
using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests namespace osu.Game.Rulesets.Taiko.Tests
{ {
[TestFixture] [TestFixture]
public class TestSceneInputDrum : SkinnableTestScene public class TestSceneInputDrum : TaikoSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{ {
typeof(InputDrum), typeof(InputDrum),
}; typeof(LegacyInputDrum),
}).ToList();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()

View File

@ -106,7 +106,7 @@ namespace osu.Game.Tests.Skins
var decoder = new LegacySkinDecoder(); var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource("skin-empty.ini")) using (var resStream = TestResources.OpenResource("skin-empty.ini"))
using (var stream = new LineBufferedReader(resStream)) using (var stream = new LineBufferedReader(resStream))
Assert.IsNull(decoder.Decode(stream).LegacyVersion); Assert.That(decoder.Decode(stream).LegacyVersion, Is.EqualTo(1.0m));
} }
} }
} }

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.IO;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -12,7 +13,10 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.IO;
using osu.Game.Rulesets.Osu;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using osuTK.Graphics; using osuTK.Graphics;
@ -22,15 +26,15 @@ namespace osu.Game.Tests.Skins
[HeadlessTest] [HeadlessTest]
public class TestSceneSkinConfigurationLookup : OsuTestScene public class TestSceneSkinConfigurationLookup : OsuTestScene
{ {
private SkinSource source1; private UserSkinSource userSource;
private SkinSource source2; private BeatmapSkinSource beatmapSource;
private SkinRequester requester; private SkinRequester requester;
[SetUp] [SetUp]
public void SetUp() => Schedule(() => public void SetUp() => Schedule(() =>
{ {
Add(new SkinProvidingContainer(source1 = new SkinSource()) Add(new SkinProvidingContainer(userSource = new UserSkinSource())
.WithChild(new SkinProvidingContainer(source2 = new SkinSource()) .WithChild(new SkinProvidingContainer(beatmapSource = new BeatmapSkinSource())
.WithChild(requester = new SkinRequester()))); .WithChild(requester = new SkinRequester())));
}); });
@ -39,31 +43,31 @@ namespace osu.Game.Tests.Skins
{ {
AddStep("Add config values", () => AddStep("Add config values", () =>
{ {
source1.Configuration.ConfigDictionary["Lookup"] = "source1"; userSource.Configuration.ConfigDictionary["Lookup"] = "user skin";
source2.Configuration.ConfigDictionary["Lookup"] = "source2"; beatmapSource.Configuration.ConfigDictionary["Lookup"] = "beatmap skin";
}); });
AddAssert("Check lookup finds source2", () => requester.GetConfig<string, string>("Lookup")?.Value == "source2"); AddAssert("Check lookup finds beatmap skin", () => requester.GetConfig<string, string>("Lookup")?.Value == "beatmap skin");
} }
[Test] [Test]
public void TestFloatLookup() public void TestFloatLookup()
{ {
AddStep("Add config values", () => source1.Configuration.ConfigDictionary["FloatTest"] = "1.1"); AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["FloatTest"] = "1.1");
AddAssert("Check float parse lookup", () => requester.GetConfig<string, float>("FloatTest")?.Value == 1.1f); AddAssert("Check float parse lookup", () => requester.GetConfig<string, float>("FloatTest")?.Value == 1.1f);
} }
[Test] [Test]
public void TestBoolLookup() public void TestBoolLookup()
{ {
AddStep("Add config values", () => source1.Configuration.ConfigDictionary["BoolTest"] = "1"); AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["BoolTest"] = "1");
AddAssert("Check bool parse lookup", () => requester.GetConfig<string, bool>("BoolTest")?.Value == true); AddAssert("Check bool parse lookup", () => requester.GetConfig<string, bool>("BoolTest")?.Value == true);
} }
[Test] [Test]
public void TestEnumLookup() public void TestEnumLookup()
{ {
AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Test"] = "Test2"); AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["Test"] = "Test2");
AddAssert("Check enum parse lookup", () => requester.GetConfig<LookupType, ValueType>(LookupType.Test)?.Value == ValueType.Test2); AddAssert("Check enum parse lookup", () => requester.GetConfig<LookupType, ValueType>(LookupType.Test)?.Value == ValueType.Test2);
} }
@ -76,7 +80,7 @@ namespace osu.Game.Tests.Skins
[Test] [Test]
public void TestLookupNull() public void TestLookupNull()
{ {
AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Lookup"] = null); AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["Lookup"] = null);
AddAssert("Check lookup null", () => AddAssert("Check lookup null", () =>
{ {
@ -88,7 +92,7 @@ namespace osu.Game.Tests.Skins
[Test] [Test]
public void TestColourLookup() public void TestColourLookup()
{ {
AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red); AddStep("Add config colour", () => userSource.Configuration.CustomColours["Lookup"] = Color4.Red);
AddAssert("Check colour lookup", () => requester.GetConfig<SkinCustomColourLookup, Color4>(new SkinCustomColourLookup("Lookup"))?.Value == Color4.Red); AddAssert("Check colour lookup", () => requester.GetConfig<SkinCustomColourLookup, Color4>(new SkinCustomColourLookup("Lookup"))?.Value == Color4.Red);
} }
@ -101,7 +105,7 @@ namespace osu.Game.Tests.Skins
[Test] [Test]
public void TestWrongColourType() public void TestWrongColourType()
{ {
AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red); AddStep("Add config colour", () => userSource.Configuration.CustomColours["Lookup"] = Color4.Red);
AddAssert("perform incorrect lookup", () => AddAssert("perform incorrect lookup", () =>
{ {
@ -127,26 +131,51 @@ namespace osu.Game.Tests.Skins
[Test] [Test]
public void TestEmptyComboColoursNoFallback() public void TestEmptyComboColoursNoFallback()
{ {
AddStep("Add custom combo colours to source1", () => source1.Configuration.AddComboColours( AddStep("Add custom combo colours to user skin", () => userSource.Configuration.AddComboColours(
new Color4(100, 150, 200, 255), new Color4(100, 150, 200, 255),
new Color4(55, 110, 166, 255), new Color4(55, 110, 166, 255),
new Color4(75, 125, 175, 255) new Color4(75, 125, 175, 255)
)); ));
AddStep("Disallow default colours fallback in source2", () => source2.Configuration.AllowDefaultComboColoursFallback = false); AddStep("Disallow default colours fallback in beatmap skin", () => beatmapSource.Configuration.AllowDefaultComboColoursFallback = false);
AddAssert("Check retrieved combo colours from source1", () => AddAssert("Check retrieved combo colours from user skin", () =>
requester.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value?.SequenceEqual(source1.Configuration.ComboColours) ?? false); requester.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value?.SequenceEqual(userSource.Configuration.ComboColours) ?? false);
} }
[Test] [Test]
public void TestLegacyVersionLookup() public void TestNullBeatmapVersionFallsBackToUserSkin()
{ {
AddStep("Set source1 version 2.3", () => source1.Configuration.LegacyVersion = 2.3m); AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m);
AddStep("Set source2 version null", () => source2.Configuration.LegacyVersion = null); AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null);
AddAssert("Check legacy version lookup", () => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m); AddAssert("Check legacy version lookup", () => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m);
} }
[Test]
public void TestSetBeatmapVersionNoFallback()
{
AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m);
AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = 1.7m);
AddAssert("Check legacy version lookup", () => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.7m);
}
[Test]
public void TestNullBeatmapAndUserVersionFallsBackToLatest()
{
AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = null);
AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null);
AddAssert("Check legacy version lookup",
() => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == LegacySkinConfiguration.LATEST_VERSION);
}
[Test]
public void TestIniWithNoVersionFallsBackTo1()
{
AddStep("Parse skin with no version", () => userSource.Configuration = new LegacySkinDecoder().Decode(new LineBufferedReader(new MemoryStream())));
AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null);
AddAssert("Check legacy version lookup", () => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.0m);
}
public enum LookupType public enum LookupType
{ {
Test Test
@ -159,14 +188,22 @@ namespace osu.Game.Tests.Skins
Test3 Test3
} }
public class SkinSource : LegacySkin public class UserSkinSource : LegacySkin
{ {
public SkinSource() public UserSkinSource()
: base(new SkinInfo(), null, null, string.Empty) : base(new SkinInfo(), null, null, string.Empty)
{ {
} }
} }
public class BeatmapSkinSource : LegacyBeatmapSkin
{
public BeatmapSkinSource()
: base(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, null, null)
{
}
}
public class SkinRequester : Drawable, ISkin public class SkinRequester : Drawable, ISkin
{ {
private ISkinSource skin; private ISkinSource skin;

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 osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -18,6 +19,20 @@ namespace osu.Game.Skinning
Configuration.AllowDefaultComboColoursFallback = false; Configuration.AllowDefaultComboColoursFallback = false;
} }
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
{
switch (lookup)
{
case LegacySkinConfiguration.LegacySetting s when s == LegacySkinConfiguration.LegacySetting.Version:
if (Configuration.LegacyVersion is decimal version)
return SkinUtils.As<TValue>(new Bindable<decimal>(version));
return null;
}
return base.GetConfig<TLookup, TValue>(lookup);
}
private static SkinInfo createSkinInfo(BeatmapInfo beatmap) => private static SkinInfo createSkinInfo(BeatmapInfo beatmap) =>
new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata.Author.ToString() }; new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata.Author.ToString() };
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osuTK.Graphics; using osuTK.Graphics;
@ -24,6 +25,8 @@ namespace osu.Game.Skinning
public Dictionary<string, Color4> CustomColours { get; set; } = new Dictionary<string, Color4>(); public Dictionary<string, Color4> CustomColours { get; set; } = new Dictionary<string, Color4>();
public Dictionary<string, string> ImageLookups = new Dictionary<string, string>();
public readonly float[] ColumnLineWidth; public readonly float[] ColumnLineWidth;
public readonly float[] ColumnSpacing; public readonly float[] ColumnSpacing;
public readonly float[] ColumnWidth; public readonly float[] ColumnWidth;
@ -45,5 +48,13 @@ namespace osu.Game.Skinning
ColumnLineWidth.AsSpan().Fill(2); ColumnLineWidth.AsSpan().Fill(2);
ColumnWidth.AsSpan().Fill(DEFAULT_COLUMN_SIZE); ColumnWidth.AsSpan().Fill(DEFAULT_COLUMN_SIZE);
} }
private float? minimumColumnWidth;
public float MinimumColumnWidth
{
get => minimumColumnWidth ?? ColumnWidth.Min();
set => minimumColumnWidth = value;
}
} }
} }

View File

@ -40,5 +40,6 @@ namespace osu.Game.Skinning
JudgementLineColour, JudgementLineColour,
ColumnBackgroundColour, ColumnBackgroundColour,
ColumnLightColour ColumnLightColour
MinimumColumnWidth
} }
} }

View File

@ -71,12 +71,6 @@ namespace osu.Game.Skinning
{ {
var pair = SplitKeyVal(line); var pair = SplitKeyVal(line);
if (pair.Key.StartsWith("Colour"))
{
HandleColours(currentConfig, line);
continue;
}
switch (pair.Key) switch (pair.Key)
{ {
case "ColumnLineWidth": case "ColumnLineWidth":
@ -106,6 +100,22 @@ namespace osu.Game.Skinning
case "LightingNWidth": case "LightingNWidth":
parseArrayValue(pair.Value, currentConfig.ExplosionWidth); parseArrayValue(pair.Value, currentConfig.ExplosionWidth);
break; break;
case "WidthForNoteHeightScale":
currentConfig.MinimumColumnWidth = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
break;
case string _ when pair.Key.StartsWith("Colour"):
HandleColours(currentConfig, line);
break;
case string _ when pair.Key.StartsWith("NoteImage"):
currentConfig.ImageLookups[pair.Key] = pair.Value;
break;
case string _ when pair.Key.StartsWith("KeyImage"):
currentConfig.ImageLookups[pair.Key] = pair.Value;
break;
} }
} }

View File

@ -71,7 +71,7 @@ namespace osu.Game.Skinning
} }
} }
else else
Configuration = new LegacySkinConfiguration { LegacyVersion = LegacySkinConfiguration.LATEST_VERSION }; Configuration = new LegacySkinConfiguration();
} }
if (storage != null) if (storage != null)
@ -123,10 +123,7 @@ namespace osu.Game.Skinning
switch (legacy) switch (legacy)
{ {
case LegacySkinConfiguration.LegacySetting.Version: case LegacySkinConfiguration.LegacySetting.Version:
if (Configuration.LegacyVersion is decimal version) return SkinUtils.As<TValue>(new Bindable<decimal>(Configuration.LegacyVersion ?? LegacySkinConfiguration.LATEST_VERSION));
return SkinUtils.As<TValue>(new Bindable<decimal>(version));
break;
} }
break; break;
@ -219,6 +216,33 @@ namespace osu.Game.Skinning
case LegacyManiaSkinConfigurationLookups.ColumnLightColour: case LegacyManiaSkinConfigurationLookups.ColumnLightColour:
Debug.Assert(maniaLookup.TargetColumn != null); Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As<TValue>(getCustomColour(existing, $"ColourLight{maniaLookup.TargetColumn + 1}")); return SkinUtils.As<TValue>(getCustomColour(existing, $"ColourLight{maniaLookup.TargetColumn + 1}"));
case LegacyManiaSkinConfigurationLookups.MinimumColumnWidth:
return SkinUtils.As<TValue>(new Bindable<float>(existing.MinimumColumnWidth));
case LegacyManiaSkinConfigurationLookups.NoteImage:
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As<TValue>(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}"));
case LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage:
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As<TValue>(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}H"));
case LegacyManiaSkinConfigurationLookups.HoldNoteTailImage:
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As<TValue>(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}T"));
case LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage:
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As<TValue>(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}L"));
case LegacyManiaSkinConfigurationLookups.KeyImage:
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As<TValue>(getManiaImage(existing, $"KeyImage{maniaLookup.TargetColumn}"));
case LegacyManiaSkinConfigurationLookups.KeyImageDown:
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As<TValue>(getManiaImage(existing, $"KeyImage{maniaLookup.TargetColumn}D"));
} }
return null; return null;
@ -227,6 +251,9 @@ namespace osu.Game.Skinning
private IBindable<Color4> getCustomColour(IHasCustomColours source, string lookup) private IBindable<Color4> getCustomColour(IHasCustomColours source, string lookup)
=> source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable<Color4>(col) : null; => source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable<Color4>(col) : null;
private IBindable<string> getManiaImage(LegacyManiaSkinConfiguration source, string lookup)
=> source.ImageLookups.TryGetValue(lookup, out var image) ? new Bindable<string>(image) : null;
public override Drawable GetDrawableComponent(ISkinComponent component) public override Drawable GetDrawableComponent(ISkinComponent component)
{ {
switch (component) switch (component)
@ -255,15 +282,15 @@ namespace osu.Game.Skinning
public override Texture GetTexture(string componentName) public override Texture GetTexture(string componentName)
{ {
componentName = getFallbackName(componentName); foreach (var name in getFallbackNames(componentName))
{
float ratio = 2; float ratio = 2;
var texture = Textures?.Get($"{componentName}@2x"); var texture = Textures?.Get($"{name}@2x");
if (texture == null) if (texture == null)
{ {
ratio = 1; ratio = 1;
texture = Textures?.Get(componentName); texture = Textures?.Get(name);
} }
if (texture != null) if (texture != null)
@ -272,6 +299,9 @@ namespace osu.Game.Skinning
return texture; return texture;
} }
return null;
}
public override SampleChannel GetSample(ISampleInfo sampleInfo) public override SampleChannel GetSample(ISampleInfo sampleInfo)
{ {
foreach (var lookup in sampleInfo.LookupNames) foreach (var lookup in sampleInfo.LookupNames)
@ -289,10 +319,14 @@ namespace osu.Game.Skinning
return null; return null;
} }
private string getFallbackName(string componentName) private IEnumerable<string> getFallbackNames(string componentName)
{ {
// May be something like "Gameplay/osu/approachcircle" from lazer, or "Arrows/note1" from a user skin.
yield return componentName;
// Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle").
string lastPiece = componentName.Split('/').Last(); string lastPiece = componentName.Split('/').Last();
return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; yield return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece;
} }
} }
} }

View File

@ -52,5 +52,12 @@ namespace osu.Game.Skinning
base.ParseLine(skin, section, line); base.ParseLine(skin, section, line);
} }
protected override LegacySkinConfiguration CreateTemplateObject()
{
var config = base.CreateTemplateObject();
config.LegacyVersion = 1.0m;
return config;
}
} }
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Game.Database; using osu.Game.Database;
@ -27,7 +28,7 @@ namespace osu.Game.Skinning
foreach (var filename in base.GetFilenames(name)) foreach (var filename in base.GetFilenames(name))
{ {
var path = getPathForFile(filename); var path = getPathForFile(filename.ToStandardisedPath());
if (path != null) if (path != null)
yield return path; yield return path;
} }

View File

@ -13,6 +13,7 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -26,18 +27,18 @@ namespace osu.Game.Tests.Visual
private Skin specialSkin; private Skin specialSkin;
private Skin oldSkin; private Skin oldSkin;
// Keep a static reference to ensure we don't use a dynamically recompiled DLL as a source (resources will be missing).
private static DllResourceStore dllStore;
protected SkinnableTestScene() protected SkinnableTestScene()
: base(2, 3) : base(2, 3)
{ {
} }
// Required to be part of the per-ruleset implementation to construct the newer version of the Ruleset.
protected abstract Ruleset CreateRulesetForSkinProvider();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio, SkinManager skinManager) private void load(AudioManager audio, SkinManager skinManager)
{ {
dllStore ??= new DllResourceStore(GetType().Assembly); var dllStore = new DllResourceStore(DynamicCompilationOriginal.GetType().Assembly);
metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore<byte[]>(dllStore, "Resources/metrics_skin"), audio, true); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore<byte[]>(dllStore, "Resources/metrics_skin"), audio, true);
defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info); defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
@ -106,7 +107,7 @@ namespace osu.Game.Tests.Visual
{ {
new OutlineBox { Alpha = autoSize ? 1 : 0 }, new OutlineBox { Alpha = autoSize ? 1 : 0 },
mainProvider.WithChild( mainProvider.WithChild(
new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider, beatmap)) new SkinProvidingContainer(CreateRulesetForSkinProvider().CreateLegacySkinProvider(mainProvider, beatmap))
{ {
Child = created, Child = created,
RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None, RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None,

View File

@ -22,7 +22,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.406.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.407.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.403.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.403.0" />
<PackageReference Include="Sentry" Version="2.1.1" /> <PackageReference Include="Sentry" Version="2.1.1" />
<PackageReference Include="SharpCompress" Version="0.25.0" /> <PackageReference Include="SharpCompress" Version="0.25.0" />

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.406.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2020.407.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.403.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.403.0" />
</ItemGroup> </ItemGroup>
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. --> <!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
@ -79,7 +79,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.406.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.407.1" />
<PackageReference Include="SharpCompress" Version="0.25.0" /> <PackageReference Include="SharpCompress" Version="0.25.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />