1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 12:17:26 +08:00

Merge branch 'master' of https://github.com/ppy/osu into arrow-easing

This commit is contained in:
Endrik Tombak 2020-04-07 17:16:56 +03:00
commit 0b70c20aa3
31 changed files with 435 additions and 103 deletions

View File

@ -5,6 +5,6 @@
"version": "3.1.100" "version": "3.1.100"
}, },
"msbuild-sdks": { "msbuild-sdks": {
"Microsoft.Build.Traversal": "2.0.32" "Microsoft.Build.Traversal": "2.0.34"
} }
} }

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.403.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2020.407.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0) if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0)
frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount); 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) if (d == null)
return; return;

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

@ -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,253 @@
// 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 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<Vector2> positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func<Vector2>)sliderStart : sliderEnd;
private List<Vector2> 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<Container<DrawableSliderRepeat>>().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<Vector2> positionToCheck, Func<Vector2, Vector2, bool> 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<HitObject> hitObjects = new List<HitObject>
{
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,
}
};
}
}

View File

@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Alpha = 0.5f, Alpha = 0.5f,
} }
}, confineMode: ConfineMode.NoScaling); });
} }
public double AnimationStartTime { get; set; } public double AnimationStartTime { get; set; }

View File

@ -186,7 +186,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.ApplySkin(skin, allowFallback); base.ApplySkin(skin, allowFallback);
bool allowBallTint = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false; bool allowBallTint = skin.GetConfig<OsuSkinConfiguration, bool>(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) protected override void CheckForResult(bool userTriggered, double timeOffset)

View File

@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
switch (osuComponent.Component) switch (osuComponent.Component)
{ {
case OsuSkinComponents.FollowPoint: case OsuSkinComponents.FollowPoint:
return this.GetAnimation(component.LookupName, true, false, true); return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false);
case OsuSkinComponents.SliderFollowCircle: case OsuSkinComponents.SliderFollowCircle:
var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true); var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);

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

@ -26,16 +26,8 @@ namespace osu.Game.Tests.Visual.Gameplay
private readonly IReadOnlyList<BreakPeriod> testBreaks = new List<BreakPeriod> private readonly IReadOnlyList<BreakPeriod> testBreaks = new List<BreakPeriod>
{ {
new BreakPeriod new BreakPeriod(1000, 5000),
{ new BreakPeriod(6000, 13500),
StartTime = 1000,
EndTime = 5000,
},
new BreakPeriod
{
StartTime = 6000,
EndTime = 13500,
},
}; };
public TestSceneBreakTracker() public TestSceneBreakTracker()
@ -70,7 +62,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestNoEffectsBreak() public void TestNoEffectsBreak()
{ {
var shortBreak = new BreakPeriod { EndTime = 500 }; var shortBreak = new BreakPeriod(0, 500);
setClock(true); setClock(true);
loadBreaksStep("short break", new[] { shortBreak }); loadBreaksStep("short break", new[] { shortBreak });
@ -127,13 +119,12 @@ namespace osu.Game.Tests.Visual.Gameplay
private void addShowBreakStep(double seconds) private void addShowBreakStep(double seconds)
{ {
AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = breakTracker.Breaks = new List<BreakPeriod> AddStep($"show '{seconds}s' break", () =>
{ {
new BreakPeriod breakOverlay.Breaks = breakTracker.Breaks = new List<BreakPeriod>
{ {
StartTime = Clock.CurrentTime, new BreakPeriod(Clock.CurrentTime, Clock.CurrentTime + seconds * 1000)
EndTime = Clock.CurrentTime + seconds * 1000, };
}
}); });
} }

View File

@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Position = new Vector2(0, 25), Position = new Vector2(-5, 25),
Current = { BindTarget = modSelect.SelectedMods } Current = { BindTarget = modSelect.SelectedMods }
} }
}; };

View File

@ -305,12 +305,9 @@ namespace osu.Game.Beatmaps.Formats
case LegacyEventType.Break: case LegacyEventType.Break:
double start = getOffsetTime(Parsing.ParseDouble(split[1])); double start = getOffsetTime(Parsing.ParseDouble(split[1]));
double end = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2])));
var breakEvent = new BreakPeriod var breakEvent = new BreakPeriod(start, end);
{
StartTime = start,
EndTime = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2])))
};
if (!breakEvent.HasEffect) if (!breakEvent.HasEffect)
return; return;

