1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 14:32:55 +08:00

Merge branch 'master' into fix-results-star-display-alignment

This commit is contained in:
Dan Balasescu 2020-04-07 12:28:33 +09:00 committed by GitHub
commit 5758a9fa4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 295 additions and 34 deletions

View File

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

View File

@ -52,6 +52,6 @@
</ItemGroup>
<ItemGroup>
<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.406.0" />
</ItemGroup>
</Project>

View File

@ -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;

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,
Alpha = 0.5f,
}
}, confineMode: ConfineMode.NoScaling);
});
}
public double AnimationStartTime { get; set; }

View File

@ -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);
@ -132,6 +132,12 @@ namespace osu.Game.Rulesets.Osu.Skinning
return SkinUtils.As<TValue>(new BindableFloat(LEGACY_CIRCLE_RADIUS));
break;
case OsuSkinConfiguration.HitCircleOverlayAboveNumber:
// See https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D
// HitCircleOverlayAboveNumer (with typo) should still be supported for now.
return source.GetConfig<OsuSkinConfiguration, TValue>(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ??
source.GetConfig<OsuSkinConfiguration, TValue>(OsuSkinConfiguration.HitCircleOverlayAboveNumer);
}
break;

View File

@ -12,6 +12,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
AllowSliderBallTint,
CursorExpand,
CursorRotate,
HitCircleOverlayAboveNumber
HitCircleOverlayAboveNumber,
HitCircleOverlayAboveNumer // Some old skins will have this typo
}
}

View File

@ -26,16 +26,8 @@ namespace osu.Game.Tests.Visual.Gameplay
private readonly IReadOnlyList<BreakPeriod> testBreaks = new List<BreakPeriod>
{
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<BreakPeriod>
AddStep($"show '{seconds}s' break", () =>
{
new BreakPeriod
breakOverlay.Breaks = breakTracker.Breaks = new List<BreakPeriod>
{
StartTime = Clock.CurrentTime,
EndTime = Clock.CurrentTime + seconds * 1000,
}
new BreakPeriod(Clock.CurrentTime, Clock.CurrentTime + seconds * 1000)
};
});
}

View File

@ -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;

View File

@ -32,6 +32,17 @@ namespace osu.Game.Beatmaps.Timing
/// </summary>
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>
/// Whether this break contains a specified time.
/// </summary>

View File

@ -108,6 +108,8 @@ namespace osu.Game.Skinning
break;
}
}
pendingLines.Clear();
}
private void parseArrayValue(string value, float[] output)

View File

@ -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;
}
}
}

View File

@ -22,10 +22,10 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.403.0" />
<PackageReference Include="ppy.osu.Framework" Version="2020.406.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.403.0" />
<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="System.ComponentModel.Annotations" Version="4.7.0" />
</ItemGroup>

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.403.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.406.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.403.0" />
</ItemGroup>
<!-- 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.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.403.0" />
<PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="ppy.osu.Framework" Version="2020.406.0" />
<PackageReference Include="SharpCompress" Version="0.25.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />