diff --git a/osu.Android.props b/osu.Android.props
index eaedcb7bc3..2a08cb7867 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index 466cbdaf8d..33fdcdaf1e 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.Tests
public float Position
{
- get => HitObject?.X ?? position;
+ get => HitObject?.EffectiveX ?? position;
set => position = value;
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
index 31c285ef22..1cbfa6338e 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private void attemptCatch(Fruit fruit)
{
- fruit.X += catcher.X;
+ fruit.X = fruit.OriginalX + catcher.X;
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty
{
CircleSize = circleSize
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index 00ce9ea8c2..fac5d03833 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
case JuiceStream juiceStream:
// Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead.
- lastPosition = juiceStream.X + juiceStream.Path.ControlPoints[^1].Position.Value.X;
+ lastPosition = juiceStream.OriginalX + juiceStream.Path.ControlPoints[^1].Position.Value.X;
// Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead.
lastStartTime = juiceStream.StartTime;
@@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
catchObject.XOffset = 0;
if (catchObject is TinyDroplet)
- catchObject.XOffset = Math.Clamp(rng.Next(-20, 20), -catchObject.X, CatchPlayfield.WIDTH - catchObject.X);
+ catchObject.XOffset = Math.Clamp(rng.Next(-20, 20), -catchObject.OriginalX, CatchPlayfield.WIDTH - catchObject.OriginalX);
else if (catchObject is Droplet)
rng.Next(); // osu!stable retrieved a random droplet rotation
}
@@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng)
{
- float offsetPosition = hitObject.X;
+ float offsetPosition = hitObject.OriginalX;
double startTime = hitObject.StartTime;
if (lastPosition == null)
@@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
if (positionDiff == 0)
{
applyRandomOffset(ref offsetPosition, timeDiff / 4d, rng);
- hitObject.XOffset = offsetPosition - hitObject.X;
+ hitObject.XOffset = offsetPosition - hitObject.OriginalX;
return;
}
@@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
if (Math.Abs(positionDiff) < timeDiff / 3)
applyOffset(ref offsetPosition, positionDiff);
- hitObject.XOffset = offsetPosition - hitObject.X;
+ hitObject.XOffset = offsetPosition - hitObject.OriginalX;
lastPosition = offsetPosition;
lastStartTime = startTime;
@@ -230,9 +230,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
currentObject.HyperDashTarget = null;
currentObject.DistanceToHyperDash = 0;
- int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
+ int thisDirection = nextObject.EffectiveX > currentObject.EffectiveX ? 1 : -1;
double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable
- double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
+ double distanceToNext = Math.Abs(nextObject.EffectiveX - currentObject.EffectiveX) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
float distanceToHyper = (float)(timeToNext * Catcher.BASE_SPEED - distanceToNext);
if (distanceToHyper < 0)
diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs
index dcd410e08f..d936ef97ac 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs
@@ -32,8 +32,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
var scalingFactor = normalized_hitobject_radius / halfCatcherWidth;
- NormalizedPosition = BaseObject.X * scalingFactor;
- LastNormalizedPosition = LastObject.X * scalingFactor;
+ NormalizedPosition = BaseObject.EffectiveX * scalingFactor;
+ LastNormalizedPosition = LastObject.EffectiveX * scalingFactor;
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
StrainTime = Math.Max(40, DeltaTime);
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index b86b3a7496..ae45182960 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -4,7 +4,6 @@
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -16,38 +15,46 @@ namespace osu.Game.Rulesets.Catch.Objects
{
public const float OBJECT_RADIUS = 64;
- // This value is after XOffset applied.
- public readonly Bindable XBindable = new Bindable();
-
- // This value is before XOffset applied.
- private float originalX;
+ public readonly Bindable OriginalXBindable = new Bindable();
///
- /// The horizontal position of the fruit between 0 and .
+ /// The horizontal position of the hit object between 0 and .
///
public float X
{
- // TODO: I don't like this asymmetry.
- get => XBindable.Value;
- // originalX is set by `XBindable.BindValueChanged`
- set => XBindable.Value = value + xOffset;
+ set => OriginalXBindable.Value = value;
}
- private float xOffset;
+ float IHasXPosition.X => OriginalXBindable.Value;
+
+ public readonly Bindable XOffsetBindable = new Bindable();
///
- /// A random offset applied to , set by the .
+ /// A random offset applied to the horizontal position, set by the beatmap processing.
///
- internal float XOffset
+ public float XOffset
{
- get => xOffset;
- set
- {
- xOffset = value;
- XBindable.Value = originalX + xOffset;
- }
+ set => XOffsetBindable.Value = value;
}
+ ///
+ /// The horizontal position of the hit object between 0 and .
+ ///
+ ///
+ /// This value is the original value specified in the beatmap, not affected by the beatmap processing.
+ /// Use for a gameplay.
+ ///
+ public float OriginalX => OriginalXBindable.Value;
+
+ ///
+ /// The effective horizontal position of the hit object between 0 and .
+ ///
+ ///
+ /// This value is the original value plus the offset applied by the beatmap processing.
+ /// Use if a value not affected by the offset is desired.
+ ///
+ public float EffectiveX => OriginalXBindable.Value + XOffsetBindable.Value;
+
public double TimePreempt = 1000;
public readonly Bindable IndexInBeatmapBindable = new Bindable();
@@ -113,10 +120,5 @@ namespace osu.Game.Rulesets.Catch.Objects
}
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
-
- protected CatchHitObject()
- {
- XBindable.BindValueChanged(x => originalX = x.NewValue - xOffset);
- }
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
index bfd124c691..0c065948ef 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
@@ -15,11 +15,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public abstract class DrawableCatchHitObject : DrawableHitObject
{
- public readonly Bindable XBindable = new Bindable();
+ public readonly Bindable OriginalXBindable = new Bindable();
+ public readonly Bindable XOffsetBindable = new Bindable();
protected override double InitialLifetimeOffset => HitObject.TimePreempt;
- protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
+ protected override float SamplePlaybackPosition => HitObject.EffectiveX / CatchPlayfield.WIDTH;
public int RandomSeed => HitObject?.RandomSeed ?? 0;
@@ -38,14 +39,16 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
base.OnApply();
- XBindable.BindTo(HitObject.XBindable);
+ OriginalXBindable.BindTo(HitObject.OriginalXBindable);
+ XOffsetBindable.BindTo(HitObject.XOffsetBindable);
}
protected override void OnFree()
{
base.OnFree();
- XBindable.UnbindFrom(HitObject.XBindable);
+ OriginalXBindable.UnbindFrom(HitObject.OriginalXBindable);
+ XOffsetBindable.UnbindFrom(HitObject.XOffsetBindable);
}
public Func CheckPosition;
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs
index 7df06bd92d..27cd7ed2bc 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs
@@ -55,10 +55,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
[BackgroundDependencyLoader]
private void load()
{
- XBindable.BindValueChanged(x =>
- {
- X = x.NewValue;
- }, true);
+ OriginalXBindable.BindValueChanged(updateXPosition);
+ XOffsetBindable.BindValueChanged(updateXPosition, true);
ScaleBindable.BindValueChanged(scale =>
{
@@ -69,6 +67,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
IndexInBeatmap.BindValueChanged(_ => UpdateComboColour());
}
+ private void updateXPosition(ValueChangedEvent _)
+ {
+ X = OriginalXBindable.Value + XOffsetBindable.Value;
+ }
+
protected override void OnApply()
{
base.OnApply();
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index d5819935ad..35fd58826e 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new TinyDroplet
{
StartTime = t + lastEvent.Value.Time,
- X = X + Path.PositionAt(
+ X = OriginalX + Path.PositionAt(
lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X,
});
}
@@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Catch.Objects
{
Samples = dropletSamples,
StartTime = e.Time,
- X = X + Path.PositionAt(e.PathProgress).X,
+ X = OriginalX + Path.PositionAt(e.PathProgress).X,
});
break;
@@ -104,14 +104,14 @@ namespace osu.Game.Rulesets.Catch.Objects
{
Samples = this.GetNodeSamples(nodeIndex++),
StartTime = e.Time,
- X = X + Path.PositionAt(e.PathProgress).X,
+ X = OriginalX + Path.PositionAt(e.PathProgress).X,
});
break;
}
}
}
- public float EndX => X + this.CurvePositionAt(1).X;
+ public float EndX => OriginalX + this.CurvePositionAt(1).X;
public double Duration
{
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
index dfc81ee8d9..32e8ab5da7 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Replays
void moveToNext(PalpableCatchHitObject h)
{
- float positionChange = Math.Abs(lastPosition - h.X);
+ float positionChange = Math.Abs(lastPosition - h.EffectiveX);
double timeAvailable = h.StartTime - lastTime;
// So we can either make it there without a dash or not.
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Catch.Replays
// todo: get correct catcher size, based on difficulty CS.
const float catcher_width_half = CatcherArea.CATCHER_SIZE * 0.3f * 0.5f;
- if (lastPosition - catcher_width_half < h.X && lastPosition + catcher_width_half > h.X)
+ if (lastPosition - catcher_width_half < h.EffectiveX && lastPosition + catcher_width_half > h.EffectiveX)
{
// we are already in the correct range.
lastTime = h.StartTime;
@@ -66,12 +66,12 @@ namespace osu.Game.Rulesets.Catch.Replays
if (impossibleJump)
{
- addFrame(h.StartTime, h.X);
+ addFrame(h.StartTime, h.EffectiveX);
}
else if (h.HyperDash)
{
addFrame(h.StartTime - timeAvailable, lastPosition);
- addFrame(h.StartTime, h.X);
+ addFrame(h.StartTime, h.EffectiveX);
}
else if (dashRequired)
{
@@ -80,23 +80,23 @@ namespace osu.Game.Rulesets.Catch.Replays
double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable;
double timeAtDashSpeed = timeWeNeedToSave / 2;
- float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable);
+ float midPosition = (float)Interpolation.Lerp(lastPosition, h.EffectiveX, (float)timeAtDashSpeed / timeAvailable);
// dash movement
addFrame(h.StartTime - timeAvailable + 1, lastPosition, true);
addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition);
- addFrame(h.StartTime, h.X);
+ addFrame(h.StartTime, h.EffectiveX);
}
else
{
double timeBefore = positionChange / movement_speed;
addFrame(h.StartTime - timeBefore, lastPosition);
- addFrame(h.StartTime, h.X);
+ addFrame(h.StartTime, h.EffectiveX);
}
lastTime = h.StartTime;
- lastPosition = h.X;
+ lastPosition = h.EffectiveX;
}
foreach (var obj in Beatmap.HitObjects)
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index f164c2655a..ed875e7002 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -216,7 +216,7 @@ namespace osu.Game.Rulesets.Catch.UI
var halfCatchWidth = catchWidth * 0.5f;
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
- var catchObjectPosition = fruit.X;
+ var catchObjectPosition = fruit.EffectiveX;
var catcherPosition = Position.X;
return catchObjectPosition >= catcherPosition - halfCatchWidth &&
@@ -250,10 +250,10 @@ namespace osu.Game.Rulesets.Catch.UI
{
var target = hitObject.HyperDashTarget;
var timeDifference = target.StartTime - hitObject.StartTime;
- double positionDifference = target.X - X;
+ double positionDifference = target.EffectiveX - X;
var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
- SetHyperDashState(Math.Abs(velocity), target.X);
+ SetHyperDashState(Math.Abs(velocity), target.EffectiveX);
}
else
SetHyperDashState();
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaJudgementPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaJudgementPiece.cs
new file mode 100644
index 0000000000..9684cbb167
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaJudgementPiece.cs
@@ -0,0 +1,90 @@
+// 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.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Animations;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Mania.Skinning.Legacy
+{
+ public class LegacyManiaJudgementPiece : CompositeDrawable, IAnimatableJudgement
+ {
+ private readonly HitResult result;
+ private readonly Drawable animation;
+
+ public LegacyManiaJudgementPiece(HitResult result, Drawable animation)
+ {
+ this.result = result;
+ this.animation = animation;
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ AutoSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ float? scorePosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ScorePosition)?.Value;
+
+ if (scorePosition != null)
+ scorePosition -= Stage.HIT_TARGET_POSITION + 150;
+
+ Y = scorePosition ?? 0;
+
+ if (animation != null)
+ {
+ InternalChild = animation.With(d =>
+ {
+ d.Anchor = Anchor.Centre;
+ d.Origin = Anchor.Centre;
+ });
+ }
+ }
+
+ public void PlayAnimation()
+ {
+ if (animation == null)
+ return;
+
+ (animation as IFramedAnimation)?.GotoFrame(0);
+
+ switch (result)
+ {
+ case HitResult.None:
+ break;
+
+ case HitResult.Miss:
+ animation.ScaleTo(1.6f);
+ animation.ScaleTo(1, 100, Easing.In);
+
+ animation.MoveTo(Vector2.Zero);
+ animation.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
+
+ animation.RotateTo(0);
+ animation.RotateTo(40, 800, Easing.InQuint);
+
+ this.FadeOutFromOne(800);
+ break;
+
+ default:
+ animation.ScaleTo(0.8f);
+ animation.ScaleTo(1, 250, Easing.OutElastic);
+
+ animation.Delay(50).ScaleTo(0.75f, 250);
+
+ this.Delay(50).FadeOut(200);
+ break;
+ }
+ }
+
+ public Drawable GetAboveHitObjectsProxiedContent() => null;
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
index 89f639e2fe..7e2a8823b6 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
@@ -136,7 +136,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
string filename = this.GetManiaSkinConfig(hitresult_mapping[result])?.Value
?? default_hitresult_skin_filenames[result];
- return this.GetAnimation(filename, true, true);
+ var animation = this.GetAnimation(filename, true, true);
+ return animation == null ? null : new LegacyManiaJudgementPiece(result, animation);
}
public override SampleChannel GetSample(ISampleInfo sampleInfo)
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs
index a3dcd0e57f..34d972e60f 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs
@@ -5,7 +5,6 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
-using osuTK;
namespace osu.Game.Rulesets.Mania.UI
{
@@ -20,37 +19,6 @@ namespace osu.Game.Rulesets.Mania.UI
{
}
- protected override void ApplyMissAnimations()
- {
- if (!(JudgementBody.Drawable is DefaultManiaJudgementPiece))
- {
- // this is temporary logic until mania's skin transformer returns IAnimatableJudgements
- JudgementBody.ScaleTo(1.6f);
- JudgementBody.ScaleTo(1, 100, Easing.In);
-
- JudgementBody.MoveTo(Vector2.Zero);
- JudgementBody.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
-
- JudgementBody.RotateTo(0);
- JudgementBody.RotateTo(40, 800, Easing.InQuint);
- JudgementBody.FadeOutFromOne(800);
-
- LifetimeEnd = JudgementBody.LatestTransformEndTime;
- }
-
- base.ApplyMissAnimations();
- }
-
- protected override void ApplyHitAnimations()
- {
- JudgementBody.ScaleTo(0.8f);
- JudgementBody.ScaleTo(1, 250, Easing.OutElastic);
-
- JudgementBody.Delay(50)
- .ScaleTo(0.75f, 250)
- .FadeOut(200);
- }
-
protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result);
private class DefaultManiaJudgementPiece : DefaultJudgementPiece
@@ -66,6 +34,27 @@ namespace osu.Game.Rulesets.Mania.UI
JudgementText.Font = JudgementText.Font.With(size: 25);
}
+
+ public override void PlayAnimation()
+ {
+ base.PlayAnimation();
+
+ switch (Result)
+ {
+ case HitResult.None:
+ case HitResult.Miss:
+ break;
+
+ default:
+ this.ScaleTo(0.8f);
+ this.ScaleTo(1, 250, Easing.OutElastic);
+
+ this.Delay(50)
+ .ScaleTo(0.75f, 250)
+ .FadeOut(200);
+ break;
+ }
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs
index 3d7960ffe3..dc34bffab1 100644
--- a/osu.Game.Rulesets.Mania/UI/Stage.cs
+++ b/osu.Game.Rulesets.Mania/UI/Stage.cs
@@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Mania.UI
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
- Y = HIT_TARGET_POSITION + 150,
+ Y = HIT_TARGET_POSITION + 150
},
topLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/HitObjectApplicationTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/HitObjectApplicationTestScene.cs
new file mode 100644
index 0000000000..07c7b4d1db
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/HitObjectApplicationTestScene.cs
@@ -0,0 +1,52 @@
+// 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.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Testing;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ public abstract class HitObjectApplicationTestScene : OsuTestScene
+ {
+ [Cached(typeof(IScrollingInfo))]
+ private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo
+ {
+ Direction = { Value = ScrollingDirection.Left },
+ TimeRange = { Value = 1000 },
+ };
+
+ private ScrollingHitObjectContainer hitObjectContainer;
+
+ [SetUpSteps]
+ public void SetUp()
+ => AddStep("create SHOC", () => Child = hitObjectContainer = new ScrollingHitObjectContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 200,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Clock = new FramedClock(new StopwatchClock())
+ });
+
+ protected void AddHitObject(DrawableHitObject hitObject)
+ => AddStep("add to SHOC", () => hitObjectContainer.Add(hitObject));
+
+ protected void RemoveHitObject(DrawableHitObject hitObject)
+ => AddStep("remove from SHOC", () => hitObjectContainer.Remove(hitObject));
+
+ protected TObject PrepareObject(TObject hitObject)
+ where TObject : TaikoHitObject
+ {
+ hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+ return hitObject;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs
index f6aec20d53..ff309f278e 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs
@@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
}
};
- hoc.Add(new DrawableBarLineMajor(createBarLineAtCurrentTime(true))
+ hoc.Add(new DrawableBarLine(createBarLineAtCurrentTime(true))
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs
new file mode 100644
index 0000000000..65230a07bc
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs
@@ -0,0 +1,32 @@
+// 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.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ public class TestSceneBarLineApplication : HitObjectApplicationTestScene
+ {
+ [Test]
+ public void TestApplyNewBarLine()
+ {
+ DrawableBarLine barLine = new DrawableBarLine(PrepareObject(new BarLine
+ {
+ StartTime = 400,
+ Major = true
+ }));
+
+ AddHitObject(barLine);
+ RemoveHitObject(barLine);
+
+ AddStep("apply new bar line", () => barLine.Apply(PrepareObject(new BarLine
+ {
+ StartTime = 200,
+ Major = false
+ }), null));
+ AddHitObject(barLine);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs
index c3fa03d404..7695ca067b 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs
@@ -145,9 +145,13 @@ namespace osu.Game.Rulesets.Taiko.Tests
private void addBarLine(bool major, double delay = scroll_time)
{
- BarLine bl = new BarLine { StartTime = DrawableRuleset.Playfield.Time.Current + delay };
+ BarLine bl = new BarLine
+ {
+ StartTime = DrawableRuleset.Playfield.Time.Current + delay,
+ Major = major
+ };
- DrawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl));
+ DrawableRuleset.Playfield.Add(bl);
}
private void addSwell(double duration = default_duration)
diff --git a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs
index 6306195704..bbfc02f975 100644
--- a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/BarLine.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.Bindables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
@@ -8,7 +9,13 @@ namespace osu.Game.Rulesets.Taiko.Objects
{
public class BarLine : TaikoHitObject, IBarLine
{
- public bool Major { get; set; }
+ public bool Major
+ {
+ get => MajorBindable.Value;
+ set => MajorBindable.Value = value;
+ }
+
+ public readonly Bindable MajorBindable = new BindableBool();
public override Judgement CreateJudgement() => new IgnoreJudgement();
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
index aadcc420df..9e50faabc1 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
@@ -1,7 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using JetBrains.Annotations;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Objects;
using osuTK;
@@ -15,49 +19,123 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
///
public class DrawableBarLine : DrawableHitObject
{
+ public new BarLine HitObject => (BarLine)base.HitObject;
+
///
/// The width of the line tracker.
///
private const float tracker_width = 2f;
///
- /// Fade out time calibrated to a pre-empt of 1000ms.
+ /// The vertical offset of the triangles from the line tracker.
///
- private const float base_fadeout_time = 100f;
+ private const float triangle_offset = 10f;
+
+ ///
+ /// The size of the triangles.
+ ///
+ private const float triangle_size = 20f;
///
/// The visual line tracker.
///
- protected SkinnableDrawable Line;
+ private SkinnableDrawable line;
///
- /// The bar line.
+ /// Container with triangles. Only visible for major lines.
///
- protected readonly BarLine BarLine;
+ private Container triangleContainer;
- public DrawableBarLine(BarLine barLine)
+ private readonly Bindable major = new Bindable();
+
+ public DrawableBarLine()
+ : this(null)
+ {
+ }
+
+ public DrawableBarLine([CanBeNull] BarLine barLine)
: base(barLine)
{
- BarLine = barLine;
+ }
+ [BackgroundDependencyLoader]
+ private void load()
+ {
Anchor = Anchor.CentreLeft;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Y;
Width = tracker_width;
- AddInternal(Line = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.BarLine), _ => new Box
+ AddRangeInternal(new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- EdgeSmoothness = new Vector2(0.5f, 0),
- })
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Alpha = 0.75f,
+ line = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.BarLine), _ => new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ EdgeSmoothness = new Vector2(0.5f, 0),
+ })
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ triangleContainer = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Children = new[]
+ {
+ new EquilateralTriangle
+ {
+ Name = "Top",
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Position = new Vector2(0, -triangle_offset),
+ Size = new Vector2(-triangle_size),
+ EdgeSmoothness = new Vector2(1),
+ },
+ new EquilateralTriangle
+ {
+ Name = "Bottom",
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.TopCentre,
+ Position = new Vector2(0, triangle_offset),
+ Size = new Vector2(triangle_size),
+ EdgeSmoothness = new Vector2(1),
+ }
+ }
+ }
});
}
- protected override void UpdateHitStateTransforms(ArmedState state) => this.FadeOut(150);
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ major.BindValueChanged(updateMajor);
+ }
+
+ private void updateMajor(ValueChangedEvent major)
+ {
+ line.Alpha = major.NewValue ? 1f : 0.75f;
+ triangleContainer.Alpha = major.NewValue ? 1 : 0;
+ }
+
+ protected override void OnApply()
+ {
+ base.OnApply();
+ major.BindTo(HitObject.MajorBindable);
+ }
+
+ protected override void OnFree()
+ {
+ base.OnFree();
+ major.UnbindFrom(HitObject.MajorBindable);
+ }
+
+ protected override void UpdateHitStateTransforms(ArmedState state)
+ {
+ using (BeginAbsoluteSequence(HitObject.StartTime))
+ this.FadeOutFromOne(150).Expire();
+ }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs
deleted file mode 100644
index 62aab3524b..0000000000
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-// 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.Graphics;
-using osu.Framework.Graphics.Containers;
-using osuTK;
-using osu.Framework.Graphics.Shapes;
-
-namespace osu.Game.Rulesets.Taiko.Objects.Drawables
-{
- public class DrawableBarLineMajor : DrawableBarLine
- {
- ///
- /// The vertical offset of the triangles from the line tracker.
- ///
- private const float triangle_offfset = 10f;
-
- ///
- /// The size of the triangles.
- ///
- private const float triangle_size = 20f;
-
- private readonly Container triangleContainer;
-
- public DrawableBarLineMajor(BarLine barLine)
- : base(barLine)
- {
- AddInternal(triangleContainer = new Container
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Children = new[]
- {
- new EquilateralTriangle
- {
- Name = "Top",
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Position = new Vector2(0, -triangle_offfset),
- Size = new Vector2(-triangle_size),
- EdgeSmoothness = new Vector2(1),
- },
- new EquilateralTriangle
- {
- Name = "Bottom",
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.TopCentre,
- Position = new Vector2(0, triangle_offfset),
- Size = new Vector2(triangle_size),
- EdgeSmoothness = new Vector2(1),
- }
- }
- });
-
- Line.Alpha = 1f;
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- using (triangleContainer.BeginAbsoluteSequence(HitObject.StartTime))
- triangleContainer.FadeOut(150);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Taiko/UI/BarLinePlayfield.cs b/osu.Game.Rulesets.Taiko/UI/BarLinePlayfield.cs
new file mode 100644
index 0000000000..cb878e8ea0
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/UI/BarLinePlayfield.cs
@@ -0,0 +1,19 @@
+// 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.Allocation;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.Objects.Drawables;
+using osu.Game.Rulesets.UI.Scrolling;
+
+namespace osu.Game.Rulesets.Taiko.UI
+{
+ public class BarLinePlayfield : ScrollingPlayfield
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ RegisterPool(15);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
index e6aacf34dc..bbf8cb8de0 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.UI
[BackgroundDependencyLoader]
private void load()
{
- new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar)));
+ new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar));
FrameStableComponents.Add(scroller = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty())
{
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index 370760f03e..d20b190c86 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -10,6 +10,7 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
@@ -38,10 +39,15 @@ namespace osu.Game.Rulesets.Taiko.UI
private SkinnableDrawable mascot;
private ProxyContainer topLevelHitContainer;
- private ScrollingHitObjectContainer barlineContainer;
private Container rightArea;
private Container leftArea;
+ ///
+ /// is purposefully not called on this to prevent i.e. being able to interact
+ /// with bar lines in the editor.
+ ///
+ private BarLinePlayfield barLinePlayfield;
+
private Container hitTargetOffsetContent;
public TaikoPlayfield(ControlPointInfo controlPoints)
@@ -84,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.UI
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
- barlineContainer = new ScrollingHitObjectContainer(),
+ barLinePlayfield = new BarLinePlayfield(),
new Container
{
Name = "Hit objects",
@@ -155,12 +161,50 @@ namespace osu.Game.Rulesets.Taiko.UI
mascot.Scale = new Vector2(DrawHeight / DEFAULT_HEIGHT);
}
+ #region Pooling support
+
+ public override void Add(HitObject h)
+ {
+ switch (h)
+ {
+ case BarLine barLine:
+ barLinePlayfield.Add(barLine);
+ break;
+
+ case TaikoHitObject taikoHitObject:
+ base.Add(taikoHitObject);
+ break;
+
+ default:
+ throw new ArgumentException($"Unsupported {nameof(HitObject)} type: {h.GetType()}");
+ }
+ }
+
+ public override bool Remove(HitObject h)
+ {
+ switch (h)
+ {
+ case BarLine barLine:
+ return barLinePlayfield.Remove(barLine);
+
+ case TaikoHitObject taikoHitObject:
+ return base.Remove(taikoHitObject);
+
+ default:
+ throw new ArgumentException($"Unsupported {nameof(HitObject)} type: {h.GetType()}");
+ }
+ }
+
+ #endregion
+
+ #region Non-pooling support
+
public override void Add(DrawableHitObject h)
{
switch (h)
{
- case DrawableBarLine barline:
- barlineContainer.Add(barline);
+ case DrawableBarLine barLine:
+ barLinePlayfield.Add(barLine);
break;
case DrawableTaikoHitObject taikoObject:
@@ -170,7 +214,7 @@ namespace osu.Game.Rulesets.Taiko.UI
break;
default:
- throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type");
+ throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type: {h.GetType()}");
}
}
@@ -178,8 +222,8 @@ namespace osu.Game.Rulesets.Taiko.UI
{
switch (h)
{
- case DrawableBarLine barline:
- return barlineContainer.Remove(barline);
+ case DrawableBarLine barLine:
+ return barLinePlayfield.Remove(barLine);
case DrawableTaikoHitObject _:
h.OnNewResult -= OnNewResult;
@@ -187,10 +231,12 @@ namespace osu.Game.Rulesets.Taiko.UI
return base.Remove(h);
default:
- throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type");
+ throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type: {h.GetType()}");
}
}
+ #endregion
+
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
{
if (!DisplayJudgements.Value)
diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs
index 742d433760..347d9e3ba7 100644
--- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs
@@ -191,7 +191,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
Anchor = Anchor.TopCentre,
Y = -separation,
- HandleDrag = e => OnRotation?.Invoke(e.Delta.X),
+ HandleDrag = e => OnRotation?.Invoke(convertDragEventToAngleOfRotation(e)),
OperationStarted = operationStarted,
OperationEnded = operationEnded
}
@@ -242,6 +242,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
private int activeOperations;
+ private float convertDragEventToAngleOfRotation(DragEvent e)
+ {
+ // Adjust coordinate system to the center of SelectionBox
+ float startAngle = MathF.Atan2(e.LastMousePosition.Y - DrawHeight / 2, e.LastMousePosition.X - DrawWidth / 2);
+ float endAngle = MathF.Atan2(e.MousePosition.Y - DrawHeight / 2, e.MousePosition.X - DrawWidth / 2);
+
+ return (endAngle - startAngle) * 180 / MathF.PI;
+ }
+
private void operationEnded()
{
if (--activeOperations == 0)
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 729119fa36..f59b36bc42 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -189,7 +189,7 @@ namespace osu.Game.Screens.Play
// after an initial delay, start the debounced load check.
// this will continue to execute even after resuming back on restart.
- Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, 1800, 0));
+ Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, Clock.CurrentTime + 1800, 0));
showMuteWarningIfNeeded();
}
diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
index 35a6140cbc..77d390875b 100644
--- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
@@ -35,6 +35,7 @@ namespace osu.Game.Skinning
public float HitPosition = (480 - 402) * POSITION_SCALE_FACTOR;
public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR;
+ public float ScorePosition = 300 * POSITION_SCALE_FACTOR;
public bool ShowJudgementLine = true;
public bool KeysUnderNotes;
diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
index a99710ea96..9db6c8bf66 100644
--- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
@@ -25,6 +25,7 @@ namespace osu.Game.Skinning
LeftLineWidth,
RightLineWidth,
HitPosition,
+ ScorePosition,
LightPosition,
HitTargetImage,
ShowJudgementLine,
diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
index 3dbec23194..6a04a95040 100644
--- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
@@ -94,6 +94,10 @@ namespace osu.Game.Skinning
currentConfig.LightPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
break;
+ case "ScorePosition":
+ currentConfig.ScorePosition = (float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
+ break;
+
case "JudgementLine":
currentConfig.ShowJudgementLine = pair.Value == "1";
break;
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 63a22eba62..80b2fef35c 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -169,6 +169,9 @@ namespace osu.Game.Skinning
case LegacyManiaSkinConfigurationLookups.HitPosition:
return SkinUtils.As(new Bindable(existing.HitPosition));
+ case LegacyManiaSkinConfigurationLookups.ScorePosition:
+ return SkinUtils.As(new Bindable(existing.ScorePosition));
+
case LegacyManiaSkinConfigurationLookups.LightPosition:
return SkinUtils.As(new Bindable(existing.LightPosition));
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index b4c7dca12f..960959f367 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 7542aded86..a5bcb91c74 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -88,7 +88,7 @@
-
+