View File

@ -32,6 +32,17 @@ namespace osu.Game.Beatmaps.Timing
/// </summary> /// </summary>
public bool HasEffect => Duration >= MIN_BREAK_DURATION; public bool HasEffect => Duration >= MIN_BREAK_DURATION;
/// <summary>
/// Constructs a new break period.
/// </summary>
/// <param name="startTime">The start time of the break period.</param>
/// <param name="endTime">The end time of the break period.</param>
public BreakPeriod(double startTime, double endTime)
{
StartTime = startTime;
EndTime = endTime;
}
/// <summary> /// <summary>
/// Whether this break contains a specified time. /// Whether this break contains a specified time.
/// </summary> /// </summary>

View File

@ -161,7 +161,7 @@ namespace osu.Game.Screens.Multi
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0), Spacing = new Vector2(15, 0),
Children = new Drawable[] Children = new Drawable[]
{ {
authorText = new LinkFlowContainer { AutoSizeAxes = Axes.Both }, authorText = new LinkFlowContainer { AutoSizeAxes = Axes.Both },

View File

@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play.HUD
} }
} }
protected readonly FillFlowContainer<ModIcon> IconsContainer; private readonly FillFlowContainer<ModIcon> iconsContainer;
private readonly OsuSpriteText unrankedText; private readonly OsuSpriteText unrankedText;
public ModDisplay() public ModDisplay()
@ -50,13 +50,12 @@ namespace osu.Game.Screens.Play.HUD
Children = new Drawable[] Children = new Drawable[]
{ {
IconsContainer = new ReverseChildIDFillFlowContainer<ModIcon> iconsContainer = new ReverseChildIDFillFlowContainer<ModIcon>
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
Margin = new MarginPadding { Left = 10, Right = 10 },
}, },
unrankedText = new OsuSpriteText unrankedText = new OsuSpriteText
{ {
@ -69,11 +68,11 @@ namespace osu.Game.Screens.Play.HUD
Current.ValueChanged += mods => Current.ValueChanged += mods =>
{ {
IconsContainer.Clear(); iconsContainer.Clear();
foreach (Mod mod in mods.NewValue) 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) if (IsLoaded)
@ -92,7 +91,7 @@ namespace osu.Game.Screens.Play.HUD
base.LoadComplete(); base.LoadComplete();
appearTransform(); appearTransform();
IconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint);
} }
private void appearTransform() private void appearTransform()
@ -104,20 +103,20 @@ namespace osu.Game.Screens.Play.HUD
expand(); expand();
using (IconsContainer.BeginDelayedSequence(1200)) using (iconsContainer.BeginDelayedSequence(1200))
contract(); contract();
} }
private void expand() private void expand()
{ {
if (ExpansionMode != ExpansionMode.AlwaysContracted) 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() private void contract()
{ {
if (ExpansionMode != ExpansionMode.AlwaysExpanded) 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) protected override bool OnHover(HoverEvent e)

View File

@ -285,7 +285,7 @@ namespace osu.Game.Screens.Play
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both, 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); protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset?.FirstAvailableHitWindows);

View File

@ -30,6 +30,9 @@ namespace osu.Game.Screens.Ranking.Expanded
private readonly ScoreInfo score; private readonly ScoreInfo score;
private readonly List<StatisticDisplay> statisticDisplays = new List<StatisticDisplay>(); private readonly List<StatisticDisplay> statisticDisplays = new List<StatisticDisplay>();
private FillFlowContainer starAndModDisplay;
private RollingCounter<long> scoreCounter; private RollingCounter<long> scoreCounter;
/// <summary> /// <summary>
@ -119,11 +122,12 @@ namespace osu.Game.Screens.Ranking.Expanded
Alpha = 0, Alpha = 0,
AlwaysPresent = true AlwaysPresent = true
}, },
new FillFlowContainer starAndModDisplay = new FillFlowContainer
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Spacing = new Vector2(5, 0),
Children = new Drawable[] Children = new Drawable[]
{ {
new StarRatingDisplay(beatmap) new StarRatingDisplay(beatmap)
@ -131,15 +135,6 @@ namespace osu.Game.Screens.Ranking.Expanded
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = 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 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() protected override void LoadComplete()

View File

@ -27,18 +27,19 @@ namespace osu.Game.Screens.Select
} }
protected readonly OsuSpriteText MultiplierText; protected readonly OsuSpriteText MultiplierText;
private readonly FooterModDisplay modDisplay; private readonly ModDisplay modDisplay;
private Color4 lowMultiplierColour; private Color4 lowMultiplierColour;
private Color4 highMultiplierColour; private Color4 highMultiplierColour;
public FooterButtonMods() public FooterButtonMods()
{ {
ButtonContentContainer.Add(modDisplay = new FooterModDisplay ButtonContentContainer.Add(modDisplay = new ModDisplay
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
DisplayUnrankedText = false, DisplayUnrankedText = false,
Scale = new Vector2(0.8f) Scale = new Vector2(0.8f),
ExpansionMode = ExpansionMode.AlwaysContracted,
}); });
ButtonContentContainer.Add(MultiplierText = new OsuSpriteText ButtonContentContainer.Add(MultiplierText = new OsuSpriteText
{ {
@ -84,16 +85,5 @@ namespace osu.Game.Screens.Select
else else
modDisplay.FadeOut(); 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();
}
}
} }
} }

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;
@ -45,5 +46,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

