diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs new file mode 100644 index 0000000000..ec186bcfb2 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs @@ -0,0 +1,114 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Catch.Judgements; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Catch.Skinning; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; +using Direction = osu.Game.Rulesets.Catch.UI.Direction; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public class TestSceneCatchSkinConfiguration : OsuTestScene + { + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + + private Catcher catcher; + + private readonly Container container; + + public TestSceneCatchSkinConfiguration() + { + Add(droppedObjectContainer = new DroppedObjectContainer()); + Add(container = new Container { RelativeSizeAxes = Axes.Both }); + } + + [TestCase(false)] + [TestCase(true)] + public void TestCatcherPlateFlipping(bool flip) + { + AddStep("setup catcher", () => + { + var skin = new TestSkin { FlipCatcherPlate = flip }; + container.Child = new SkinProvidingContainer(skin) + { + Child = catcher = new Catcher(new Container()) + { + Anchor = Anchor.Centre + } + }; + }); + + Fruit fruit = new Fruit(); + + AddStep("catch fruit", () => catchFruit(fruit, 20)); + + float position = 0; + + AddStep("record fruit position", () => position = getCaughtObjectPosition(fruit)); + + AddStep("face left", () => catcher.VisualDirection = Direction.Left); + + if (flip) + AddAssert("fruit position changed", () => !Precision.AlmostEquals(getCaughtObjectPosition(fruit), position)); + else + AddAssert("fruit position unchanged", () => Precision.AlmostEquals(getCaughtObjectPosition(fruit), position)); + + AddStep("face right", () => catcher.VisualDirection = Direction.Right); + + AddAssert("fruit position restored", () => Precision.AlmostEquals(getCaughtObjectPosition(fruit), position)); + } + + private float getCaughtObjectPosition(Fruit fruit) + { + var caughtObject = catcher.ChildrenOfType().Single(c => c.HitObject == fruit); + return caughtObject.Parent.ToSpaceOfOtherDrawable(caughtObject.Position, catcher).X; + } + + private void catchFruit(Fruit fruit, float x) + { + fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + var drawableFruit = new DrawableFruit(fruit) { X = x }; + var judgement = fruit.CreateJudgement(); + catcher.OnNewResult(drawableFruit, new CatchJudgementResult(fruit, judgement) + { + Type = judgement.MaxResult + }); + } + + private class TestSkin : DefaultSkin + { + public bool FlipCatcherPlate { get; set; } + + public TestSkin() + : base(null) + { + } + + public override IBindable GetConfig(TLookup lookup) + { + if (lookup is CatchSkinConfiguration config) + { + if (config == CatchSkinConfiguration.FlipCatcherPlate) + return SkinUtils.As(new Bindable(FlipCatcherPlate)); + } + + return base.GetConfig(lookup); + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs b/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs new file mode 100644 index 0000000000..ea8d742b1a --- /dev/null +++ b/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Catch.Skinning +{ + public enum CatchSkinConfiguration + { + /// + /// Whether the contents of the catcher plate should be visually flipped when the catcher direction is changed. + /// + FlipCatcherPlate + } +} diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 287ed1b4c7..5e744ec001 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -103,6 +103,19 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy result.Value = LegacyColourCompatibility.DisallowZeroAlpha(result.Value); return (IBindable)result; + + case CatchSkinConfiguration config: + switch (config) + { + case CatchSkinConfiguration.FlipCatcherPlate: + // Don't flip catcher plate contents if the catcher is provided by this legacy skin. + if (GetDrawableComponent(new CatchSkinComponent(CatchSkinComponents.Catcher)) != null) + return (IBindable)new Bindable(); + + break; + } + + break; } return base.GetConfig(lookup); diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index f2e29b2e10..57523d3505 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -79,8 +79,8 @@ namespace osu.Game.Rulesets.Catch.UI public CatcherAnimationState CurrentState { - get => body.AnimationState.Value; - private set => body.AnimationState.Value = value; + get => Body.AnimationState.Value; + private set => Body.AnimationState.Value = value; } /// @@ -103,18 +103,22 @@ namespace osu.Game.Rulesets.Catch.UI } } - public Direction VisualDirection - { - get => Scale.X > 0 ? Direction.Right : Direction.Left; - set => Scale = new Vector2((value == Direction.Right ? 1 : -1) * Math.Abs(Scale.X), Scale.Y); - } + /// + /// The currently facing direction. + /// + public Direction VisualDirection { get; set; } = Direction.Right; + + /// + /// Whether the contents of the catcher plate should be visually flipped when the catcher direction is changed. + /// + private bool flipCatcherPlate; /// /// Width of the area that can be used to attempt catches during gameplay. /// private readonly float catchWidth; - private readonly SkinnableCatcher body; + internal readonly SkinnableCatcher Body; private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR; private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR; @@ -155,7 +159,7 @@ namespace osu.Game.Rulesets.Catch.UI // offset fruit vertically to better place "above" the plate. Y = -5 }, - body = new SkinnableCatcher(), + Body = new SkinnableCatcher(), hitExplosionContainer = new HitExplosionContainer { Anchor = Anchor.TopCentre, @@ -344,6 +348,8 @@ namespace osu.Game.Rulesets.Catch.UI trails.HyperDashTrailsColour = hyperDashColour; trails.EndGlowSpritesColour = hyperDashEndGlowColour; + flipCatcherPlate = skin.GetConfig(CatchSkinConfiguration.FlipCatcherPlate)?.Value ?? true; + runHyperDashStateTransition(HyperDashing); } @@ -351,6 +357,10 @@ namespace osu.Game.Rulesets.Catch.UI { base.Update(); + var scaleFromDirection = new Vector2((int)VisualDirection, 1); + Body.Scale = scaleFromDirection; + caughtObjectContainer.Scale = hitExplosionContainer.Scale = flipCatcherPlate ? scaleFromDirection : Vector2.One; + // Correct overshooting. if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) || (hyperDashDirection < 0 && hyperDashTargetPosition > X)) @@ -459,7 +469,7 @@ namespace osu.Game.Rulesets.Catch.UI break; case DroppedObjectAnimation.Explode: - var originalX = droppedObjectTarget.ToSpaceOfOtherDrawable(d.DrawPosition, caughtObjectContainer).X * Scale.X; + float originalX = droppedObjectTarget.ToSpaceOfOtherDrawable(d.DrawPosition, caughtObjectContainer).X * caughtObjectContainer.Scale.X; d.MoveToY(d.Y - 50, 250, Easing.OutSine).Then().MoveToY(d.Y + 50, 500, Easing.InSine); d.MoveToX(d.X + originalX * 6, 1000); d.FadeOut(750); diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index 7e4a5b6a86..b59fabcb70 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Catch.UI CatcherTrail sprite = trailPool.Get(); sprite.AnimationState = catcher.CurrentState; - sprite.Scale = catcher.Scale; + sprite.Scale = catcher.Scale * catcher.Body.Scale; sprite.Position = catcher.Position; target.Add(sprite);