diff --git a/global.json b/global.json
index 0223dc7330..6c793a3f1d 100644
--- a/global.json
+++ b/global.json
@@ -5,6 +5,6 @@
"version": "3.1.100"
},
"msbuild-sdks": {
- "Microsoft.Build.Traversal": "2.0.32"
+ "Microsoft.Build.Traversal": "2.0.34"
}
}
\ No newline at end of file
diff --git a/osu.Android.props b/osu.Android.props
index 77365b51a9..aaac6ec427 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs
index c87a1d438b..ce0b9fe4b6 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0)
frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount);
- explosion = skin.GetAnimation(imageName, true, false, startAtCurrentTime: true, frameLength: frameLength).With(d =>
+ explosion = skin.GetAnimation(imageName, true, false, frameLength: frameLength).With(d =>
{
if (d == null)
return;
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs
index d2ceb06d0b..85523ae3c0 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs
@@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Mania.Skinning
private Container directionContainer;
private Sprite noteSprite;
+ private float? minimumColumnWidth;
+
public LegacyNotePiece()
{
RelativeSizeAxes = Axes.X;
@@ -29,6 +31,8 @@ namespace osu.Game.Rulesets.Mania.Skinning
[BackgroundDependencyLoader]
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
{
+ minimumColumnWidth = skin.GetConfig(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.MinimumColumnWidth))?.Value;
+
InternalChild = directionContainer = new Container
{
Origin = Anchor.BottomCentre,
@@ -47,8 +51,10 @@ namespace osu.Game.Rulesets.Mania.Skinning
if (noteSprite.Texture != null)
{
- var scale = DrawWidth / noteSprite.Texture.DisplayWidth;
- noteSprite.Scale = new Vector2(scale);
+ // The height is scaled to the minimum column width, if provided.
+ float minimumWidth = minimumColumnWidth ?? DrawWidth;
+
+ noteSprite.Scale = Vector2.Divide(new Vector2(DrawWidth, minimumWidth), noteSprite.Texture.DisplayWidth);
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
index c8c537964f..14cad39b04 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
@@ -64,6 +64,7 @@ namespace osu.Game.Rulesets.Mania.UI
{
// Mania doesn't care about global velocity
p.Velocity = 1;
+ p.BaseBeatLength *= Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier;
// For non-mania beatmap, speed changes should only happen through timing points
if (!isForCurrentRuleset)
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
new file mode 100644
index 0000000000..f5b20fd1c5
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
@@ -0,0 +1,253 @@
+// 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 Humanizer;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Framework.Timing;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Configuration;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using osu.Game.Storyboards;
+using osuTK;
+using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [TestFixture]
+ public class TestSceneSliderSnaking : TestSceneOsuPlayer
+ {
+ [Resolved]
+ private AudioManager audioManager { get; set; }
+
+ private TrackVirtualManual track;
+
+ protected override bool Autoplay => autoplay;
+ private bool autoplay;
+
+ private readonly BindableBool snakingIn = new BindableBool();
+ private readonly BindableBool snakingOut = new BindableBool();
+
+ private const double duration_of_span = 3605;
+ private const double fade_in_modifier = -1200;
+
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
+ {
+ var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
+ track = (TrackVirtualManual)working.Track;
+ return working;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(RulesetConfigCache configCache)
+ {
+ var config = (OsuRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
+ config.BindWith(OsuRulesetSetting.SnakingInSliders, snakingIn);
+ config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut);
+ }
+
+ private DrawableSlider slider;
+
+ [SetUpSteps]
+ public override void SetUpSteps() { }
+
+ [TestCase(0)]
+ [TestCase(1)]
+ [TestCase(2)]
+ public void TestSnakingEnabled(int sliderIndex)
+ {
+ AddStep("enable autoplay", () => autoplay = true);
+ base.SetUpSteps();
+ AddUntilStep("wait for track to start running", () => track.IsRunning);
+
+ double startTime = hitObjects[sliderIndex].StartTime;
+ retrieveDrawableSlider(sliderIndex);
+ setSnaking(true);
+
+ ensureSnakingIn(startTime + fade_in_modifier);
+
+ for (int i = 0; i < sliderIndex; i++)
+ {
+ // non-final repeats should not snake out
+ ensureNoSnakingOut(startTime, i);
+ }
+
+ // final repeat should snake out
+ ensureSnakingOut(startTime, sliderIndex);
+ }
+
+ [TestCase(0)]
+ [TestCase(1)]
+ [TestCase(2)]
+ public void TestSnakingDisabled(int sliderIndex)
+ {
+ AddStep("have autoplay", () => autoplay = true);
+ base.SetUpSteps();
+ AddUntilStep("wait for track to start running", () => track.IsRunning);
+
+ double startTime = hitObjects[sliderIndex].StartTime;
+ retrieveDrawableSlider(sliderIndex);
+ setSnaking(false);
+
+ ensureNoSnakingIn(startTime + fade_in_modifier);
+
+ for (int i = 0; i <= sliderIndex; i++)
+ {
+ // no snaking out ever, including final repeat
+ ensureNoSnakingOut(startTime, i);
+ }
+ }
+
+ [Test]
+ public void TestRepeatArrowDoesNotMoveWhenHit()
+ {
+ AddStep("enable autoplay", () => autoplay = true);
+ setSnaking(true);
+ base.SetUpSteps();
+
+ // repeat might have a chance to update its position depending on where in the frame its hit,
+ // so some leniency is allowed here instead of checking strict equality
+ checkPositionChange(16600, sliderRepeat, positionAlmostSame);
+ }
+
+ [Test]
+ public void TestRepeatArrowMovesWhenNotHit()
+ {
+ AddStep("disable autoplay", () => autoplay = false);
+ setSnaking(true);
+ base.SetUpSteps();
+
+ checkPositionChange(16600, sliderRepeat, positionDecreased);
+ }
+
+ private void retrieveDrawableSlider(int index) => AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () =>
+ {
+ slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index);
+ });
+
+ private void ensureSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionIncreased);
+ private void ensureNoSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionRemainsSame);
+
+ private void ensureSnakingOut(double startTime, int repeatIndex)
+ {
+ var repeatTime = timeAtRepeat(startTime, repeatIndex);
+
+ if (repeatIndex % 2 == 0)
+ checkPositionChange(repeatTime, sliderStart, positionIncreased);
+ else
+ checkPositionChange(repeatTime, sliderEnd, positionDecreased);
+ }
+
+ private void ensureNoSnakingOut(double startTime, int repeatIndex) =>
+ checkPositionChange(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame);
+
+ private double timeAtRepeat(double startTime, int repeatIndex) => startTime + 100 + duration_of_span * repeatIndex;
+ private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func)sliderStart : sliderEnd;
+
+ private List sliderCurve => ((PlaySliderBody)slider.Body.Drawable).CurrentCurve;
+ private Vector2 sliderStart() => sliderCurve.First();
+ private Vector2 sliderEnd() => sliderCurve.Last();
+
+ private Vector2 sliderRepeat()
+ {
+ var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1);
+ var repeat = drawable.ChildrenOfType>().First().Children.First();
+ return repeat.Position;
+ }
+
+ private bool positionRemainsSame(Vector2 previous, Vector2 current) => previous == current;
+ private bool positionIncreased(Vector2 previous, Vector2 current) => current.X > previous.X && current.Y > previous.Y;
+ private bool positionDecreased(Vector2 previous, Vector2 current) => current.X < previous.X && current.Y < previous.Y;
+ private bool positionAlmostSame(Vector2 previous, Vector2 current) => Precision.AlmostEquals(previous, current, 1);
+
+ private void checkPositionChange(double startTime, Func positionToCheck, Func positionAssertion)
+ {
+ Vector2 previousPosition = Vector2.Zero;
+
+ string positionDescription = positionToCheck.Method.Name.Humanize(LetterCasing.LowerCase);
+ string assertionDescription = positionAssertion.Method.Name.Humanize(LetterCasing.LowerCase);
+
+ addSeekStep(startTime);
+ AddStep($"save {positionDescription} position", () => previousPosition = positionToCheck.Invoke());
+ addSeekStep(startTime + 100);
+ AddAssert($"{positionDescription} {assertionDescription}", () =>
+ {
+ var currentPosition = positionToCheck.Invoke();
+ return positionAssertion.Invoke(previousPosition, currentPosition);
+ });
+ }
+
+ private void setSnaking(bool value)
+ {
+ AddStep($"{(value ? "enable" : "disable")} snaking", () =>
+ {
+ snakingIn.Value = value;
+ snakingOut.Value = value;
+ });
+ }
+
+ private void addSeekStep(double time)
+ {
+ AddStep($"seek to {time}", () => track.Seek(time));
+
+ AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
+ }
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
+ {
+ HitObjects = hitObjects
+ };
+
+ private readonly List hitObjects = new List
+ {
+ new Slider
+ {
+ StartTime = 3000,
+ Position = new Vector2(100, 100),
+ Path = new SliderPath(PathType.PerfectCurve, new[]
+ {
+ Vector2.Zero,
+ new Vector2(300, 200)
+ }),
+ },
+ new Slider
+ {
+ StartTime = 13000,
+ Position = new Vector2(100, 100),
+ Path = new SliderPath(PathType.PerfectCurve, new[]
+ {
+ Vector2.Zero,
+ new Vector2(300, 200)
+ }),
+ RepeatCount = 1,
+ },
+ new Slider
+ {
+ StartTime = 23000,
+ Position = new Vector2(100, 100),
+ Path = new SliderPath(PathType.PerfectCurve, new[]
+ {
+ Vector2.Zero,
+ new Vector2(300, 200)
+ }),
+ RepeatCount = 2,
+ },
+ new HitCircle
+ {
+ StartTime = 199999,
+ }
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
index 8bb324d02e..a981648444 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
Anchor = Anchor.Centre,
Alpha = 0.5f,
}
- }, confineMode: ConfineMode.NoScaling);
+ });
}
public double AnimationStartTime { get; set; }
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 5c7f4a42b3..9b6f39d91d 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -186,7 +186,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.ApplySkin(skin, allowFallback);
bool allowBallTint = skin.GetConfig(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
- Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White;
+ Ball.AccentColour = allowBallTint ? AccentColour.Value : Color4.White;
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
index 487401c939..ba0003b5cd 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
switch (osuComponent.Component)
{
case OsuSkinComponents.FollowPoint:
- return this.GetAnimation(component.LookupName, true, false, true);
+ return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false);
case OsuSkinComponents.SliderFollowCircle:
var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
index cef38bbbb8..aedf26ee75 100644
--- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
+++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
@@ -106,7 +106,7 @@ namespace osu.Game.Tests.Skins
var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource("skin-empty.ini"))
using (var stream = new LineBufferedReader(resStream))
- Assert.IsNull(decoder.Decode(stream).LegacyVersion);
+ Assert.That(decoder.Decode(stream).LegacyVersion, Is.EqualTo(1.0m));
}
}
}
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
index 35313ee858..685decf097 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.IO;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -12,7 +13,10 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;
+using osu.Game.IO;
+using osu.Game.Rulesets.Osu;
using osu.Game.Skinning;
+using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
@@ -22,15 +26,15 @@ namespace osu.Game.Tests.Skins
[HeadlessTest]
public class TestSceneSkinConfigurationLookup : OsuTestScene
{
- private SkinSource source1;
- private SkinSource source2;
+ private UserSkinSource userSource;
+ private BeatmapSkinSource beatmapSource;
private SkinRequester requester;
[SetUp]
public void SetUp() => Schedule(() =>
{
- Add(new SkinProvidingContainer(source1 = new SkinSource())
- .WithChild(new SkinProvidingContainer(source2 = new SkinSource())
+ Add(new SkinProvidingContainer(userSource = new UserSkinSource())
+ .WithChild(new SkinProvidingContainer(beatmapSource = new BeatmapSkinSource())
.WithChild(requester = new SkinRequester())));
});
@@ -39,31 +43,31 @@ namespace osu.Game.Tests.Skins
{
AddStep("Add config values", () =>
{
- source1.Configuration.ConfigDictionary["Lookup"] = "source1";
- source2.Configuration.ConfigDictionary["Lookup"] = "source2";
+ userSource.Configuration.ConfigDictionary["Lookup"] = "user skin";
+ beatmapSource.Configuration.ConfigDictionary["Lookup"] = "beatmap skin";
});
- AddAssert("Check lookup finds source2", () => requester.GetConfig("Lookup")?.Value == "source2");
+ AddAssert("Check lookup finds beatmap skin", () => requester.GetConfig("Lookup")?.Value == "beatmap skin");
}
[Test]
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("FloatTest")?.Value == 1.1f);
}
[Test]
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("BoolTest")?.Value == true);
}
[Test]
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.Test)?.Value == ValueType.Test2);
}
@@ -76,7 +80,7 @@ namespace osu.Game.Tests.Skins
[Test]
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", () =>
{
@@ -88,7 +92,7 @@ namespace osu.Game.Tests.Skins
[Test]
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(new SkinCustomColourLookup("Lookup"))?.Value == Color4.Red);
}
@@ -101,7 +105,7 @@ namespace osu.Game.Tests.Skins
[Test]
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", () =>
{
@@ -127,26 +131,51 @@ namespace osu.Game.Tests.Skins
[Test]
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(55, 110, 166, 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", () =>
- requester.GetConfig>(GlobalSkinColours.ComboColours)?.Value?.SequenceEqual(source1.Configuration.ComboColours) ?? false);
+ AddAssert("Check retrieved combo colours from user skin", () =>
+ requester.GetConfig>(GlobalSkinColours.ComboColours)?.Value?.SequenceEqual(userSource.Configuration.ComboColours) ?? false);
}
[Test]
- public void TestLegacyVersionLookup()
+ public void TestNullBeatmapVersionFallsBackToUserSkin()
{
- AddStep("Set source1 version 2.3", () => source1.Configuration.LegacyVersion = 2.3m);
- AddStep("Set source2 version null", () => source2.Configuration.LegacyVersion = null);
+ AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m);
+ AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null);
AddAssert("Check legacy version lookup", () => requester.GetConfig(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.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.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.Version)?.Value == 1.0m);
+ }
+
public enum LookupType
{
Test
@@ -159,14 +188,22 @@ namespace osu.Game.Tests.Skins
Test3
}
- public class SkinSource : LegacySkin
+ public class UserSkinSource : LegacySkin
{
- public SkinSource()
+ public UserSkinSource()
: 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
{
private ISkinSource skin;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs
index ff25e609c1..91d6f2f143 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs
@@ -26,16 +26,8 @@ namespace osu.Game.Tests.Visual.Gameplay
private readonly IReadOnlyList testBreaks = new List
{
- new BreakPeriod
- {
- StartTime = 1000,
- EndTime = 5000,
- },
- new BreakPeriod
- {
- StartTime = 6000,
- EndTime = 13500,
- },
+ new BreakPeriod(1000, 5000),
+ new BreakPeriod(6000, 13500),
};
public TestSceneBreakTracker()
@@ -70,7 +62,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestNoEffectsBreak()
{
- var shortBreak = new BreakPeriod { EndTime = 500 };
+ var shortBreak = new BreakPeriod(0, 500);
setClock(true);
loadBreaksStep("short break", new[] { shortBreak });
@@ -127,13 +119,12 @@ namespace osu.Game.Tests.Visual.Gameplay
private void addShowBreakStep(double seconds)
{
- AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = breakTracker.Breaks = new List
+ AddStep($"show '{seconds}s' break", () =>
{
- new BreakPeriod
+ breakOverlay.Breaks = breakTracker.Breaks = new List
{
- StartTime = Clock.CurrentTime,
- EndTime = Clock.CurrentTime + seconds * 1000,
- }
+ new BreakPeriod(Clock.CurrentTime, Clock.CurrentTime + seconds * 1000)
+ };
});
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
index 03a19b6690..2294cd6966 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
@@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
- Position = new Vector2(0, 25),
+ Position = new Vector2(-5, 25),
Current = { BindTarget = modSelect.SelectedMods }
}
};
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index f5b27eddd2..33bb9774df 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -305,12 +305,9 @@ namespace osu.Game.Beatmaps.Formats
case LegacyEventType.Break:
double start = getOffsetTime(Parsing.ParseDouble(split[1]));
+ double end = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2])));
- var breakEvent = new BreakPeriod
- {
- StartTime = start,
- EndTime = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2])))
- };
+ var breakEvent = new BreakPeriod(start, end);
if (!breakEvent.HasEffect)
return;
diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs
index 5d79c7a86b..bb8ae4a66a 100644
--- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs
+++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs
@@ -32,6 +32,17 @@ namespace osu.Game.Beatmaps.Timing
///
public bool HasEffect => Duration >= MIN_BREAK_DURATION;
+ ///
+ /// Constructs a new break period.
+ ///
+ /// The start time of the break period.
+ /// The end time of the break period.
+ public BreakPeriod(double startTime, double endTime)
+ {
+ StartTime = startTime;
+ EndTime = endTime;
+ }
+
///
/// Whether this break contains a specified time.
///
diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs
index 6cd1aa912f..ed3f9af8e2 100644
--- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs
+++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs
@@ -161,7 +161,7 @@ namespace osu.Game.Screens.Multi
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
- Spacing = new Vector2(10, 0),
+ Spacing = new Vector2(15, 0),
Children = new Drawable[]
{
authorText = new LinkFlowContainer { AutoSizeAxes = Axes.Both },
diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs
index 336b03544f..99c31241f1 100644
--- a/osu.Game/Screens/Play/HUD/ModDisplay.cs
+++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play.HUD
}
}
- protected readonly FillFlowContainer IconsContainer;
+ private readonly FillFlowContainer iconsContainer;
private readonly OsuSpriteText unrankedText;
public ModDisplay()
@@ -50,13 +50,12 @@ namespace osu.Game.Screens.Play.HUD
Children = new Drawable[]
{
- IconsContainer = new ReverseChildIDFillFlowContainer
+ iconsContainer = new ReverseChildIDFillFlowContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
- Margin = new MarginPadding { Left = 10, Right = 10 },
},
unrankedText = new OsuSpriteText
{
@@ -69,11 +68,11 @@ namespace osu.Game.Screens.Play.HUD
Current.ValueChanged += mods =>
{
- IconsContainer.Clear();
+ iconsContainer.Clear();
foreach (Mod mod in mods.NewValue)
{
- IconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) });
+ iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) });
}
if (IsLoaded)
@@ -92,7 +91,7 @@ namespace osu.Game.Screens.Play.HUD
base.LoadComplete();
appearTransform();
- IconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint);
+ iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint);
}
private void appearTransform()
@@ -104,20 +103,20 @@ namespace osu.Game.Screens.Play.HUD
expand();
- using (IconsContainer.BeginDelayedSequence(1200))
+ using (iconsContainer.BeginDelayedSequence(1200))
contract();
}
private void expand()
{
if (ExpansionMode != ExpansionMode.AlwaysContracted)
- IconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint);
+ iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint);
}
private void contract()
{
if (ExpansionMode != ExpansionMode.AlwaysExpanded)
- IconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint);
+ iconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint);
}
protected override bool OnHover(HoverEvent e)
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index a5f8051557..e06f6d19c2 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -285,7 +285,7 @@ namespace osu.Game.Screens.Play
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
- Margin = new MarginPadding { Top = 20, Right = 10 },
+ Margin = new MarginPadding { Top = 20, Right = 20 },
};
protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset?.FirstAvailableHitWindows);
diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
index df7eed9a02..b058cc142b 100644
--- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
+++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
@@ -30,6 +30,9 @@ namespace osu.Game.Screens.Ranking.Expanded
private readonly ScoreInfo score;
private readonly List statisticDisplays = new List();
+
+ private FillFlowContainer starAndModDisplay;
+
private RollingCounter scoreCounter;
///
@@ -119,11 +122,12 @@ namespace osu.Game.Screens.Ranking.Expanded
Alpha = 0,
AlwaysPresent = true
},
- new FillFlowContainer
+ starAndModDisplay = new FillFlowContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
+ Spacing = new Vector2(5, 0),
Children = new Drawable[]
{
new StarRatingDisplay(beatmap)
@@ -131,15 +135,6 @@ namespace osu.Game.Screens.Ranking.Expanded
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
- new ModDisplay
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- DisplayUnrankedText = false,
- ExpansionMode = ExpansionMode.AlwaysExpanded,
- Scale = new Vector2(0.5f),
- Current = { Value = score.Mods }
- }
}
},
new FillFlowContainer
@@ -214,6 +209,19 @@ namespace osu.Game.Screens.Ranking.Expanded
}
}
};
+
+ if (score.Mods.Any())
+ {
+ starAndModDisplay.Add(new ModDisplay
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ DisplayUnrankedText = false,
+ ExpansionMode = ExpansionMode.AlwaysExpanded,
+ Scale = new Vector2(0.5f),
+ Current = { Value = score.Mods }
+ });
+ }
}
protected override void LoadComplete()
diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs
index 2411cf26f9..02333da0dc 100644
--- a/osu.Game/Screens/Select/FooterButtonMods.cs
+++ b/osu.Game/Screens/Select/FooterButtonMods.cs
@@ -27,18 +27,19 @@ namespace osu.Game.Screens.Select
}
protected readonly OsuSpriteText MultiplierText;
- private readonly FooterModDisplay modDisplay;
+ private readonly ModDisplay modDisplay;
private Color4 lowMultiplierColour;
private Color4 highMultiplierColour;
public FooterButtonMods()
{
- ButtonContentContainer.Add(modDisplay = new FooterModDisplay
+ ButtonContentContainer.Add(modDisplay = new ModDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
DisplayUnrankedText = false,
- Scale = new Vector2(0.8f)
+ Scale = new Vector2(0.8f),
+ ExpansionMode = ExpansionMode.AlwaysContracted,
});
ButtonContentContainer.Add(MultiplierText = new OsuSpriteText
{
@@ -84,16 +85,5 @@ namespace osu.Game.Screens.Select
else
modDisplay.FadeOut();
}
-
- private class FooterModDisplay : ModDisplay
- {
- public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false;
-
- public FooterModDisplay()
- {
- ExpansionMode = ExpansionMode.AlwaysContracted;
- IconsContainer.Margin = new MarginPadding();
- }
- }
}
}
diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs
index 1c39fc41bb..1190a330fe 100644
--- a/osu.Game/Skinning/LegacyBeatmapSkin.cs
+++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Audio;
+using osu.Framework.Bindables;
using osu.Framework.IO.Stores;
using osu.Game.Beatmaps;
@@ -18,6 +19,20 @@ namespace osu.Game.Skinning
Configuration.AllowDefaultComboColoursFallback = false;
}
+ public override IBindable GetConfig(TLookup lookup)
+ {
+ switch (lookup)
+ {
+ case LegacySkinConfiguration.LegacySetting s when s == LegacySkinConfiguration.LegacySetting.Version:
+ if (Configuration.LegacyVersion is decimal version)
+ return SkinUtils.As(new Bindable(version));
+
+ return null;
+ }
+
+ return base.GetConfig(lookup);
+ }
+
private static SkinInfo createSkinInfo(BeatmapInfo beatmap) =>
new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata.Author.ToString() };
}
diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
index ac257b8c80..08b3b8ff5a 100644
--- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using osu.Game.Beatmaps.Formats;
using osuTK.Graphics;
@@ -45,5 +46,13 @@ namespace osu.Game.Skinning
ColumnLineWidth.AsSpan().Fill(2);
ColumnWidth.AsSpan().Fill(DEFAULT_COLUMN_SIZE);
}
+
+ private float? minimumColumnWidth;
+
+ public float MinimumColumnWidth
+ {
+ get => minimumColumnWidth ?? ColumnWidth.Min();
+ set => minimumColumnWidth = value;
+ }
}
}
diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
index 853d07c060..588e9e3ee2 100644
--- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
@@ -36,6 +36,7 @@ namespace osu.Game.Skinning
HoldNoteBodyImage,
ExplosionImage,
ExplosionScale,
- ColumnLineColour
+ ColumnLineColour,
+ MinimumColumnWidth
}
}
diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
index 3393fe09b3..4fe36c2239 100644
--- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
@@ -106,8 +106,14 @@ namespace osu.Game.Skinning
case "LightingNWidth":
parseArrayValue(pair.Value, currentConfig.ExplosionWidth);
break;
+
+ case "WidthForNoteHeightScale":
+ currentConfig.MinimumColumnWidth = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
+ break;
}
}
+
+ pendingLines.Clear();
}
private void parseArrayValue(string value, float[] output)
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 3d3eac97f6..cf956d9d4a 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -71,7 +71,7 @@ namespace osu.Game.Skinning
}
}
else
- Configuration = new LegacySkinConfiguration { LegacyVersion = LegacySkinConfiguration.LATEST_VERSION };
+ Configuration = new LegacySkinConfiguration();
}
if (storage != null)
@@ -122,10 +122,7 @@ namespace osu.Game.Skinning
switch (legacy)
{
case LegacySkinConfiguration.LegacySetting.Version:
- if (Configuration.LegacyVersion is decimal version)
- return SkinUtils.As(new Bindable(version));
-
- break;
+ return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? LegacySkinConfiguration.LATEST_VERSION));
}
break;
@@ -207,6 +204,9 @@ namespace osu.Game.Skinning
case LegacyManiaSkinConfigurationLookups.ColumnLineColour:
return SkinUtils.As(getCustomColour(existing, "ColourColumnLine"));
+
+ case LegacyManiaSkinConfigurationLookups.MinimumColumnWidth:
+ return SkinUtils.As(new Bindable(existing.MinimumColumnWidth));
}
return null;
diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs
index 88ba7b23b7..5d4b8de7ac 100644
--- a/osu.Game/Skinning/LegacySkinDecoder.cs
+++ b/osu.Game/Skinning/LegacySkinDecoder.cs
@@ -52,5 +52,12 @@ namespace osu.Game.Skinning
base.ParseLine(skin, section, line);
}
+
+ protected override LegacySkinConfiguration CreateTemplateObject()
+ {
+ var config = base.CreateTemplateObject();
+ config.LegacyVersion = 1.0m;
+ return config;
+ }
}
}
diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs
index 9bfde4fdcb..549571dec4 100644
--- a/osu.Game/Skinning/LegacySkinExtensions.cs
+++ b/osu.Game/Skinning/LegacySkinExtensions.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Skinning
public static class LegacySkinExtensions
{
public static Drawable GetAnimation(this ISkin source, string componentName, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-",
- bool startAtCurrentTime = false, double? frameLength = null)
+ bool startAtCurrentTime = true, double? frameLength = null)
{
Texture texture;
@@ -72,7 +72,7 @@ namespace osu.Game.Skinning
if (timeReference != null)
{
Clock = timeReference.Clock;
- PlaybackPosition = timeReference.AnimationStartTime - timeReference.Clock.CurrentTime;
+ PlaybackPosition = timeReference.Clock.CurrentTime - timeReference.AnimationStartTime;
}
}
}
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs
index 2e7b66ea4f..a85936edf7 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs
@@ -46,7 +46,6 @@ namespace osu.Game.Storyboards.Drawables
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
- PlaybackPosition = Video.StartTime
};
}
@@ -56,6 +55,8 @@ namespace osu.Game.Storyboards.Drawables
if (video == null) return;
+ video.PlaybackPosition = Clock.CurrentTime - Video.StartTime;
+
using (video.BeginAbsoluteSequence(0))
video.FadeIn(500);
}
diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs
index d0113b3096..69e17af01b 100644
--- a/osu.Game/Tests/Visual/SkinnableTestScene.cs
+++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual
[BackgroundDependencyLoader]
private void load(AudioManager audio, SkinManager skinManager)
{
- var dllStore = new DllResourceStore(GetType().Assembly);
+ var dllStore = new DllResourceStore(DynamicCompilationOriginal.GetType().Assembly);
metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true);
defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 8d31fbf280..3e2c2b1599 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -22,10 +22,10 @@
-
+
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index e2b98720be..7903d964ce 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -79,8 +79,8 @@
-
-
+
+