diff --git a/osu.Android.props b/osu.Android.props
index b147fdd05b..9e729d8705 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,7 +51,7 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail@2x.png
similarity index 100%
rename from osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail.png
rename to osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail@2x.png
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai@2x.png
similarity index 100%
rename from osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai.png
rename to osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai@2x.png
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index 5880a227c2..4d9dbbbc5f 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -72,10 +72,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
protected override Skill[] CreateSkills(IBeatmap beatmap)
{
using (var catcher = new Catcher(beatmap.BeatmapInfo.BaseDifficulty))
- {
halfCatcherWidth = catcher.CatchWidth * 0.5f;
- halfCatcherWidth *= 0.8f; // We're only using 80% of the catcher's width to simulate imperfect gameplay.
- }
return new Skill[]
{
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index 8fa9c61b6f..13935e036b 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -37,10 +37,15 @@ namespace osu.Game.Rulesets.Catch.UI
public CatcherAnimationState CurrentState { get; private set; }
+ ///
+ /// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable.
+ ///
+ private const float allowed_catch_range = 0.8f;
+
///
/// Width of the area that can be used to attempt catches during gameplay.
///
- internal float CatchWidth => CatcherArea.CATCHER_SIZE * Math.Abs(Scale.X);
+ internal float CatchWidth => CatcherArea.CATCHER_SIZE * Math.Abs(Scale.X) * allowed_catch_range;
protected bool Dashing
{
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
index 52eb8d597e..ef69e3d2d1 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.UI
public CatcherSprite(CatcherAnimationState state)
: base(new CatchSkinComponent(componentFromState(state)), _ =>
- new DefaultCatcherSprite(state), confineMode: ConfineMode.ScaleDownToFit)
+ new DefaultCatcherSprite(state), confineMode: ConfineMode.ScaleToFit)
{
RelativeSizeAxes = Axes.None;
Size = new Vector2(CatcherArea.CATCHER_SIZE);
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index 6286c80d7c..9b0759d9d2 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
@@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Mods
void handleHitCircle(DrawableHitCircle circle)
{
- if (!circle.IsHovered)
+ if (!circle.HitArea.IsHovered)
return;
Debug.Assert(circle.HitObject.HitWindows != null);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
index 7e530ca047..8bb324d02e 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
///
/// A single follow point positioned between two adjacent s.
///
- public class FollowPoint : Container
+ public class FollowPoint : Container, IAnimationTimeReference
{
private const float width = 8;
@@ -45,5 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
}
}, confineMode: ConfineMode.NoScaling);
}
+
+ public double AnimationStartTime { get; set; }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
index d0935e46f7..6f09bbcd57 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
@@ -116,6 +116,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
int point = 0;
+ ClearInternal();
+
for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing)
{
float fraction = (float)d / distance;
@@ -126,13 +128,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
FollowPoint fp;
- if (InternalChildren.Count > point)
- {
- fp = (FollowPoint)InternalChildren[point];
- fp.ClearTransforms();
- }
- else
- AddInternal(fp = new FollowPoint());
+ AddInternal(fp = new FollowPoint());
fp.Position = pointStartPosition;
fp.Rotation = rotation;
@@ -142,6 +138,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
if (firstTransformStartTime == null)
firstTransformStartTime = fadeInTime;
+ fp.AnimationStartTime = fadeInTime;
+
using (fp.BeginAbsoluteSequence(fadeInTime))
{
fp.FadeIn(osuEnd.TimeFadeIn);
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs
index 38ba4c5974..e7486ef9b0 100644
--- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs
@@ -62,6 +62,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
}
};
+ bool overlayAboveNumber = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true;
+
+ if (!overlayAboveNumber)
+ ChangeInternalChildDepth(hitCircleText, -float.MaxValue);
+
state.BindTo(drawableObject.State);
state.BindValueChanged(updateState, true);
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
index 5d99960f10..c6920bd03e 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
@@ -11,6 +11,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
SliderPathRadius,
AllowSliderBallTint,
CursorExpand,
- CursorRotate
+ CursorRotate,
+ HitCircleOverlayAboveNumber
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
index ec94053679..3b91243fee 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
@@ -43,16 +43,15 @@ namespace osu.Game.Tests.Visual.Gameplay
{
new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true),
- new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling)
}
},
};
});
- AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 }));
+ AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 50 }));
AddStep("adjust scale", () => fill.Scale = new Vector2(2));
- AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 }));
+ AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 50 }));
}
[Test]
@@ -74,7 +73,6 @@ namespace osu.Game.Tests.Visual.Gameplay
Children = new[]
{
new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true),
- new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling)
}
@@ -82,9 +80,9 @@ namespace osu.Game.Tests.Visual.Gameplay
};
});
- AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 }));
+ AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 50, 30 }));
AddStep("adjust scale", () => fill.Scale = new Vector2(2));
- AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 }));
+ AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 50, 30 }));
}
[Test]
@@ -182,7 +180,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public new Drawable Drawable => base.Drawable;
public ExposedSkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null,
- ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ ConfineMode confineMode = ConfineMode.ScaleToFit)
: base(new TestSkinComponent(name), defaultImplementation, allowFallback, confineMode)
{
}
diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
index c356dd246d..a4a560c8e4 100644
--- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
+++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
@@ -28,10 +28,11 @@ namespace osu.Game.Scoring.Legacy
{
var score = new Score
{
- ScoreInfo = new ScoreInfo(),
Replay = new Replay()
};
+ WorkingBeatmap workingBeatmap;
+
using (SerializationReader sr = new SerializationReader(stream))
{
currentRuleset = GetRuleset(sr.ReadByte());
@@ -41,7 +42,7 @@ namespace osu.Game.Scoring.Legacy
var version = sr.ReadInt32();
- var workingBeatmap = GetBeatmap(sr.ReadString());
+ workingBeatmap = GetBeatmap(sr.ReadString());
if (workingBeatmap is DummyWorkingBeatmap)
throw new BeatmapNotFoundException();
@@ -113,6 +114,10 @@ namespace osu.Game.Scoring.Legacy
CalculateAccuracy(score.ScoreInfo);
+ // before returning for database import, we must restore the database-sourced BeatmapInfo.
+ // if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception.
+ score.ScoreInfo.Beatmap = workingBeatmap.BeatmapInfo;
+
return score;
}
diff --git a/osu.Game/Skinning/IAnimationTimeReference.cs b/osu.Game/Skinning/IAnimationTimeReference.cs
new file mode 100644
index 0000000000..bcff10a24b
--- /dev/null
+++ b/osu.Game/Skinning/IAnimationTimeReference.cs
@@ -0,0 +1,25 @@
+// 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.Timing;
+
+namespace osu.Game.Skinning
+{
+ ///
+ /// Denotes an object which provides a reference time to start animations from.
+ ///
+ [Cached]
+ public interface IAnimationTimeReference
+ {
+ ///
+ /// The reference clock.
+ ///
+ IFrameBasedClock Clock { get; }
+
+ ///
+ /// The time which animations should be started from, relative to .
+ ///
+ double AnimationStartTime { get; }
+ }
+}
diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs
index 52328d43b2..8765b161d4 100644
--- a/osu.Game/Skinning/LegacySkinExtensions.cs
+++ b/osu.Game/Skinning/LegacySkinExtensions.cs
@@ -3,10 +3,12 @@
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
+using osu.Framework.Timing;
namespace osu.Game.Skinning
{
@@ -22,7 +24,7 @@ namespace osu.Game.Skinning
if (textures.Length > 0)
{
- var animation = new TextureAnimation
+ var animation = new SkinnableTextureAnimation
{
DefaultFrameLength = getFrameLength(source, applyConfigFrameRate, textures),
Repeat = looping,
@@ -53,6 +55,25 @@ namespace osu.Game.Skinning
}
}
+ public class SkinnableTextureAnimation : TextureAnimation
+ {
+ [Resolved(canBeNull: true)]
+ private IAnimationTimeReference timeReference { get; set; }
+
+ public SkinnableTextureAnimation()
+ : base(false)
+ {
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ if (timeReference != null)
+ Clock = new FramedOffsetClock(timeReference.Clock) { Offset = -timeReference.AnimationStartTime };
+ }
+ }
+
private const double default_frame_time = 1000 / 60d;
private static double getFrameLength(ISkin source, bool applyConfigFrameRate, Texture[] textures)
diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs
index f6ac6494b4..0f0d3da5aa 100644
--- a/osu.Game/Skinning/SkinnableDrawable.cs
+++ b/osu.Game/Skinning/SkinnableDrawable.cs
@@ -98,20 +98,13 @@ namespace osu.Game.Skinning
switch (confineMode)
{
- case ConfineMode.NoScaling:
- return;
-
- case ConfineMode.ScaleDownToFit:
- if (Drawable.DrawSize.X <= DrawSize.X && Drawable.DrawSize.Y <= DrawSize.Y)
- return;
-
+ case ConfineMode.ScaleToFit:
+ Drawable.RelativeSizeAxes = Axes.Both;
+ Drawable.Size = Vector2.One;
+ Drawable.Scale = Vector2.One;
+ Drawable.FillMode = FillMode.Fit;
break;
}
-
- Drawable.RelativeSizeAxes = Axes.Both;
- Drawable.Size = Vector2.One;
- Drawable.Scale = Vector2.One;
- Drawable.FillMode = FillMode.Fit;
}
finally
{
@@ -127,7 +120,6 @@ namespace osu.Game.Skinning
/// Don't apply any scaling. This allows the user element to be of any size, exceeding specified bounds.
///
NoScaling,
- ScaleDownToFit,
ScaleToFit,
}
}
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs
index bc6e01a729..c4d796e30b 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs
@@ -50,7 +50,7 @@ namespace osu.Game.Storyboards.Drawables
AddInternal(Content = new Container
{
- Size = new Vector2(640, 480),
+ RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs
index def4eed2ca..2ada83c3b4 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs
@@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers;
namespace osu.Game.Storyboards.Drawables
{
- public class DrawableStoryboardLayer : LifetimeManagementContainer
+ public class DrawableStoryboardLayer : CompositeDrawable
{
public StoryboardLayer Layer { get; }
public bool Enabled;
@@ -23,17 +23,34 @@ namespace osu.Game.Storyboards.Drawables
Origin = Anchor.Centre;
Enabled = layer.VisibleWhenPassing;
Masking = layer.Masking;
+
+ InternalChild = new LayerElementContainer(layer);
}
- [BackgroundDependencyLoader]
- private void load(CancellationToken? cancellationToken)
+ private class LayerElementContainer : LifetimeManagementContainer
{
- foreach (var element in Layer.Elements)
- {
- cancellationToken?.ThrowIfCancellationRequested();
+ private readonly StoryboardLayer storyboardLayer;
- if (element.IsDrawable)
- AddInternal(element.CreateDrawable());
+ public LayerElementContainer(StoryboardLayer layer)
+ {
+ storyboardLayer = layer;
+
+ Width = 640;
+ Height = 480;
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(CancellationToken? cancellationToken)
+ {
+ foreach (var element in storyboardLayer.Elements)
+ {
+ cancellationToken?.ThrowIfCancellationRequested();
+
+ if (element.IsDrawable)
+ AddInternal(element.CreateDrawable());
+ }
}
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 781c566b5f..30c11a1cdb 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -22,8 +22,8 @@
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index a2c6106931..d035f5c4d8 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,8 +70,8 @@
-
-
+
+
@@ -79,7 +79,7 @@
-
+