From c72224fa9412d72367a112e461029b864d5f782f Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Sun, 8 Aug 2021 13:45:13 -0400 Subject: [PATCH 1/6] Add "Mirror" mod to osu!catch --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 1 + .../Mods/CatchModMirror.cs | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index eafa1b9b9d..9fee6b2bc1 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -117,6 +117,7 @@ namespace osu.Game.Rulesets.Catch { new CatchModDifficultyAdjust(), new CatchModClassic(), + new CatchModMirror(), }; case ModType.Automation: diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs b/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs new file mode 100644 index 0000000000..8eb092bfb3 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs @@ -0,0 +1,39 @@ +// 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.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModMirror : ModMirror, IApplicableToHitObject + { + public override string Description => "Fruits are flipped horizontally."; + + public void ApplyToHitObject(HitObject hitObject) + { + var catchObject = (CatchHitObject)hitObject; + + if (catchObject is BananaShower) + return; + + catchObject.OriginalX = CatchPlayfield.WIDTH - catchObject.OriginalX; + + foreach (var nested in catchObject.NestedHitObjects.Cast()) + nested.OriginalX = CatchPlayfield.WIDTH - nested.OriginalX; + + if (!(catchObject is JuiceStream juiceStream)) + return; + + var controlPoints = juiceStream.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray(); + foreach (var point in controlPoints) + point.Position.Value = new Vector2(-point.Position.Value.X, point.Position.Value.Y); + + juiceStream.Path = new SliderPath(controlPoints, juiceStream.Path.ExpectedDistance.Value); + } + } +} From 3a741affa325fac471a06febff810827a76f44d5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 8 Aug 2021 21:27:48 +0300 Subject: [PATCH 2/6] Remove whitespaces --- osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs b/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs index 8eb092bfb3..ce5b78e12f 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Mods public class CatchModMirror : ModMirror, IApplicableToHitObject { public override string Description => "Fruits are flipped horizontally."; - + public void ApplyToHitObject(HitObject hitObject) { var catchObject = (CatchHitObject)hitObject; From 30cda318f93fe5947cb44335b9ed163e2294d6fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Aug 2021 15:57:18 +0900 Subject: [PATCH 3/6] Reorganise code slightly --- .../Mods/CatchModMirror.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs b/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs index ce5b78e12f..7fe7e70fd0 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs @@ -16,24 +16,24 @@ namespace osu.Game.Rulesets.Catch.Mods public void ApplyToHitObject(HitObject hitObject) { - var catchObject = (CatchHitObject)hitObject; - - if (catchObject is BananaShower) + if (hitObject is BananaShower) return; + var catchObject = (CatchHitObject)hitObject; + catchObject.OriginalX = CatchPlayfield.WIDTH - catchObject.OriginalX; foreach (var nested in catchObject.NestedHitObjects.Cast()) nested.OriginalX = CatchPlayfield.WIDTH - nested.OriginalX; - if (!(catchObject is JuiceStream juiceStream)) - return; + if (catchObject is JuiceStream juiceStream) + { + var controlPoints = juiceStream.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray(); + foreach (var point in controlPoints) + point.Position.Value = new Vector2(-point.Position.Value.X, point.Position.Value.Y); - var controlPoints = juiceStream.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray(); - foreach (var point in controlPoints) - point.Position.Value = new Vector2(-point.Position.Value.X, point.Position.Value.Y); - - juiceStream.Path = new SliderPath(controlPoints, juiceStream.Path.ExpectedDistance.Value); + juiceStream.Path = new SliderPath(controlPoints, juiceStream.Path.ExpectedDistance.Value); + } } } } From 30cb4280a450664dd04fc13b31c80ec8f96798c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 Aug 2021 22:21:54 +0200 Subject: [PATCH 4/6] Add test for catch beatmap mirroring --- .../Mods/CatchModMirrorTest.cs | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs b/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs new file mode 100644 index 0000000000..8e66284a23 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs @@ -0,0 +1,122 @@ +// 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 NUnit.Framework; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Catch.Mods; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Tests.Mods +{ + [TestFixture] + public class CatchModMirrorTest + { + [Test] + public void TestModMirror() + { + IBeatmap original = createBeatmap(false); + IBeatmap mirrored = createBeatmap(true); + + assertEffectivePositionsMirrored(original, mirrored); + } + + private static IBeatmap createBeatmap(bool withMirrorMod) + { + var beatmap = createRawBeatmap(); + var mirrorMod = new CatchModMirror(); + + var beatmapProcessor = new CatchBeatmapProcessor(beatmap); + beatmapProcessor.PreProcess(); + + foreach (var hitObject in beatmap.HitObjects) + { + hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + if (withMirrorMod) + mirrorMod.ApplyToHitObject(hitObject); + } + + beatmapProcessor.PostProcess(); + + return beatmap; + } + + private static IBeatmap createRawBeatmap() => new Beatmap + { + HitObjects = new List + { + new Fruit + { + OriginalX = 150, + StartTime = 0 + }, + new Fruit + { + OriginalX = 450, + StartTime = 500 + }, + new JuiceStream + { + OriginalX = 250, + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(new Vector2(-100, 1)), + new PathControlPoint(new Vector2(0, 2)), + new PathControlPoint(new Vector2(100, 3)), + new PathControlPoint(new Vector2(0, 4)) + } + }, + StartTime = 1000, + }, + new BananaShower + { + StartTime = 5000, + Duration = 5000 + } + } + }; + + private static void assertEffectivePositionsMirrored(IBeatmap original, IBeatmap mirrored) + { + if (original.HitObjects.Count != mirrored.HitObjects.Count) + Assert.Fail($"Top-level object count mismatch (original: {original.HitObjects.Count}, mirrored: {mirrored.HitObjects.Count})"); + + for (int i = 0; i < original.HitObjects.Count; ++i) + { + var originalObject = (CatchHitObject)original.HitObjects[i]; + var mirroredObject = (CatchHitObject)mirrored.HitObjects[i]; + + // banana showers themselves are exempt, as we only really care about their nested bananas' positions. + if (!effectivePositionMirrored(originalObject, mirroredObject) && !(originalObject is BananaShower)) + Assert.Fail($"{originalObject.GetType().Name} at time {originalObject.StartTime} is not mirrored ({printEffectivePositions(originalObject, mirroredObject)})"); + + if (originalObject.NestedHitObjects.Count != mirroredObject.NestedHitObjects.Count) + Assert.Fail($"{originalObject.GetType().Name} nested object count mismatch (original: {originalObject.NestedHitObjects.Count}, mirrored: {mirroredObject.NestedHitObjects.Count})"); + + for (int j = 0; j < originalObject.NestedHitObjects.Count; ++j) + { + var originalNested = (CatchHitObject)originalObject.NestedHitObjects[j]; + var mirroredNested = (CatchHitObject)mirroredObject.NestedHitObjects[j]; + + if (!effectivePositionMirrored(originalNested, mirroredNested)) + Assert.Fail($"{originalObject.GetType().Name}'s nested {originalNested.GetType().Name} at time {originalObject.StartTime} is not mirrored ({printEffectivePositions(originalNested, mirroredNested)})"); + } + } + } + + private static string printEffectivePositions(CatchHitObject original, CatchHitObject mirrored) + => $"original X: {original.EffectiveX}, mirrored X is: {mirrored.EffectiveX}, mirrored X should be: {CatchPlayfield.WIDTH - original.EffectiveX}"; + + private static bool effectivePositionMirrored(CatchHitObject original, CatchHitObject mirrored) + => Precision.AlmostEquals(original.EffectiveX, CatchPlayfield.WIDTH - mirrored.EffectiveX); + } +} From 637e5cb6b713bd942c6dd58d6f1a56c0ead646b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 Aug 2021 21:42:41 +0200 Subject: [PATCH 5/6] Fix offsets not being mirrored --- .../Mods/CatchModMirrorTest.cs | 8 +++---- .../Mods/CatchModMirror.cs | 21 +++++++++++++++++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs b/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs index 8e66284a23..fbbfee6b60 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs @@ -36,15 +36,13 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods beatmapProcessor.PreProcess(); foreach (var hitObject in beatmap.HitObjects) - { hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - if (withMirrorMod) - mirrorMod.ApplyToHitObject(hitObject); - } - beatmapProcessor.PostProcess(); + if (withMirrorMod) + mirrorMod.ApplyToBeatmap(beatmap); + return beatmap; } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs b/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs index 7fe7e70fd0..3aa8862a0a 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Catch.UI; @@ -10,11 +12,22 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModMirror : ModMirror, IApplicableToHitObject + public class CatchModMirror : ModMirror, IApplicableToBeatmap { public override string Description => "Fruits are flipped horizontally."; - public void ApplyToHitObject(HitObject hitObject) + /// + /// is used instead of , + /// as applies offsets in . + /// runs after post-processing, while runs before it. + /// + public void ApplyToBeatmap(IBeatmap beatmap) + { + foreach (var hitObject in beatmap.HitObjects) + applyToHitObject(hitObject); + } + + private void applyToHitObject(HitObject hitObject) { if (hitObject is BananaShower) return; @@ -22,9 +35,13 @@ namespace osu.Game.Rulesets.Catch.Mods var catchObject = (CatchHitObject)hitObject; catchObject.OriginalX = CatchPlayfield.WIDTH - catchObject.OriginalX; + catchObject.XOffset = -catchObject.XOffset; foreach (var nested in catchObject.NestedHitObjects.Cast()) + { nested.OriginalX = CatchPlayfield.WIDTH - nested.OriginalX; + nested.XOffset = -nested.XOffset; + } if (catchObject is JuiceStream juiceStream) { From a9e53107d146ac1fad598136b439f9296a1c51a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 Aug 2021 21:51:46 +0200 Subject: [PATCH 6/6] Also mirror banana showers --- .../Mods/CatchModMirror.cs | 51 +++++++++++++++---- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs b/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs index 3aa8862a0a..932c8cad85 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs @@ -29,11 +29,30 @@ namespace osu.Game.Rulesets.Catch.Mods private void applyToHitObject(HitObject hitObject) { - if (hitObject is BananaShower) - return; - var catchObject = (CatchHitObject)hitObject; + switch (catchObject) + { + case Fruit fruit: + mirrorEffectiveX(fruit); + break; + + case JuiceStream juiceStream: + mirrorEffectiveX(juiceStream); + mirrorJuiceStreamPath(juiceStream); + break; + + case BananaShower bananaShower: + mirrorBananaShower(bananaShower); + break; + } + } + + /// + /// Mirrors the effective X position of and its nested hit objects. + /// + private static void mirrorEffectiveX(CatchHitObject catchObject) + { catchObject.OriginalX = CatchPlayfield.WIDTH - catchObject.OriginalX; catchObject.XOffset = -catchObject.XOffset; @@ -42,15 +61,27 @@ namespace osu.Game.Rulesets.Catch.Mods nested.OriginalX = CatchPlayfield.WIDTH - nested.OriginalX; nested.XOffset = -nested.XOffset; } + } - if (catchObject is JuiceStream juiceStream) - { - var controlPoints = juiceStream.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray(); - foreach (var point in controlPoints) - point.Position.Value = new Vector2(-point.Position.Value.X, point.Position.Value.Y); + /// + /// Mirrors the path of the . + /// + private static void mirrorJuiceStreamPath(JuiceStream juiceStream) + { + var controlPoints = juiceStream.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray(); + foreach (var point in controlPoints) + point.Position.Value = new Vector2(-point.Position.Value.X, point.Position.Value.Y); - juiceStream.Path = new SliderPath(controlPoints, juiceStream.Path.ExpectedDistance.Value); - } + juiceStream.Path = new SliderPath(controlPoints, juiceStream.Path.ExpectedDistance.Value); + } + + /// + /// Mirrors X positions of all bananas in the . + /// + private static void mirrorBananaShower(BananaShower bananaShower) + { + foreach (var banana in bananaShower.NestedHitObjects.OfType()) + banana.XOffset = CatchPlayfield.WIDTH - banana.XOffset; } } }