diff --git a/osu.Android.props b/osu.Android.props
index 7e17f9da16..b147fdd05b 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
index e2465d727e..acdd0a420c 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
@@ -30,6 +31,22 @@ namespace osu.Game.Rulesets.Catch.Mods
Value = 5,
};
+ public override string SettingDescription
+ {
+ get
+ {
+ string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}";
+ string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}";
+
+ return string.Join(", ", new[]
+ {
+ circleSize,
+ base.SettingDescription,
+ approachRate
+ }.Where(s => !string.IsNullOrEmpty(s)));
+ }
+ }
+
protected override void TransferSettings(BeatmapDifficulty difficulty)
{
base.TransferSettings(difficulty);
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index e361b29a9d..8fa9c61b6f 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -141,14 +141,14 @@ namespace osu.Game.Rulesets.Catch.UI
var ourRadius = fruit.DisplayRadius;
float theirRadius = 0;
- const float allowance = 6;
+ const float allowance = 10;
while (caughtFruit.Any(f =>
f.LifetimeEnd == double.MaxValue &&
Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2)))
{
var diff = (ourRadius + theirRadius) / allowance;
- fruit.X += (RNG.NextSingle() - 0.5f) * 2 * diff;
+ fruit.X += (RNG.NextSingle() - 0.5f) * diff * 2;
fruit.Y -= RNG.NextSingle() * diff;
}
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
index b93e372027..8c73c36e99 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Replays.Legacy;
using osu.Game.Rulesets.Mania.Beatmaps;
@@ -26,13 +27,7 @@ namespace osu.Game.Rulesets.Mania.Replays
public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{
- // We don't need to fully convert, just create the converter
- var converter = new ManiaBeatmapConverter(beatmap, new ManiaRuleset());
-
- // NB: Via co-op mod, osu-stable can have two stages with floor(col/2) and ceil(col/2) columns. This will need special handling
- // elsewhere in the game if we do choose to support the old co-op mod anyway. For now, assume that there is only one stage.
-
- var stage = new StageDefinition { Columns = converter.TargetColumns };
+ var maniaBeatmap = (ManiaBeatmap)beatmap;
var normalAction = ManiaAction.Key1;
var specialAction = ManiaAction.Special1;
@@ -42,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Replays
while (activeColumns > 0)
{
- var isSpecial = stage.IsSpecialColumn(counter);
+ var isSpecial = maniaBeatmap.Stages.First().IsSpecialColumn(counter);
if ((activeColumns & 1) > 0)
Actions.Add(isSpecial ? specialAction : normalAction);
@@ -59,17 +54,15 @@ namespace osu.Game.Rulesets.Mania.Replays
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
{
+ var maniaBeatmap = (ManiaBeatmap)beatmap;
+
int keys = 0;
- var converter = new ManiaBeatmapConverter(beatmap, new ManiaRuleset());
-
- var stage = new StageDefinition { Columns = converter.TargetColumns };
-
var specialColumns = new List();
- for (int i = 0; i < converter.TargetColumns; i++)
+ for (int i = 0; i < maniaBeatmap.TotalColumns; i++)
{
- if (stage.IsSpecialColumn(i))
+ if (maniaBeatmap.Stages.First().IsSpecialColumn(i))
specialColumns.Add(i);
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderstartcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderstartcircle@2x.png
new file mode 100644
index 0000000000..4d630443cd
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderstartcircle@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderstartcircleoverlay@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderstartcircleoverlay@2x.png
new file mode 100644
index 0000000000..a824784942
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderstartcircleoverlay@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs
new file mode 100644
index 0000000000..e406f9ddff
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs
@@ -0,0 +1,70 @@
+// 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 NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [TestFixture]
+ public class TestSceneSpinnerSpunOut : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(SpinnerDisc),
+ typeof(DrawableSpinner),
+ typeof(DrawableOsuHitObject),
+ typeof(OsuModSpunOut)
+ };
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ SelectedMods.Value = new[] { new OsuModSpunOut() };
+ });
+
+ [Test]
+ public void TestSpunOut()
+ {
+ DrawableSpinner spinner = null;
+
+ AddStep("create spinner", () => spinner = createSpinner());
+
+ AddUntilStep("wait for end", () => Time.Current > spinner.LifetimeEnd);
+
+ AddAssert("spinner is completed", () => spinner.Progress >= 1);
+ }
+
+ private DrawableSpinner createSpinner()
+ {
+ var spinner = new Spinner
+ {
+ StartTime = Time.Current + 500,
+ EndTime = Time.Current + 2500
+ };
+ spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ var drawableSpinner = new DrawableSpinner(spinner)
+ {
+ Anchor = Anchor.Centre
+ };
+
+ foreach (var mod in SelectedMods.Value.OfType())
+ mod.ApplyToDrawableHitObjects(new[] { drawableSpinner });
+
+ Add(drawableSpinner);
+ return drawableSpinner;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
index 75de6896a3..8228161008 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
@@ -30,6 +31,22 @@ namespace osu.Game.Rulesets.Osu.Mods
Value = 5,
};
+ public override string SettingDescription
+ {
+ get
+ {
+ string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}";
+ string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}";
+
+ return string.Join(", ", new[]
+ {
+ circleSize,
+ base.SettingDescription,
+ approachRate
+ }.Where(s => !string.IsNullOrEmpty(s)));
+ }
+ }
+
protected override void TransferSettings(BeatmapDifficulty difficulty)
{
base.TransferSettings(difficulty);
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
index 9d5d300a9e..7b54baa99b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
@@ -2,21 +2,46 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModSpunOut : Mod
+ public class OsuModSpunOut : Mod, IApplicableToDrawableHitObjects
{
public override string Name => "Spun Out";
public override string Acronym => "SO";
public override IconUsage? Icon => OsuIcon.ModSpunout;
- public override ModType Type => ModType.DifficultyReduction;
+ public override ModType Type => ModType.Automation;
public override string Description => @"Spinners will be automatically completed.";
public override double ScoreMultiplier => 0.9;
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot) };
+
+ public void ApplyToDrawableHitObjects(IEnumerable drawables)
+ {
+ foreach (var hitObject in drawables)
+ {
+ if (hitObject is DrawableSpinner spinner)
+ {
+ spinner.HandleUserInput = false;
+ spinner.OnUpdate += onSpinnerUpdate;
+ }
+ }
+ }
+
+ private void onSpinnerUpdate(Drawable drawable)
+ {
+ var spinner = (DrawableSpinner)drawable;
+
+ spinner.Disc.Tracking = true;
+ spinner.Disc.Rotate(MathUtils.RadiansToDegrees((float)spinner.Clock.ElapsedFrameTime * 0.03f));
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index da1e666aba..5202327245 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public readonly SkinnableDrawable CirclePiece;
private readonly Container scaleContainer;
+ protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle;
+
public DrawableHitCircle(HitCircle h)
: base(h)
{
@@ -57,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return true;
},
},
- CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece()),
+ CirclePiece = new SkinnableDrawable(new OsuSkinComponent(CirclePieceComponent), _ => new MainCirclePiece()),
ApproachCircle = new ApproachCircle
{
Alpha = 0,
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 2d5b9d874c..5c7f4a42b3 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -11,6 +11,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Rulesets.Scoring;
using osuTK.Graphics;
using osu.Game.Skinning;
@@ -196,6 +197,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ApplyResult(r => r.Type = r.Judgement.MaxResult);
}
+ public override void PlaySamples()
+ {
+ // rather than doing it this way, we should probably attach the sample to the tail circle.
+ // this can only be done after we stop using LegacyLastTick.
+ if (TailCircle.Result.Type != HitResult.Miss)
+ base.PlaySamples();
+ }
+
protected override void UpdateStateTransforms(ArmedState state)
{
base.UpdateStateTransforms(state);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
index c5609b01e0..a360071f26 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
@@ -14,6 +14,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly IBindable positionBindable = new Bindable();
private readonly IBindable pathVersion = new Bindable();
+ protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle;
+
private readonly Slider slider;
public DrawableSliderHead(Slider slider, HitCircle h)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
index b9cee71ca1..b04d484195 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
@@ -87,6 +87,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public void UpdateSnakingPosition(Vector2 start, Vector2 end)
{
+ // When the repeat is hit, the arrow should fade out on spot rather than following the slider
+ if (IsHit) return;
+
bool isRepeatAtEnd = sliderRepeat.RepeatIndex % 2 == 0;
List curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index 0ec7f2ebfe..3c8ab0f5ab 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -176,17 +176,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void Update()
{
- Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false;
- if (!SpmCounter.IsPresent && Disc.Tracking)
- SpmCounter.FadeIn(HitObject.TimeFadeIn);
-
base.Update();
+ if (HandleUserInput)
+ Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false;
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
+ if (!SpmCounter.IsPresent && Disc.Tracking)
+ SpmCounter.FadeIn(HitObject.TimeFadeIn);
+
circle.Rotation = Disc.Rotation;
Ticks.Rotation = Disc.Rotation;
SpmCounter.SetRotation(Disc.RotationAbsolute);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
index 3de30d51d9..d4ef039b79 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
@@ -73,6 +73,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}
}
+ ///
+ /// Whether currently in the correct time range to allow spinning.
+ ///
+ private bool isSpinnableTime => spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current;
+
protected override bool OnMouseMove(MouseMoveEvent e)
{
mousePosition = Parent.ToLocalSpace(e.ScreenSpaceMousePosition);
@@ -93,27 +98,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
protected override void Update()
{
base.Update();
-
var thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2));
- bool validAndTracking = tracking && spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current;
+ var delta = thisAngle - lastAngle;
- if (validAndTracking)
- {
- if (!rotationTransferred)
- {
- currentRotation = Rotation * 2;
- rotationTransferred = true;
- }
-
- if (thisAngle - lastAngle > 180)
- lastAngle += 360;
- else if (lastAngle - thisAngle > 180)
- lastAngle -= 360;
-
- currentRotation += thisAngle - lastAngle;
- RotationAbsolute += Math.Abs(thisAngle - lastAngle) * Math.Sign(Clock.ElapsedFrameTime);
- }
+ if (tracking)
+ Rotate(delta);
lastAngle = thisAngle;
@@ -128,5 +118,38 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Rotation = (float)Interpolation.Lerp(Rotation, currentRotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1));
}
+
+ ///
+ /// Rotate the disc by the provided angle (in addition to any existing rotation).
+ ///
+ ///
+ /// Will be a no-op if not a valid time to spin.
+ ///
+ /// The delta angle.
+ public void Rotate(float angle)
+ {
+ if (!isSpinnableTime)
+ return;
+
+ if (!rotationTransferred)
+ {
+ currentRotation = Rotation * 2;
+ rotationTransferred = true;
+ }
+
+ if (angle > 180)
+ {
+ lastAngle += 360;
+ angle -= 360;
+ }
+ else if (-angle > 180)
+ {
+ lastAngle -= 360;
+ angle += 360;
+ }
+
+ currentRotation += angle;
+ RotationAbsolute += Math.Abs(angle) * Math.Sign(Clock.ElapsedFrameTime);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs
index a8fd3764c5..ac6c6905e4 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs
@@ -34,8 +34,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public class SliderRepeatJudgement : OsuJudgement
{
- public override bool IsBonus => true;
-
protected override int NumericResultFor(HitResult result) => result == MaxResult ? 30 : 0;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
index 212a84c04a..22f3f559db 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
@@ -36,8 +36,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public class SliderTickJudgement : OsuJudgement
{
- public override bool IsBonus => true;
-
protected override int NumericResultFor(HitResult result) => result == MaxResult ? 10 : 0;
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index a2c0e051d0..a0f5b8fe01 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -113,7 +113,6 @@ namespace osu.Game.Rulesets.Osu
new OsuModEasy(),
new OsuModNoFail(),
new MultiMod(new OsuModHalfTime(), new OsuModDaycore()),
- new OsuModSpunOut(),
};
case ModType.DifficultyIncrease:
@@ -139,6 +138,7 @@ namespace osu.Game.Rulesets.Osu
new MultiMod(new OsuModAutoplay(), new OsuModCinema()),
new OsuModRelax(),
new OsuModAutopilot(),
+ new OsuModSpunOut(),
};
case ModType.Fun:
diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
index 4ea4220faf..b2cdc8ccbf 100644
--- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
@@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Osu
ApproachCircle,
ReverseArrow,
HitCircleText,
+ SliderHeadHitCircle,
SliderFollowCircle,
SliderBall,
SliderBody,
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs
index 93ae0371df..38ba4c5974 100644
--- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs
@@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Objects.Drawables;
@@ -18,8 +19,12 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
public class LegacyMainCirclePiece : CompositeDrawable
{
- public LegacyMainCirclePiece()
+ private readonly string priorityLookup;
+
+ public LegacyMainCirclePiece(string priorityLookup = null)
{
+ this.priorityLookup = priorityLookup;
+
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
}
@@ -39,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
hitCircleSprite = new Sprite
{
- Texture = skin.GetTexture("hitcircle"),
+ Texture = getTextureWithFallback(string.Empty),
Colour = drawableObject.AccentColour.Value,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -51,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
}, confineMode: ConfineMode.NoScaling),
new Sprite
{
- Texture = skin.GetTexture("hitcircleoverlay"),
+ Texture = getTextureWithFallback("overlay"),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
@@ -65,6 +70,16 @@ namespace osu.Game.Rulesets.Osu.Skinning
indexInCurrentCombo.BindTo(osuObject.IndexInCurrentComboBindable);
indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true);
+
+ Texture getTextureWithFallback(string name)
+ {
+ Texture tex = null;
+
+ if (!string.IsNullOrEmpty(priorityLookup))
+ tex = skin.GetTexture($"{priorityLookup}{name}");
+
+ return tex ?? skin.GetTexture($"hitcircle{name}");
+ }
}
private void updateState(ValueChangedEvent state)
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
index d6c3f443eb..075c536b4c 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
@@ -82,6 +82,12 @@ namespace osu.Game.Rulesets.Osu.Skinning
return null;
+ case OsuSkinComponents.SliderHeadHitCircle:
+ if (hasHitCircle.Value)
+ return new LegacyMainCirclePiece("sliderstartcircle");
+
+ return null;
+
case OsuSkinComponents.HitCircle:
if (hasHitCircle.Value)
return new LegacyMainCirclePiece();
diff --git a/osu.Game.Tests/Resources/mania-skin-duplicate.ini b/osu.Game.Tests/Resources/mania-skin-duplicate.ini
new file mode 100644
index 0000000000..2f4fa92c52
--- /dev/null
+++ b/osu.Game.Tests/Resources/mania-skin-duplicate.ini
@@ -0,0 +1,9 @@
+[Mania]
+Keys: 4
+ColumnWidth: 10,10,10,10
+HitPosition: 470
+
+[Mania]
+Keys: 4
+ColumnWidth: 20,20,20,20
+HitPosition: 460
\ No newline at end of file
diff --git a/osu.Game.Tests/Resources/mania-skin-extra-data.ini b/osu.Game.Tests/Resources/mania-skin-extra-data.ini
new file mode 100644
index 0000000000..e538b5335a
--- /dev/null
+++ b/osu.Game.Tests/Resources/mania-skin-extra-data.ini
@@ -0,0 +1,4 @@
+[Mania]
+Keys: 4
+ColumnWidth: 10,10,10,10,10,10,10
+HitPosition: 470
\ No newline at end of file
diff --git a/osu.Game.Tests/Resources/mania-skin-multiple.ini b/osu.Game.Tests/Resources/mania-skin-multiple.ini
new file mode 100644
index 0000000000..247c7738a0
--- /dev/null
+++ b/osu.Game.Tests/Resources/mania-skin-multiple.ini
@@ -0,0 +1,9 @@
+[Mania]
+Keys: 4
+ColumnWidth: 10,10,10,10
+HitPosition: 470
+
+[Mania]
+Keys: 2
+ColumnWidth: 20,20
+HitPosition: 460
\ No newline at end of file
diff --git a/osu.Game.Tests/Resources/mania-skin-single.ini b/osu.Game.Tests/Resources/mania-skin-single.ini
new file mode 100644
index 0000000000..3ae38fd75e
--- /dev/null
+++ b/osu.Game.Tests/Resources/mania-skin-single.ini
@@ -0,0 +1,4 @@
+[Mania]
+Keys: 4
+ColumnWidth: 10,10,10,10
+HitPosition: 470
\ No newline at end of file
diff --git a/osu.Game.Tests/Resources/storyboard_no_video.osu b/osu.Game.Tests/Resources/storyboard_no_video.osu
new file mode 100644
index 0000000000..25f1ff6361
--- /dev/null
+++ b/osu.Game.Tests/Resources/storyboard_no_video.osu
@@ -0,0 +1,31 @@
+osu file format v14
+
+[Events]
+//Background and Video events
+0,0,"BG.jpg",0,0
+Video,0,"video.avi"
+//Break Periods
+//Storyboard Layer 0 (Background)
+//Storyboard Layer 1 (Fail)
+//Storyboard Layer 2 (Pass)
+//Storyboard Layer 3 (Foreground)
+//Storyboard Layer 4 (Overlay)
+//Storyboard Sound Samples
+
+[TimingPoints]
+1674,333.333333333333,4,2,1,70,1,0
+1674,-100,4,2,1,70,0,0
+3340,-100,4,2,1,70,0,0
+3507,-100,4,2,1,70,0,0
+3673,-100,4,2,1,70,0,0
+
+[Colours]
+Combo1 : 240,80,80
+Combo2 : 171,252,203
+Combo3 : 128,128,255
+Combo4 : 249,254,186
+
+[HitObjects]
+148,303,1674,5,6,3:2:0:0:
+378,252,1840,1,0,0:0:0:0:
+389,270,2340,5,2,0:1:0:0:
diff --git a/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs
new file mode 100644
index 0000000000..736f97f39f
--- /dev/null
+++ b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs
@@ -0,0 +1,87 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.IO;
+using osu.Game.Skinning;
+using osu.Game.Tests.Resources;
+
+namespace osu.Game.Tests.Skins
+{
+ [TestFixture]
+ public class LegacyManiaSkinDecoderTest
+ {
+ [Test]
+ public void TestParseSingleConfig()
+ {
+ var decoder = new LegacyManiaSkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("mania-skin-single.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var configs = decoder.Decode(stream);
+
+ Assert.That(configs.Count, Is.EqualTo(1));
+ Assert.That(configs[0].Keys, Is.EqualTo(4));
+ Assert.That(configs[0].ColumnWidth, Is.EquivalentTo(new float[] { 16, 16, 16, 16 }));
+ Assert.That(configs[0].HitPosition, Is.EqualTo(16));
+ }
+ }
+
+ [Test]
+ public void TestParseMultipleConfig()
+ {
+ var decoder = new LegacyManiaSkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("mania-skin-multiple.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var configs = decoder.Decode(stream);
+
+ Assert.That(configs.Count, Is.EqualTo(2));
+
+ Assert.That(configs[0].Keys, Is.EqualTo(4));
+ Assert.That(configs[0].ColumnWidth, Is.EquivalentTo(new float[] { 16, 16, 16, 16 }));
+ Assert.That(configs[0].HitPosition, Is.EqualTo(16));
+
+ Assert.That(configs[1].Keys, Is.EqualTo(2));
+ Assert.That(configs[1].ColumnWidth, Is.EquivalentTo(new float[] { 32, 32 }));
+ Assert.That(configs[1].HitPosition, Is.EqualTo(32));
+ }
+ }
+
+ [Test]
+ public void TestParseDuplicateConfig()
+ {
+ var decoder = new LegacyManiaSkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("mania-skin-single.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var configs = decoder.Decode(stream);
+
+ Assert.That(configs.Count, Is.EqualTo(1));
+ Assert.That(configs[0].Keys, Is.EqualTo(4));
+ Assert.That(configs[0].ColumnWidth, Is.EquivalentTo(new float[] { 16, 16, 16, 16 }));
+ Assert.That(configs[0].HitPosition, Is.EqualTo(16));
+ }
+ }
+
+ [Test]
+ public void TestParseWithUnnecessaryExtraData()
+ {
+ var decoder = new LegacyManiaSkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("mania-skin-extra-data.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var configs = decoder.Decode(stream);
+
+ Assert.That(configs.Count, Is.EqualTo(1));
+ Assert.That(configs[0].Keys, Is.EqualTo(4));
+ Assert.That(configs[0].ColumnWidth, Is.EquivalentTo(new float[] { 16, 16, 16, 16 }));
+ Assert.That(configs[0].HitPosition, Is.EqualTo(16));
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs
index 83a7b896d2..b7dcad3825 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs
@@ -4,7 +4,6 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
@@ -74,9 +73,6 @@ namespace osu.Game.Tests.Visual.Gameplay
Beatmap.Value = working;
SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) };
- Player?.Exit();
- Player = null;
-
Player = CreatePlayer(ruleset);
LoadScreen(Player);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
index afeda5fb7c..4b1c2ec256 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
@@ -3,8 +3,14 @@
using System.ComponentModel;
using System.Linq;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.Break;
+using osu.Game.Screens.Ranking;
namespace osu.Game.Tests.Visual.Gameplay
{
@@ -15,20 +21,38 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Player CreatePlayer(Ruleset ruleset)
{
- SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
- return new TestPlayer(false, false);
+ SelectedMods.Value = new[] { ruleset.GetAutoplayMod() };
+ return new TestPlayer(false);
}
protected override void AddCheckSteps()
{
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2));
- AddStep("seek to break time", () => Player.GameplayClockContainer.Seek(Player.BreakOverlay.Breaks.First().StartTime));
- AddUntilStep("wait for seek to complete", () =>
- Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= Player.BreakOverlay.Breaks.First().StartTime);
- AddAssert("test keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting);
+ seekToBreak(0);
+ AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting);
+ AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType().Single().AccuracyDisplay.Current.Value == 1);
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
+
+ seekToBreak(0);
+ seekToBreak(1);
+
+ AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
+ AddUntilStep("results displayed", () => getResultsScreen() != null);
+
+ AddAssert("score has combo", () => getResultsScreen().Score.Combo > 100);
+ AddAssert("score has no misses", () => getResultsScreen().Score.Statistics[HitResult.Miss] == 0);
+
+ ResultsScreen getResultsScreen() => Stack.CurrentScreen as ResultsScreen;
+ }
+
+ private void seekToBreak(int breakIndex)
+ {
+ AddStep($"seek to break {breakIndex}", () => Player.GameplayClockContainer.Seek(destBreak().StartTime));
+ AddUntilStep("wait for seek to complete", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= destBreak().StartTime);
+
+ BreakPeriod destBreak() => Player.ChildrenOfType().First().Breaks.ElementAt(breakIndex);
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs
similarity index 80%
rename from osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs
rename to osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs
index 19dce303ea..ff25e609c1 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Graphics;
using osu.Framework.Timing;
using osu.Game.Beatmaps.Timing;
using osu.Game.Screens.Play;
@@ -12,14 +13,16 @@ using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
- public class TestSceneBreakOverlay : OsuTestScene
+ public class TestSceneBreakTracker : OsuTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
typeof(BreakOverlay),
};
- private readonly TestBreakOverlay breakOverlay;
+ private readonly BreakOverlay breakOverlay;
+
+ private readonly TestBreakTracker breakTracker;
private readonly IReadOnlyList testBreaks = new List
{
@@ -35,9 +38,23 @@ namespace osu.Game.Tests.Visual.Gameplay
},
};
- public TestSceneBreakOverlay()
+ public TestSceneBreakTracker()
{
- Add(breakOverlay = new TestBreakOverlay(true));
+ AddRange(new Drawable[]
+ {
+ breakTracker = new TestBreakTracker(),
+ breakOverlay = new BreakOverlay(true, null)
+ {
+ ProcessCustomClock = false,
+ }
+ });
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ breakOverlay.Clock = breakTracker.Clock;
}
[Test]
@@ -88,7 +105,7 @@ namespace osu.Game.Tests.Visual.Gameplay
loadBreaksStep("multiple breaks", testBreaks);
seekAndAssertBreak("seek to break start", testBreaks[1].StartTime, true);
- AddAssert("is skipped to break #2", () => breakOverlay.CurrentBreakIndex == 1);
+ AddAssert("is skipped to break #2", () => breakTracker.CurrentBreakIndex == 1);
seekAndAssertBreak("seek to break middle", testBreaks[1].StartTime + testBreaks[1].Duration / 2, true);
seekAndAssertBreak("seek to break end", testBreaks[1].EndTime, false);
@@ -110,7 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void addShowBreakStep(double seconds)
{
- AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List
+ AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = breakTracker.Breaks = new List
{
new BreakPeriod
{
@@ -122,12 +139,12 @@ namespace osu.Game.Tests.Visual.Gameplay
private void setClock(bool useManual)
{
- AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakOverlay.SwitchClock(useManual));
+ AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakTracker.SwitchClock(useManual));
}
private void loadBreaksStep(string breakDescription, IReadOnlyList breaks)
{
- AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breaks);
+ AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breakTracker.Breaks = breaks);
seekAndAssertBreak("seek back to 0", 0, false);
}
@@ -151,17 +168,18 @@ namespace osu.Game.Tests.Visual.Gameplay
private void seekAndAssertBreak(string seekStepDescription, double time, bool shouldBeBreak)
{
- AddStep(seekStepDescription, () => breakOverlay.ManualClockTime = time);
+ AddStep(seekStepDescription, () => breakTracker.ManualClockTime = time);
AddAssert($"is{(!shouldBeBreak ? " not" : string.Empty)} break time", () =>
{
- breakOverlay.ProgressTime();
- return breakOverlay.IsBreakTime.Value == shouldBeBreak;
+ breakTracker.ProgressTime();
+ return breakTracker.IsBreakTime.Value == shouldBeBreak;
});
}
- private class TestBreakOverlay : BreakOverlay
+ private class TestBreakTracker : BreakTracker
{
- private readonly FramedClock framedManualClock;
+ public readonly FramedClock FramedManualClock;
+
private readonly ManualClock manualClock;
private IFrameBasedClock originalClock;
@@ -173,20 +191,19 @@ namespace osu.Game.Tests.Visual.Gameplay
set => manualClock.CurrentTime = value;
}
- public TestBreakOverlay(bool letterboxing)
- : base(letterboxing)
+ public TestBreakTracker()
{
- framedManualClock = new FramedClock(manualClock = new ManualClock());
+ FramedManualClock = new FramedClock(manualClock = new ManualClock());
ProcessCustomClock = false;
}
public void ProgressTime()
{
- framedManualClock.ProcessFrame();
+ FramedManualClock.ProcessFrame();
Update();
}
- public void SwitchClock(bool setManual) => Clock = setManual ? framedManualClock : originalClock;
+ public void SwitchClock(bool setManual) => Clock = setManual ? FramedManualClock : originalClock;
protected override void LoadComplete()
{
diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
similarity index 99%
rename from osu.Game.Tests/Gameplay/TestSceneReplayRecorder.cs
rename to osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
index 734991b868..c7455583e4 100644
--- a/osu.Game.Tests/Gameplay/TestSceneReplayRecorder.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
@@ -17,13 +17,12 @@ using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
-using osu.Game.Tests.Visual;
using osu.Game.Tests.Visual.UserInterface;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
-namespace osu.Game.Tests.Gameplay
+namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneReplayRecorder : OsuManualInputManagerTestScene
{
diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
similarity index 99%
rename from osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs
rename to osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
index 057d026132..7822f07957 100644
--- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
@@ -13,12 +13,11 @@ using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
-using osu.Game.Tests.Visual;
using osu.Game.Tests.Visual.UserInterface;
using osuTK;
using osuTK.Graphics;
-namespace osu.Game.Tests.Gameplay
+namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneReplayRecording : OsuTestScene
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
index ff8437311e..9f1492a25f 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
@@ -9,8 +9,12 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Formats;
+using osu.Game.IO;
using osu.Game.Overlays;
+using osu.Game.Storyboards;
using osu.Game.Storyboards.Drawables;
+using osu.Game.Tests.Resources;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Gameplay
@@ -54,7 +58,11 @@ namespace osu.Game.Tests.Visual.Gameplay
State = { Value = Visibility.Visible },
}
});
+ }
+ [Test]
+ public void TestStoryboard()
+ {
AddStep("Restart", restart);
AddToggleStep("Passing", passing =>
{
@@ -62,6 +70,12 @@ namespace osu.Game.Tests.Visual.Gameplay
});
}
+ [Test]
+ public void TestStoryboardMissingVideo()
+ {
+ AddStep("Load storyboard with missing video", loadStoryboardNoVideo);
+ }
+
[BackgroundDependencyLoader]
private void load()
{
@@ -94,5 +108,28 @@ namespace osu.Game.Tests.Visual.Gameplay
storyboardContainer.Add(storyboard);
decoupledClock.ChangeSource(working.Track);
}
+
+ private void loadStoryboardNoVideo()
+ {
+ if (storyboard != null)
+ storyboardContainer.Remove(storyboard);
+
+ var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true };
+ storyboardContainer.Clock = decoupledClock;
+
+ Storyboard sb;
+
+ using (var str = TestResources.OpenResource("storyboard_no_video.osu"))
+ using (var bfr = new LineBufferedReader(str))
+ {
+ var decoder = new LegacyStoryboardDecoder();
+ sb = decoder.Decode(bfr);
+ }
+
+ storyboard = sb.CreateDrawable(Beatmap.Value);
+
+ storyboardContainer.Add(storyboard);
+ decoupledClock.ChangeSource(Beatmap.Value.Track);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs
index b3064ba9be..c44363d9ea 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs
@@ -36,8 +36,6 @@ namespace osu.Game.Tests.Visual.Menus
[Test]
public void TestInstantLoad()
{
- // visual only, very impossible to test this using asserts.
-
AddStep("load immediately", () =>
{
loader = new TestLoader();
@@ -46,12 +44,17 @@ namespace osu.Game.Tests.Visual.Menus
LoadScreen(loader);
});
- AddAssert("spinner did not display", () => loader.LoadingSpinner?.Alpha == 0);
+ spinnerNotPresentOrHidden();
AddUntilStep("loaded", () => loader.ScreenLoaded);
AddUntilStep("not current", () => !loader.IsCurrentScreen());
+
+ spinnerNotPresentOrHidden();
}
+ private void spinnerNotPresentOrHidden() =>
+ AddAssert("spinner did not display", () => loader.LoadingSpinner == null || loader.LoadingSpinner.Alpha == 0);
+
[Test]
public void TestDelayedLoad()
{
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
index 0cc37bbd57..76a8ee9914 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
@@ -83,6 +83,82 @@ namespace osu.Game.Tests.Visual.SongSelect
waitForSelection(set_count, 3);
}
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestTraversalBeyondVisible(bool forwards)
+ {
+ var sets = new List();
+
+ const int total_set_count = 200;
+
+ for (int i = 0; i < total_set_count; i++)
+ sets.Add(createTestBeatmapSet(i + 1));
+
+ loadBeatmaps(sets);
+
+ for (int i = 1; i < total_set_count; i += i)
+ selectNextAndAssert(i);
+
+ void selectNextAndAssert(int amount)
+ {
+ setSelected(forwards ? 1 : total_set_count, 1);
+
+ AddStep($"{(forwards ? "Next" : "Previous")} beatmap {amount} times", () =>
+ {
+ for (int i = 0; i < amount; i++)
+ {
+ carousel.SelectNext(forwards ? 1 : -1);
+ }
+ });
+
+ waitForSelection(forwards ? amount + 1 : total_set_count - amount);
+ }
+ }
+
+ [Test]
+ public void TestTraversalBeyondVisibleDifficulties()
+ {
+ var sets = new List();
+
+ const int total_set_count = 20;
+
+ for (int i = 0; i < total_set_count; i++)
+ sets.Add(createTestBeatmapSet(i + 1));
+
+ loadBeatmaps(sets);
+
+ // Selects next set once, difficulty index doesn't change
+ selectNextAndAssert(3, true, 2, 1);
+
+ // Selects next set 16 times (50 \ 3 == 16), difficulty index changes twice (50 % 3 == 2)
+ selectNextAndAssert(50, true, 17, 3);
+
+ // Travels around the carousel thrice (200 \ 60 == 3)
+ // continues to select 20 times (200 \ 60 == 20)
+ // selects next set 6 times (20 \ 3 == 6)
+ // difficulty index changes twice (20 % 3 == 2)
+ selectNextAndAssert(200, true, 7, 3);
+
+ // All same but in reverse
+ selectNextAndAssert(3, false, 19, 3);
+ selectNextAndAssert(50, false, 4, 1);
+ selectNextAndAssert(200, false, 14, 1);
+
+ void selectNextAndAssert(int amount, bool forwards, int expectedSet, int expectedDiff)
+ {
+ // Select very first or very last difficulty
+ setSelected(forwards ? 1 : 20, forwards ? 1 : 3);
+
+ AddStep($"{(forwards ? "Next" : "Previous")} difficulty {amount} times", () =>
+ {
+ for (int i = 0; i < amount; i++)
+ carousel.SelectNext(forwards ? 1 : -1, false);
+ });
+
+ waitForSelection(expectedSet, expectedDiff);
+ }
+ }
+
///
/// Test filtering
///
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
index d56324dbe8..03a19b6690 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
@@ -91,13 +91,14 @@ namespace osu.Game.Tests.Visual.UserInterface
var easierMods = osu.GetModsFor(ModType.DifficultyReduction);
var harderMods = osu.GetModsFor(ModType.DifficultyIncrease);
+ var conversionMods = osu.GetModsFor(ModType.Conversion);
var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail);
var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden);
var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime));
- var spunOutMod = easierMods.FirstOrDefault(m => m is OsuModSpunOut);
+ var targetMod = conversionMods.FirstOrDefault(m => m is OsuModTarget);
var easy = easierMods.FirstOrDefault(m => m is OsuModEasy);
var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock);
@@ -109,7 +110,7 @@ namespace osu.Game.Tests.Visual.UserInterface
testMultiplierTextColour(noFailMod, () => modSelect.LowMultiplierColour);
testMultiplierTextColour(hiddenMod, () => modSelect.HighMultiplierColour);
- testUnimplementedMod(spunOutMod);
+ testUnimplementedMod(targetMod);
}
[Test]
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index e28e235788..bbc0aad467 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -41,6 +41,7 @@ namespace osu.Game.Beatmaps.Formats
section = Section.None;
}
+ OnBeginNewSection(section);
continue;
}
@@ -57,6 +58,14 @@ namespace osu.Game.Beatmaps.Formats
protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.AsSpan().TrimStart().StartsWith("//".AsSpan(), StringComparison.Ordinal);
+ ///
+ /// Invoked when a new has been entered.
+ ///
+ /// The entered .
+ protected virtual void OnBeginNewSection(Section section)
+ {
+ }
+
protected virtual void ParseLine(T output, Section section, string line)
{
line = StripComments(line);
@@ -139,7 +148,8 @@ namespace osu.Game.Beatmaps.Formats
Colours,
HitObjects,
Variables,
- Fonts
+ Fonts,
+ Mania
}
internal class LegacyDifficultyControlPoint : DifficultyControlPoint
diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs
index 4485ce3447..39c1fdad52 100644
--- a/osu.Game/Graphics/Containers/UserDimContainer.cs
+++ b/osu.Game/Graphics/Containers/UserDimContainer.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Graphics.Containers
///
/// Whether player is in break time.
- /// Must be bound to to allow for dim adjustments in gameplay.
+ /// Must be bound to to allow for dim adjustments in gameplay.
///
public readonly IBindable IsBreakTime = new Bindable();
diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
index 59d39a1c3c..e7f2f21465 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
@@ -56,24 +57,32 @@ namespace osu.Game.Overlays.Settings.Sections.Input
},
};
- rawInputToggle.ValueChanged += enabled =>
+ if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows)
{
- // this is temporary until we support per-handler settings.
- const string raw_mouse_handler = @"OsuTKRawMouseHandler";
- const string standard_mouse_handler = @"OsuTKMouseHandler";
-
- ignoredInputHandler.Value = enabled.NewValue ? standard_mouse_handler : raw_mouse_handler;
- };
-
- ignoredInputHandler = config.GetBindable(FrameworkSetting.IgnoredInputHandlers);
- ignoredInputHandler.ValueChanged += handler =>
+ rawInputToggle.Disabled = true;
+ sensitivity.Bindable.Disabled = true;
+ }
+ else
{
- bool raw = !handler.NewValue.Contains("Raw");
- rawInputToggle.Value = raw;
- sensitivity.Bindable.Disabled = !raw;
- };
+ rawInputToggle.ValueChanged += enabled =>
+ {
+ // this is temporary until we support per-handler settings.
+ const string raw_mouse_handler = @"OsuTKRawMouseHandler";
+ const string standard_mouse_handler = @"OsuTKMouseHandler";
- ignoredInputHandler.TriggerChange();
+ ignoredInputHandler.Value = enabled.NewValue ? standard_mouse_handler : raw_mouse_handler;
+ };
+
+ ignoredInputHandler = config.GetBindable(FrameworkSetting.IgnoredInputHandlers);
+ ignoredInputHandler.ValueChanged += handler =>
+ {
+ bool raw = !handler.NewValue.Contains("Raw");
+ rawInputToggle.Value = raw;
+ sensitivity.Bindable.Disabled = !raw;
+ };
+
+ ignoredInputHandler.TriggerChange();
+ }
}
private class SensitivitySetting : SettingsSlider
diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs
index 46c0c1da07..0e5fe3fc9c 100644
--- a/osu.Game/Rulesets/Mods/Mod.cs
+++ b/osu.Game/Rulesets/Mods/Mod.cs
@@ -2,9 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
using Newtonsoft.Json;
+using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Configuration;
using osu.Game.IO.Serialization;
+using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mods
{
@@ -42,6 +48,51 @@ namespace osu.Game.Rulesets.Mods
[JsonIgnore]
public virtual string Description => string.Empty;
+ ///
+ /// The tooltip to display for this mod when used in a .
+ ///
+ ///
+ /// Differs from , as the value of attributes (AR, CS, etc) changeable via the mod
+ /// are displayed in the tooltip.
+ ///
+ [JsonIgnore]
+ public string IconTooltip
+ {
+ get
+ {
+ string description = SettingDescription;
+
+ return string.IsNullOrEmpty(description) ? Name : $"{Name} ({description})";
+ }
+ }
+
+ ///
+ /// The description of editable settings of a mod to use in the .
+ ///
+ ///
+ /// Parentheses are added to the tooltip, surrounding the value of this property. If this property is string.Empty,
+ /// the tooltip will not have parentheses.
+ ///
+ public virtual string SettingDescription
+ {
+ get
+ {
+ var tooltipTexts = new List();
+
+ foreach ((SettingSourceAttribute attr, PropertyInfo property) in this.GetOrderedSettingsSourceProperties())
+ {
+ object bindableObj = property.GetValue(this);
+
+ if ((bindableObj as IHasDefaultValue)?.IsDefault == true)
+ continue;
+
+ tooltipTexts.Add($"{attr.Label} {bindableObj}");
+ }
+
+ return string.Join(", ", tooltipTexts.Where(s => !string.IsNullOrEmpty(s)));
+ }
+ }
+
///
/// The score multiplier of this mod.
///
diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
index 2083671072..c3a8efdd66 100644
--- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
+++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
@@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites;
using System;
using System.Collections.Generic;
using osu.Game.Configuration;
+using System.Linq;
namespace osu.Game.Rulesets.Mods
{
@@ -52,6 +53,21 @@ namespace osu.Game.Rulesets.Mods
Value = 5,
};
+ public override string SettingDescription
+ {
+ get
+ {
+ string drainRate = DrainRate.IsDefault ? string.Empty : $"HP {DrainRate.Value:N1}";
+ string overallDifficulty = OverallDifficulty.IsDefault ? string.Empty : $"OD {OverallDifficulty.Value:N1}";
+
+ return string.Join(", ", new[]
+ {
+ drainRate,
+ overallDifficulty
+ }.Where(s => !string.IsNullOrEmpty(s)));
+ }
+ }
+
private BeatmapDifficulty difficulty;
public void ReadFromDifficulty(BeatmapDifficulty difficulty)
@@ -79,7 +95,7 @@ namespace osu.Game.Rulesets.Mods
///
/// Transfer a setting from to a configuration bindable.
- /// Only performs the transfer if the user it not currently overriding..
+ /// Only performs the transfer if the user is not currently overriding.
///
protected void TransferSetting(BindableNumber bindable, T beatmapDefault)
where T : struct, IComparable, IConvertible, IEquatable
diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs
index b56be95dfe..c1c4124b98 100644
--- a/osu.Game/Rulesets/Mods/ModEasy.cs
+++ b/osu.Game/Rulesets/Mods/ModEasy.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using Humanizer;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
@@ -28,6 +29,8 @@ namespace osu.Game.Rulesets.Mods
MaxValue = 10
};
+ public override string SettingDescription => Retries.IsDefault ? string.Empty : $"{"lives".ToQuantity(Retries.Value)}";
+
private int retries;
private BindableNumber health;
diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs
index 1739524bcd..cb2ff149f1 100644
--- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs
+++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs
@@ -15,5 +15,7 @@ namespace osu.Game.Rulesets.Mods
{
track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange);
}
+
+ public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x";
}
}
diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
index 9e63142b42..c1f3e357a1 100644
--- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs
+++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
@@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Mods
[SettingSource("Final rate", "The final speed to ramp to")]
public abstract BindableNumber FinalRate { get; }
+ public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x";
+
private double finalRateTime;
private double beginRampTime;
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index aa29e42fac..0011faefbb 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -38,6 +38,15 @@ namespace osu.Game.Rulesets.Objects.Drawables
private readonly Lazy> nestedHitObjects = new Lazy>();
public IReadOnlyList NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : (IReadOnlyList)Array.Empty();
+ ///
+ /// Whether this object should handle any user input events.
+ ///
+ public bool HandleUserInput { get; set; } = true;
+
+ public override bool PropagatePositionalInputSubTree => HandleUserInput;
+
+ public override bool PropagateNonPositionalInputSubTree => HandleUserInput;
+
///
/// Invoked when a has been applied by this or a nested .
///
@@ -344,7 +353,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// Plays all the hit sounds for this .
/// This is invoked automatically when this is hit.
///
- public void PlaySamples() => Samples?.Play();
+ public virtual void PlaySamples() => Samples?.Play();
protected override void Update()
{
diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs
index 27993ff173..5062c92afe 100644
--- a/osu.Game/Rulesets/UI/DrawableRuleset.cs
+++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs
@@ -72,9 +72,9 @@ namespace osu.Game.Rulesets.UI
///
public override Playfield Playfield => playfield.Value;
- private Container overlays;
+ public override Container Overlays { get; } = new Container { RelativeSizeAxes = Axes.Both };
- public override Container Overlays => overlays;
+ public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both };
public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock;
@@ -187,11 +187,12 @@ namespace osu.Game.Rulesets.UI
FrameStablePlayback = FrameStablePlayback,
Children = new Drawable[]
{
+ FrameStableComponents,
KeyBindingInputManager
.WithChild(CreatePlayfieldAdjustmentContainer()
.WithChild(Playfield)
),
- overlays = new Container { RelativeSizeAxes = Axes.Both }
+ Overlays,
}
},
};
@@ -406,10 +407,15 @@ namespace osu.Game.Rulesets.UI
public abstract Playfield Playfield { get; }
///
- /// Place to put drawables above hit objects but below UI.
+ /// Content to be placed above hitobjects. Will be affected by frame stability.
///
public abstract Container Overlays { get; }
+ ///
+ /// Components to be run potentially multiple times in line with frame-stable gameplay.
+ ///
+ public abstract Container FrameStableComponents { get; }
+
///
/// The frame-stable clock which is being used for playfield display.
///
diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
index e569bb8459..3ba28aad45 100644
--- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
+++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.UI
{
///
/// A container which consumes a parent gameplay clock and standardises frame counts for children.
- /// Will ensure a minimum of 40 frames per clock second is maintained, regardless of any system lag or seeks.
+ /// Will ensure a minimum of 50 frames per clock second is maintained, regardless of any system lag or seeks.
///
public class FrameStabilityContainer : Container, IHasReplayHandler
{
diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs
index 3edab0745d..8ea6c74349 100644
--- a/osu.Game/Rulesets/UI/ModIcon.cs
+++ b/osu.Game/Rulesets/UI/ModIcon.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.UI
private readonly ModType type;
- public virtual string TooltipText { get; }
+ public virtual string TooltipText => mod.IconTooltip;
private Mod mod;
@@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.UI
type = mod.Type;
- TooltipText = mod.Name;
-
Size = new Vector2(size);
Children = new Drawable[]
diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
index 58b64e1b8f..c356dd246d 100644
--- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
+++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
@@ -45,9 +45,6 @@ namespace osu.Game.Scoring.Legacy
if (workingBeatmap is DummyWorkingBeatmap)
throw new BeatmapNotFoundException();
- currentBeatmap = workingBeatmap.Beatmap;
- scoreInfo.Beatmap = currentBeatmap.BeatmapInfo;
-
scoreInfo.User = new User { Username = sr.ReadString() };
// MD5Hash
@@ -68,6 +65,9 @@ namespace osu.Game.Scoring.Legacy
scoreInfo.Mods = currentRuleset.ConvertFromLegacyMods((LegacyMods)sr.ReadInt32()).ToArray();
+ currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods);
+ scoreInfo.Beatmap = currentBeatmap.BeatmapInfo;
+
/* score.HpGraphString = */
sr.ReadString();
diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs
index ee8be87352..c978f4e96d 100644
--- a/osu.Game/Screens/Play/BreakOverlay.cs
+++ b/osu.Game/Screens/Play/BreakOverlay.cs
@@ -2,8 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -16,8 +14,6 @@ namespace osu.Game.Screens.Play
{
public class BreakOverlay : Container
{
- private readonly ScoreProcessor scoreProcessor;
-
///
/// The duration of the break overlay fading.
///
@@ -37,10 +33,6 @@ namespace osu.Game.Screens.Play
{
breaks = value;
- // reset index in case the new breaks list is smaller than last one
- isBreakTime.Value = false;
- CurrentBreakIndex = 0;
-
if (IsLoaded)
initializeBreaks();
}
@@ -48,27 +40,17 @@ namespace osu.Game.Screens.Play
public override bool RemoveCompletedTransforms => false;
- ///
- /// Whether the gameplay is currently in a break.
- ///
- public IBindable IsBreakTime => isBreakTime;
-
- protected int CurrentBreakIndex;
-
- private readonly BindableBool isBreakTime = new BindableBool();
-
private readonly Container remainingTimeAdjustmentBox;
private readonly Container remainingTimeBox;
private readonly RemainingTimeCounter remainingTimeCounter;
- private readonly BreakInfo info;
private readonly BreakArrows breakArrows;
- private readonly double gameplayStartTime;
- public BreakOverlay(bool letterboxing, double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null)
+ public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor)
{
- this.gameplayStartTime = gameplayStartTime;
- this.scoreProcessor = scoreProcessor;
RelativeSizeAxes = Axes.Both;
+
+ BreakInfo info;
+
Child = fadeContainer = new Container
{
Alpha = 0,
@@ -119,13 +101,11 @@ namespace osu.Game.Screens.Play
}
};
- if (scoreProcessor != null) bindProcessor(scoreProcessor);
- }
-
- [BackgroundDependencyLoader(true)]
- private void load(GameplayClock clock)
- {
- if (clock != null) Clock = clock;
+ if (scoreProcessor != null)
+ {
+ info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy);
+ info.GradeDisplay.Current.BindTo(scoreProcessor.Rank);
+ }
}
protected override void LoadComplete()
@@ -134,42 +114,6 @@ namespace osu.Game.Screens.Play
initializeBreaks();
}
- protected override void Update()
- {
- base.Update();
- updateBreakTimeBindable();
- }
-
- private void updateBreakTimeBindable() =>
- isBreakTime.Value = getCurrentBreak()?.HasEffect == true
- || Clock.CurrentTime < gameplayStartTime
- || scoreProcessor?.HasCompleted == true;
-
- private BreakPeriod getCurrentBreak()
- {
- if (breaks?.Count > 0)
- {
- var time = Clock.CurrentTime;
-
- if (time > breaks[CurrentBreakIndex].EndTime)
- {
- while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1)
- CurrentBreakIndex++;
- }
- else
- {
- while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0)
- CurrentBreakIndex--;
- }
-
- var closest = breaks[CurrentBreakIndex];
-
- return closest.Contains(time) ? closest : null;
- }
-
- return null;
- }
-
private void initializeBreaks()
{
FinishTransforms(true);
@@ -207,11 +151,5 @@ namespace osu.Game.Screens.Play
}
}
}
-
- private void bindProcessor(ScoreProcessor processor)
- {
- info.AccuracyDisplay.Current.BindTo(processor.Accuracy);
- info.GradeDisplay.Current.BindTo(processor.Rank);
- }
}
}
diff --git a/osu.Game/Screens/Play/BreakTracker.cs b/osu.Game/Screens/Play/BreakTracker.cs
new file mode 100644
index 0000000000..64262d52b5
--- /dev/null
+++ b/osu.Game/Screens/Play/BreakTracker.cs
@@ -0,0 +1,82 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Screens.Play
+{
+ public class BreakTracker : Component
+ {
+ private readonly ScoreProcessor scoreProcessor;
+
+ private readonly double gameplayStartTime;
+
+ ///
+ /// Whether the gameplay is currently in a break.
+ ///
+ public IBindable IsBreakTime => isBreakTime;
+
+ protected int CurrentBreakIndex;
+
+ private readonly BindableBool isBreakTime = new BindableBool();
+
+ private IReadOnlyList breaks;
+
+ public IReadOnlyList Breaks
+ {
+ get => breaks;
+ set
+ {
+ breaks = value;
+
+ // reset index in case the new breaks list is smaller than last one
+ isBreakTime.Value = false;
+ CurrentBreakIndex = 0;
+ }
+ }
+
+ public BreakTracker(double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null)
+ {
+ this.gameplayStartTime = gameplayStartTime;
+ this.scoreProcessor = scoreProcessor;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ isBreakTime.Value = getCurrentBreak()?.HasEffect == true
+ || Clock.CurrentTime < gameplayStartTime
+ || scoreProcessor?.HasCompleted == true;
+ }
+
+ private BreakPeriod getCurrentBreak()
+ {
+ if (breaks?.Count > 0)
+ {
+ var time = Clock.CurrentTime;
+
+ if (time > breaks[CurrentBreakIndex].EndTime)
+ {
+ while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1)
+ CurrentBreakIndex++;
+ }
+ else
+ {
+ while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0)
+ CurrentBreakIndex--;
+ }
+
+ var closest = breaks[CurrentBreakIndex];
+
+ return closest.Contains(time) ? closest : null;
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index cdca81df5e..54612a7e6d 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -77,6 +77,8 @@ namespace osu.Game.Screens.Play
public BreakOverlay BreakOverlay;
+ private BreakTracker breakTracker;
+
protected ScoreProcessor ScoreProcessor { get; private set; }
protected HealthProcessor HealthProcessor { get; private set; }
@@ -208,7 +210,7 @@ namespace osu.Game.Screens.Play
foreach (var overlay in DrawableRuleset.Overlays.OfType())
overlay.BindHealthProcessor(HealthProcessor);
- BreakOverlay.IsBreakTime.BindValueChanged(onBreakTimeChanged, true);
+ breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true);
}
private void addUnderlayComponents(Container target)
@@ -235,12 +237,30 @@ namespace osu.Game.Screens.Play
DrawableRuleset,
new ComboEffects(ScoreProcessor)
});
+
+ DrawableRuleset.FrameStableComponents.AddRange(new Drawable[]
+ {
+ ScoreProcessor,
+ HealthProcessor,
+ breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor)
+ {
+ Breaks = working.Beatmap.Breaks
+ }
+ });
+
+ HealthProcessor.IsBreakTime.BindTo(breakTracker.IsBreakTime);
}
private void addOverlayComponents(Container target, WorkingBeatmap working)
{
target.AddRange(new[]
{
+ BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
+ {
+ Clock = DrawableRuleset.FrameStableClock,
+ ProcessCustomClock = false,
+ Breaks = working.Beatmap.Breaks
+ },
// display the cursor above some HUD elements.
DrawableRuleset.Cursor?.CreateProxy() ?? new Container(),
DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(),
@@ -297,20 +317,8 @@ namespace osu.Game.Screens.Play
performImmediateExit();
},
},
- failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }
+ failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, },
});
-
- DrawableRuleset.Overlays.Add(BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Breaks = working.Beatmap.Breaks
- });
-
- DrawableRuleset.Overlays.Add(ScoreProcessor);
- DrawableRuleset.Overlays.Add(HealthProcessor);
-
- HealthProcessor.IsBreakTime.BindTo(BreakOverlay.IsBreakTime);
}
private void onBreakTimeChanged(ValueChangedEvent isBreakTime)
@@ -322,7 +330,7 @@ namespace osu.Game.Screens.Play
private void updatePauseOnFocusLostState() =>
HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost
&& !DrawableRuleset.HasReplayLoaded.Value
- && !BreakOverlay.IsBreakTime.Value;
+ && !breakTracker.IsBreakTime.Value;
private IBeatmap loadPlayableBeatmap()
{
@@ -544,7 +552,7 @@ namespace osu.Game.Screens.Play
PauseOverlay.Hide();
// breaks and time-based conditions may allow instant resume.
- if (BreakOverlay.IsBreakTime.Value)
+ if (breakTracker.IsBreakTime.Value)
completeResume();
else
DrawableRuleset.RequestResume(completeResume);
@@ -578,8 +586,8 @@ namespace osu.Game.Screens.Play
Background.BlurAmount.Value = 0;
// bind component bindables.
- Background.IsBreakTime.BindTo(BreakOverlay.IsBreakTime);
- DimmableStoryboard.IsBreakTime.BindTo(BreakOverlay.IsBreakTime);
+ Background.IsBreakTime.BindTo(breakTracker.IsBreakTime);
+ DimmableStoryboard.IsBreakTime.BindTo(breakTracker.IsBreakTime);
Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
@@ -633,6 +641,39 @@ namespace osu.Game.Screens.Play
return base.OnExiting(next);
}
+ protected virtual void GotoRanking()
+ {
+ if (DrawableRuleset.ReplayScore != null)
+ {
+ // if a replay is present, we likely don't want to import into the local database.
+ this.Push(CreateResults(CreateScore()));
+ return;
+ }
+
+ LegacyByteArrayReader replayReader = null;
+
+ var score = new Score { ScoreInfo = CreateScore() };
+
+ if (recordingReplay?.Frames.Count > 0)
+ {
+ score.Replay = recordingReplay;
+
+ using (var stream = new MemoryStream())
+ {
+ new LegacyScoreEncoder(score, gameplayBeatmap.PlayableBeatmap).Encode(stream);
+ replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr");
+ }
+ }
+
+ scoreManager.Import(score.ScoreInfo, replayReader)
+ .ContinueWith(imported => Schedule(() =>
+ {
+ // screen may be in the exiting transition phase.
+ if (this.IsCurrentScreen())
+ this.Push(CreateResults(imported.Result));
+ }));
+ }
+
private void fadeOut(bool instant = false)
{
float fadeOutDuration = instant ? 0 : 250;
@@ -645,36 +686,7 @@ namespace osu.Game.Screens.Play
private void scheduleGotoRanking()
{
completionProgressDelegate?.Cancel();
- completionProgressDelegate = Schedule(delegate
- {
- if (DrawableRuleset.ReplayScore != null)
- this.Push(CreateResults(DrawableRuleset.ReplayScore.ScoreInfo));
- else
- {
- var score = new Score { ScoreInfo = CreateScore() };
-
- LegacyByteArrayReader replayReader = null;
-
- if (recordingReplay?.Frames.Count > 0)
- {
- score.Replay = recordingReplay;
-
- using (var stream = new MemoryStream())
- {
- new LegacyScoreEncoder(score, gameplayBeatmap).Encode(stream);
- replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr");
- }
- }
-
- scoreManager.Import(score.ScoreInfo, replayReader)
- .ContinueWith(imported => Schedule(() =>
- {
- // screen may be in the exiting transition phase.
- if (this.IsCurrentScreen())
- this.Push(CreateResults(imported.Result));
- }));
- }
- });
+ completionProgressDelegate = Schedule(GotoRanking);
}
#endregion
diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs
index 8708b5f634..74c853340d 100644
--- a/osu.Game/Screens/Play/ReplayPlayer.cs
+++ b/osu.Game/Screens/Play/ReplayPlayer.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Screens;
using osu.Game.Scoring;
namespace osu.Game.Screens.Play
@@ -23,6 +24,11 @@ namespace osu.Game.Screens.Play
DrawableRuleset?.SetReplayScore(score);
}
+ protected override void GotoRanking()
+ {
+ this.Push(CreateResults(DrawableRuleset.ReplayScore.ScoreInfo));
+ }
+
protected override ScoreInfo CreateScore() => score.ScoreInfo;
}
}
diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs
index 803b33a998..d063d8749f 100644
--- a/osu.Game/Screens/Ranking/ResultsScreen.cs
+++ b/osu.Game/Screens/Ranking/ResultsScreen.cs
@@ -31,13 +31,13 @@ namespace osu.Game.Screens.Ranking
[Resolved(CanBeNull = true)]
private Player player { get; set; }
- private readonly ScoreInfo score;
+ public readonly ScoreInfo Score;
private Drawable bottomPanel;
public ResultsScreen(ScoreInfo score)
{
- this.score = score;
+ Score = score;
}
[BackgroundDependencyLoader]
@@ -47,7 +47,7 @@ namespace osu.Game.Screens.Ranking
{
new ResultsScrollContainer
{
- Child = new ScorePanel(score)
+ Child = new ScorePanel(Score)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -77,7 +77,7 @@ namespace osu.Game.Screens.Ranking
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
- new ReplayDownloadButton(score) { Width = 300 },
+ new ReplayDownloadButton(Score) { Width = 300 },
new RetryButton { Width = 300 },
}
}
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index fa8974f55a..59dddc2baa 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -253,46 +253,37 @@ namespace osu.Game.Screens.Select
/// Whether to skip individual difficulties and only increment over full groups.
public void SelectNext(int direction = 1, bool skipDifficulties = true)
{
- var visibleItems = Items.Where(s => !s.Item.Filtered.Value).ToList();
-
- if (!visibleItems.Any())
+ if (beatmapSets.All(s => s.Filtered.Value))
return;
- DrawableCarouselItem drawable = null;
+ if (skipDifficulties)
+ selectNextSet(direction, true);
+ else
+ selectNextDifficulty(direction);
+ }
- if (selectedBeatmap != null && (drawable = selectedBeatmap.Drawables.FirstOrDefault()) == null)
- // if the selected beatmap isn't present yet, we can't correctly change selection.
- // we can fix this by changing this method to not reference drawables / Items in the first place.
- return;
+ private void selectNextSet(int direction, bool skipDifficulties)
+ {
+ var unfilteredSets = beatmapSets.Where(s => !s.Filtered.Value).ToList();
- int originalIndex = visibleItems.IndexOf(drawable);
- int currentIndex = originalIndex;
+ var nextSet = unfilteredSets[(unfilteredSets.IndexOf(selectedBeatmapSet) + direction + unfilteredSets.Count) % unfilteredSets.Count];
- // local function to increment the index in the required direction, wrapping over extremities.
- int incrementIndex() => currentIndex = (currentIndex + direction + visibleItems.Count) % visibleItems.Count;
+ if (skipDifficulties)
+ select(nextSet);
+ else
+ select(direction > 0 ? nextSet.Beatmaps.First(b => !b.Filtered.Value) : nextSet.Beatmaps.Last(b => !b.Filtered.Value));
+ }
- while (incrementIndex() != originalIndex)
- {
- var item = visibleItems[currentIndex].Item;
+ private void selectNextDifficulty(int direction)
+ {
+ var unfilteredDifficulties = selectedBeatmapSet.Children.Where(s => !s.Filtered.Value).ToList();
- if (item.Filtered.Value || item.State.Value == CarouselItemState.Selected) continue;
+ int index = unfilteredDifficulties.IndexOf(selectedBeatmap);
- switch (item)
- {
- case CarouselBeatmap beatmap:
- if (skipDifficulties) continue;
-
- select(beatmap);
- return;
-
- case CarouselBeatmapSet set:
- if (skipDifficulties)
- select(set);
- else
- select(direction > 0 ? set.Beatmaps.First(b => !b.Filtered.Value) : set.Beatmaps.Last(b => !b.Filtered.Value));
- return;
- }
- }
+ if (index + direction < 0 || index + direction >= unfilteredDifficulties.Count)
+ selectNextSet(direction, false);
+ else
+ select(unfilteredDifficulties[index + direction]);
}
///
diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
new file mode 100644
index 0000000000..5dd185879b
--- /dev/null
+++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
@@ -0,0 +1,30 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+
+namespace osu.Game.Skinning
+{
+ public class LegacyManiaSkinConfiguration
+ {
+ public readonly int Keys;
+
+ public readonly float[] ColumnLineWidth;
+ public readonly float[] ColumnSpacing;
+ public readonly float[] ColumnWidth;
+
+ public float HitPosition = 124.8f; // (480 - 402) * 1.6f
+
+ public LegacyManiaSkinConfiguration(int keys)
+ {
+ Keys = keys;
+
+ ColumnLineWidth = new float[keys + 1];
+ ColumnSpacing = new float[keys - 1];
+ ColumnWidth = new float[keys];
+
+ ColumnLineWidth.AsSpan().Fill(2);
+ ColumnWidth.AsSpan().Fill(48);
+ }
+ }
+}
diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
new file mode 100644
index 0000000000..ae6c8eeb15
--- /dev/null
+++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
@@ -0,0 +1,110 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using osu.Game.Beatmaps.Formats;
+
+namespace osu.Game.Skinning
+{
+ public class LegacyManiaSkinDecoder : LegacyDecoder>
+ {
+ private const float size_scale_factor = 1.6f;
+
+ public LegacyManiaSkinDecoder()
+ : base(1)
+ {
+ }
+
+ private readonly List pendingLines = new List();
+ private LegacyManiaSkinConfiguration currentConfig;
+
+ protected override void OnBeginNewSection(Section section)
+ {
+ base.OnBeginNewSection(section);
+
+ // If a new section is reached with pending lines remaining, they can all be discarded as there isn't a valid configuration to parse them into.
+ pendingLines.Clear();
+ currentConfig = null;
+ }
+
+ protected override void ParseLine(List output, Section section, string line)
+ {
+ line = StripComments(line);
+
+ switch (section)
+ {
+ case Section.Mania:
+ var pair = SplitKeyVal(line);
+
+ switch (pair.Key)
+ {
+ case "Keys":
+ currentConfig = new LegacyManiaSkinConfiguration(int.Parse(pair.Value, CultureInfo.InvariantCulture));
+
+ // Silently ignore duplicate configurations.
+ if (output.All(c => c.Keys != currentConfig.Keys))
+ output.Add(currentConfig);
+
+ // All existing lines can be flushed now that we have a valid configuration.
+ flushPendingLines();
+ break;
+
+ default:
+ pendingLines.Add(line);
+
+ // Hold all lines until a "Keys" item is found.
+ if (currentConfig != null)
+ flushPendingLines();
+ break;
+ }
+
+ break;
+ }
+ }
+
+ private void flushPendingLines()
+ {
+ Debug.Assert(currentConfig != null);
+
+ foreach (var line in pendingLines)
+ {
+ var pair = SplitKeyVal(line);
+
+ switch (pair.Key)
+ {
+ case "ColumnLineWidth":
+ parseArrayValue(pair.Value, currentConfig.ColumnLineWidth);
+ break;
+
+ case "ColumnSpacing":
+ parseArrayValue(pair.Value, currentConfig.ColumnSpacing);
+ break;
+
+ case "ColumnWidth":
+ parseArrayValue(pair.Value, currentConfig.ColumnWidth);
+ break;
+
+ case "HitPosition":
+ currentConfig.HitPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * size_scale_factor;
+ break;
+ }
+ }
+ }
+
+ private void parseArrayValue(string value, float[] output)
+ {
+ string[] values = value.Split(',');
+
+ for (int i = 0; i < values.Length; i++)
+ {
+ if (i >= output.Length)
+ break;
+
+ output[i] = float.Parse(values[i], CultureInfo.InvariantCulture) * size_scale_factor;
+ }
+ }
+ }
+}
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs
index 00df388d09..d4dbdf1ea8 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs
@@ -55,6 +55,8 @@ namespace osu.Game.Storyboards.Drawables
{
base.LoadComplete();
+ if (videoSprite == null) return;
+
using (videoSprite.BeginAbsoluteSequence(0))
videoSprite.FadeIn(500);
}
diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs
index 0da3ae7f87..64f4d7b95b 100644
--- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs
@@ -26,16 +26,16 @@ namespace osu.Game.Tests.Visual
protected OsuManualInputManagerTestScene()
{
+ MenuCursorContainer cursorContainer;
+
base.Content.AddRange(new Drawable[]
{
InputManager = new ManualInputManager
{
UseParentInput = true,
Child = new GlobalActionContainer(null)
- {
- RelativeSizeAxes = Axes.Both,
- Child = content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }
- },
+ .WithChild((cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both })
+ .WithChild(content = new OsuTooltipContainer(cursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }))
},
new Container
{
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 3894c06994..781c566b5f 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -23,7 +23,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 9cc9792ecf..a2c6106931 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -71,7 +71,7 @@
-
+
@@ -79,7 +79,7 @@
-
+