@ -36,6 +36,7 @@ namespace osu.Game.Skinning
HoldNoteBodyImage, HoldNoteBodyImage,
ExplosionImage, ExplosionImage,
ExplosionScale, ExplosionScale,
ColumnLineColour ColumnLineColour,
MinimumColumnWidth
} }
} }

View File

@ -106,8 +106,14 @@ 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;
} }
} }
pendingLines.Clear();
} }
private void parseArrayValue(string value, float[] output) private void parseArrayValue(string value, float[] output)

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)
@ -122,10 +122,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;
@ -207,6 +204,9 @@ namespace osu.Game.Skinning
case LegacyManiaSkinConfigurationLookups.ColumnLineColour: case LegacyManiaSkinConfigurationLookups.ColumnLineColour:
return SkinUtils.As<TValue>(getCustomColour(existing, "ColourColumnLine")); return SkinUtils.As<TValue>(getCustomColour(existing, "ColourColumnLine"));
case LegacyManiaSkinConfigurationLookups.MinimumColumnWidth:
return SkinUtils.As<TValue>(new Bindable<float>(existing.MinimumColumnWidth));
} }
return null; return null;

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

@ -14,7 +14,7 @@ namespace osu.Game.Skinning
public static class LegacySkinExtensions public static class LegacySkinExtensions
{ {
public static Drawable GetAnimation(this ISkin source, string componentName, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-", 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; Texture texture;
@ -72,7 +72,7 @@ namespace osu.Game.Skinning
if (timeReference != null) if (timeReference != null)
{ {
Clock = timeReference.Clock; Clock = timeReference.Clock;
PlaybackPosition = timeReference.AnimationStartTime - timeReference.Clock.CurrentTime; PlaybackPosition = timeReference.Clock.CurrentTime - timeReference.AnimationStartTime;
} }
} }
} }

View File

@ -46,7 +46,6 @@ namespace osu.Game.Storyboards.Drawables
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Alpha = 0, Alpha = 0,
PlaybackPosition = Video.StartTime
}; };
} }
@ -56,6 +55,8 @@ namespace osu.Game.Storyboards.Drawables
if (video == null) return; if (video == null) return;
video.PlaybackPosition = Clock.CurrentTime - Video.StartTime;
using (video.BeginAbsoluteSequence(0)) using (video.BeginAbsoluteSequence(0))
video.FadeIn(500); video.FadeIn(500);
} }

View File

@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio, SkinManager skinManager) 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<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);

View File

@ -22,10 +22,10 @@
<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.403.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.24.0" /> <PackageReference Include="SharpCompress" Version="0.25.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
</ItemGroup> </ItemGroup>

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.403.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,8 +79,8 @@
<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.403.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.407.1" />
<PackageReference Include="SharpCompress" Version="0.24.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" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />