// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable using System.IO; using System.Text; using NUnit.Framework; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; using osuTK; using Decoder = osu.Game.Beatmaps.Formats.Decoder; namespace osu.Game.Tests.Editing { [TestFixture] public class LegacyEditorBeatmapPatcherTest { private LegacyEditorBeatmapPatcher patcher; private EditorBeatmap current; [SetUp] public void Setup() { patcher = new LegacyEditorBeatmapPatcher(current = new EditorBeatmap(new OsuBeatmap { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } })); } [Test] public void TestPatchNoObjectChanges() { runTest(new OsuBeatmap()); } [Test] public void TestAddHitObject() { var patch = new OsuBeatmap { HitObjects = { new HitCircle { StartTime = 1000, NewCombo = true } } }; runTest(patch); } [Test] public void TestInsertHitObject() { current.AddRange(new[] { new HitCircle { StartTime = 1000, NewCombo = true }, new HitCircle { StartTime = 3000 }, }); var patch = new OsuBeatmap { HitObjects = { (OsuHitObject)current.HitObjects[0], new HitCircle { StartTime = 2000 }, (OsuHitObject)current.HitObjects[1], } }; runTest(patch); } [Test] public void TestDeleteHitObject() { current.AddRange(new[] { new HitCircle { StartTime = 1000, NewCombo = true }, new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 3000 }, }); var patch = new OsuBeatmap { HitObjects = { (OsuHitObject)current.HitObjects[0], (OsuHitObject)current.HitObjects[2], } }; runTest(patch); } [Test] public void TestChangeStartTime() { current.AddRange(new[] { new HitCircle { StartTime = 1000, NewCombo = true }, new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 3000 }, }); var patch = new OsuBeatmap { HitObjects = { new HitCircle { StartTime = 500, NewCombo = true }, (OsuHitObject)current.HitObjects[1], (OsuHitObject)current.HitObjects[2], } }; runTest(patch); } [Test] public void TestChangeSample() { current.AddRange(new[] { new HitCircle { StartTime = 1000, NewCombo = true }, new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 3000 }, }); var patch = new OsuBeatmap { HitObjects = { (OsuHitObject)current.HitObjects[0], new HitCircle { StartTime = 2000, Samples = { new HitSampleInfo(HitSampleInfo.HIT_FINISH) } }, (OsuHitObject)current.HitObjects[2], } }; runTest(patch); } [Test] public void TestChangeSliderPath() { current.AddRange(new OsuHitObject[] { new HitCircle { StartTime = 1000, NewCombo = true }, new Slider { StartTime = 2000, Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(Vector2.One), new PathControlPoint(new Vector2(2), PathType.BEZIER), new PathControlPoint(new Vector2(3)), }, 50) }, new HitCircle { StartTime = 3000 }, }); var patch = new OsuBeatmap { HitObjects = { (OsuHitObject)current.HitObjects[0], new Slider { StartTime = 2000, Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero, PathType.BEZIER), new PathControlPoint(new Vector2(4)), new PathControlPoint(new Vector2(5)), }, 100) }, (OsuHitObject)current.HitObjects[2], } }; runTest(patch); } [Test] public void TestAddMultipleHitObjects() { current.AddRange(new[] { new HitCircle { StartTime = 1000, NewCombo = true }, new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 3000 }, }); var patch = new OsuBeatmap { HitObjects = { new HitCircle { StartTime = 500, NewCombo = true }, (OsuHitObject)current.HitObjects[0], new HitCircle { StartTime = 1500 }, (OsuHitObject)current.HitObjects[1], new HitCircle { StartTime = 2250 }, new HitCircle { StartTime = 2500 }, (OsuHitObject)current.HitObjects[2], new HitCircle { StartTime = 3500 }, } }; runTest(patch); } [Test] public void TestDeleteMultipleHitObjects() { current.AddRange(new[] { new HitCircle { StartTime = 500, NewCombo = true }, new HitCircle { StartTime = 1000 }, new HitCircle { StartTime = 1500 }, new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 2250 }, new HitCircle { StartTime = 2500 }, new HitCircle { StartTime = 3000 }, new HitCircle { StartTime = 3500 }, }); var patchedFirst = (HitCircle)current.HitObjects[1]; patchedFirst.NewCombo = true; var patch = new OsuBeatmap { HitObjects = { (OsuHitObject)current.HitObjects[1], (OsuHitObject)current.HitObjects[3], (OsuHitObject)current.HitObjects[6], } }; runTest(patch); } [Test] public void TestChangeSamplesOfMultipleHitObjects() { current.AddRange(new[] { new HitCircle { StartTime = 500, NewCombo = true }, new HitCircle { StartTime = 1000 }, new HitCircle { StartTime = 1500 }, new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 2250 }, new HitCircle { StartTime = 2500 }, new HitCircle { StartTime = 3000 }, new HitCircle { StartTime = 3500 }, }); var patch = new OsuBeatmap { HitObjects = { (OsuHitObject)current.HitObjects[0], new HitCircle { StartTime = 1000, Samples = { new HitSampleInfo(HitSampleInfo.HIT_FINISH) } }, (OsuHitObject)current.HitObjects[2], (OsuHitObject)current.HitObjects[3], new HitCircle { StartTime = 2250, Samples = { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) } }, (OsuHitObject)current.HitObjects[5], new HitCircle { StartTime = 3000, Samples = { new HitSampleInfo(HitSampleInfo.HIT_CLAP) } }, (OsuHitObject)current.HitObjects[7], } }; runTest(patch); } [Test] public void TestAddAndDeleteHitObjects() { current.AddRange(new[] { new HitCircle { StartTime = 500, NewCombo = true }, new HitCircle { StartTime = 1000 }, new HitCircle { StartTime = 1500 }, new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 2250 }, new HitCircle { StartTime = 2500 }, new HitCircle { StartTime = 3000 }, new HitCircle { StartTime = 3500 }, }); var patch = new OsuBeatmap { HitObjects = { new HitCircle { StartTime = 750, NewCombo = true }, (OsuHitObject)current.HitObjects[1], (OsuHitObject)current.HitObjects[4], (OsuHitObject)current.HitObjects[5], new HitCircle { StartTime = 2650 }, new HitCircle { StartTime = 2750 }, new HitCircle { StartTime = 4000 }, } }; runTest(patch); } [Test] public void TestChangeHitObjectAtSameTime() { current.AddRange(new[] { new HitCircle { StartTime = 500, Position = new Vector2(50), NewCombo = true }, new HitCircle { StartTime = 500, Position = new Vector2(100), NewCombo = true }, new HitCircle { StartTime = 500, Position = new Vector2(150), NewCombo = true }, new HitCircle { StartTime = 500, Position = new Vector2(200), NewCombo = true }, }); var patch = new OsuBeatmap { HitObjects = { new HitCircle { StartTime = 500, Position = new Vector2(150), NewCombo = true }, new HitCircle { StartTime = 500, Position = new Vector2(100), NewCombo = true }, new HitCircle { StartTime = 500, Position = new Vector2(50), NewCombo = true }, new HitCircle { StartTime = 500, Position = new Vector2(200), NewCombo = true }, } }; runTest(patch); } private void runTest(IBeatmap patch) { // Due to the method of testing, "patch" comes in without having been decoded via a beatmap decoder. // This causes issues because the decoder adds various default properties (e.g. new combo on first object, default samples). // To resolve "patch" into a sane state it is encoded and then re-decoded. patch = decode(encode(patch)); // Apply the patch. patcher.Patch(encode(current), encode(patch)); // Convert beatmaps to strings for assertion purposes. string currentStr = Encoding.ASCII.GetString(encode(current)); string patchStr = Encoding.ASCII.GetString(encode(patch)); Assert.That(currentStr, Is.EqualTo(patchStr)); } private byte[] encode(IBeatmap beatmap) { using (var encoded = new MemoryStream()) { using (var sw = new StreamWriter(encoded)) new LegacyBeatmapEncoder(beatmap, null).Encode(sw); return encoded.ToArray(); } } private IBeatmap decode(byte[] state) { using (var stream = new MemoryStream(state)) using (var reader = new LineBufferedReader(stream)) return Decoder.GetDecoder<Beatmap>(reader).Decode(reader); } } }