From 2b42dfb9cbc81f2d567f56bda28f4ebf2bcaf199 Mon Sep 17 00:00:00 2001 From: 63411 <62799417+molneya@users.noreply.github.com> Date: Tue, 28 Jun 2022 20:32:35 +0800 Subject: [PATCH 01/31] apply individualStrain decay only when a note appears in that column --- .../Difficulty/Skills/Strain.cs | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs index c2e532430c..74334c90e4 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -21,7 +21,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 1; - private readonly double[] holdEndTimes; + private readonly double[] startTimes; + private readonly double[] endTimes; private readonly double[] individualStrains; private double individualStrain; @@ -30,7 +31,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills public Strain(Mod[] mods, int totalColumns) : base(mods) { - holdEndTimes = new double[totalColumns]; + startTimes = new double[totalColumns]; + endTimes = new double[totalColumns]; individualStrains = new double[totalColumns]; overallStrain = 1; } @@ -38,32 +40,27 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills protected override double StrainValueOf(DifficultyHitObject current) { var maniaCurrent = (ManiaDifficultyHitObject)current; + double startTime = maniaCurrent.StartTime; double endTime = maniaCurrent.EndTime; int column = maniaCurrent.BaseObject.Column; - double closestEndTime = Math.Abs(endTime - maniaCurrent.LastObject.StartTime); // Lowest value we can assume with the current information - - double holdFactor = 1.0; // Factor to all additional strains in case something else is held - double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly bool isOverlapping = false; - // Fill up the holdEndTimes array - for (int i = 0; i < holdEndTimes.Length; ++i) + double closestEndTime = Math.Abs(endTime - startTime); // Lowest value we can assume with the current information + double holdFactor = 1.0; // Factor to all additional strains in case something else is held + double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly + + for (int i = 0; i < endTimes.Length; ++i) { // The current note is overlapped if a previous note or end is overlapping the current note body - isOverlapping |= Precision.DefinitelyBigger(holdEndTimes[i], maniaCurrent.StartTime, 1) && Precision.DefinitelyBigger(endTime, holdEndTimes[i], 1); + isOverlapping |= Precision.DefinitelyBigger(endTimes[i], startTime, 1) && Precision.DefinitelyBigger(endTime, endTimes[i], 1); // We give a slight bonus to everything if something is held meanwhile - if (Precision.DefinitelyBigger(holdEndTimes[i], endTime, 1)) + if (Precision.DefinitelyBigger(endTimes[i], endTime, 1)) holdFactor = 1.25; - closestEndTime = Math.Min(closestEndTime, Math.Abs(endTime - holdEndTimes[i])); - - // Decay individual strains - individualStrains[i] = applyDecay(individualStrains[i], current.DeltaTime, individual_decay_base); + closestEndTime = Math.Min(closestEndTime, Math.Abs(endTime - endTimes[i])); } - holdEndTimes[column] = endTime; - // The hold addition is given if there was an overlap, however it is only valid if there are no other note with a similar ending. // Releasing multiple notes is just as easy as releasing 1. Nerfs the hold addition by half if the closest release is release_threshold away. // holdAddition @@ -77,11 +74,19 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills if (isOverlapping) holdAddition = 1 / (1 + Math.Exp(0.5 * (release_threshold - closestEndTime))); - // Increase individual strain in own column + // Decay and increase individualStrains in own column + individualStrains[column] = applyDecay(individualStrains[column], startTime - startTimes[column], individual_decay_base); individualStrains[column] += 2.0 * holdFactor; + individualStrain = individualStrains[column]; - overallStrain = applyDecay(overallStrain, current.DeltaTime, overall_decay_base) + (1 + holdAddition) * holdFactor; + // Decay and increase overallStrain + overallStrain = applyDecay(overallStrain, current.DeltaTime, overall_decay_base); + overallStrain += (1 + holdAddition) * holdFactor; + + // Update startTimes and endTimes arrays + startTimes[column] = startTime; + endTimes[column] = endTime; return individualStrain + overallStrain - CurrentStrain; } From 4fdbf3ff247921129865b79236596bf2b1f6e66e Mon Sep 17 00:00:00 2001 From: 63411 <62799417+molneya@users.noreply.github.com> Date: Thu, 14 Jul 2022 15:07:58 +0800 Subject: [PATCH 02/31] individualStrain should be the hardest individualStrain column for notes in a chord --- osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs index 74334c90e4..657dfbb575 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -78,7 +78,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills individualStrains[column] = applyDecay(individualStrains[column], startTime - startTimes[column], individual_decay_base); individualStrains[column] += 2.0 * holdFactor; - individualStrain = individualStrains[column]; + // individualStrain should be the hardest individualStrain column for notes in a chord + individualStrain = maniaCurrent.DeltaTime == 0 ? Math.Max(individualStrain, individualStrains[column]) : individualStrains[column]; // Decay and increase overallStrain overallStrain = applyDecay(overallStrain, current.DeltaTime, overall_decay_base); From 3525dfb0f15242880092fde6ddc361b7e40b6c1d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 12 Aug 2022 01:17:33 +0200 Subject: [PATCH 03/31] added merging feature --- .../Edit/OsuSelectionHandler.cs | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 4a6d94f303..f9d4fbfc72 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -6,13 +6,16 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Utils; using osu.Game.Extensions; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -23,6 +26,12 @@ namespace osu.Game.Rulesets.Osu.Edit [Resolved(CanBeNull = true)] private IDistanceSnapProvider? snapProvider { get; set; } + [Resolved(CanBeNull = true)] + private EditorBeatmap? editorBeatmap { get; set; } + + [Resolved(CanBeNull = true)] + private IEditorChangeHandler? changeHandler { get; set; } + /// /// During a transform, the initial origin is stored so it can be used throughout the operation. /// @@ -322,5 +331,105 @@ namespace osu.Game.Rulesets.Osu.Edit private OsuHitObject[] selectedMovableObjects => SelectedItems.OfType() .Where(h => !(h is Spinner)) .ToArray(); + + /// + /// All osu! hitobjects which can be merged. + /// + private OsuHitObject[] selectedMergeableObjects => SelectedItems.OfType() + .Where(h => h is HitCircle or Slider) + .OrderBy(h => h.StartTime) + .ToArray(); + + private void mergeSelection() + { + if (editorBeatmap == null || changeHandler == null || selectedMergeableObjects.Length < 2) + return; + + changeHandler.BeginChange(); + + // Have an initial slider object. + var firstHitObject = selectedMergeableObjects[0]; + var mergedHitObject = firstHitObject as Slider ?? new Slider + { + StartTime = firstHitObject.StartTime, + Position = firstHitObject.Position, + NewCombo = firstHitObject.NewCombo, + SampleControlPoint = firstHitObject.SampleControlPoint, + }; + + if (mergedHitObject.Path.ControlPoints.Count == 0) + { + mergedHitObject.Path.ControlPoints.Add(new PathControlPoint(Vector2.Zero, PathType.Linear)); + } + + // Merge all the selected hit objects into one slider path. + bool lastCircle = firstHitObject is HitCircle; + + foreach (var selectedMergeableObject in selectedMergeableObjects.Skip(1)) + { + if (selectedMergeableObject is IHasPath hasPath) + { + var offset = lastCircle ? selectedMergeableObject.Position - mergedHitObject.Position : mergedHitObject.Path.ControlPoints[^1].Position; + float distanceToLastControlPoint = Vector2.Distance(mergedHitObject.Path.ControlPoints[^1].Position, offset); + + // Calculate the distance required to travel to the expected distance of the merging slider. + mergedHitObject.Path.ExpectedDistance.Value = mergedHitObject.Path.CalculatedDistance + distanceToLastControlPoint + hasPath.Path.Distance; + + // Remove the last control point if it sits exactly on the start of the next control point. + if (Precision.AlmostEquals(distanceToLastControlPoint, 0)) + { + mergedHitObject.Path.ControlPoints.RemoveAt(mergedHitObject.Path.ControlPoints.Count - 1); + } + + mergedHitObject.Path.ControlPoints.AddRange(hasPath.Path.ControlPoints.Select(o => new PathControlPoint(o.Position + offset, o.Type))); + lastCircle = false; + } + else + { + // Turn the last control point into a linear type if this is the first merging circle in a sequence, so the subsequent control points can be inherited path type. + if (!lastCircle) + { + mergedHitObject.Path.ControlPoints.Last().Type = PathType.Linear; + } + + mergedHitObject.Path.ControlPoints.Add(new PathControlPoint(selectedMergeableObject.Position - mergedHitObject.Position)); + mergedHitObject.Path.ExpectedDistance.Value = null; + lastCircle = true; + } + } + + // Make sure only the merged hit object is in the beatmap. + if (firstHitObject is Slider) + { + foreach (var selectedMergeableObject in selectedMergeableObjects.Skip(1)) + { + editorBeatmap.Remove(selectedMergeableObject); + } + } + else + { + foreach (var selectedMergeableObject in selectedMergeableObjects) + { + editorBeatmap.Remove(selectedMergeableObject); + } + + editorBeatmap.Add(mergedHitObject); + } + + // Make sure the merged hitobject is selected. + SelectedItems.Clear(); + SelectedItems.Add(mergedHitObject); + + changeHandler.EndChange(); + } + + protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) + { + foreach (var item in base.GetContextMenuItemsForSelection(selection)) + yield return item; + + if (selection.Count() > 1 && selection.All(o => o.Item is HitCircle or Slider)) + yield return new OsuMenuItem("Merge selection", MenuItemType.Destructive, mergeSelection); + } } } From 3abc333813218a832ef3f2c229af4da5c52abbbf Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 15 Aug 2022 17:18:55 +0200 Subject: [PATCH 04/31] added hotkey for merging selection --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index f9d4fbfc72..e3cd31f6a3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; @@ -18,6 +19,7 @@ using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components; using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit { @@ -62,6 +64,17 @@ namespace osu.Game.Rulesets.Osu.Edit referencePathTypes = null; } + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.Key == Key.M && e.ControlPressed && e.ShiftPressed) + { + mergeSelection(); + return true; + } + + return false; + } + public override bool HandleMovement(MoveSelectionEvent moveEvent) { var hitObjects = selectedMovableObjects; From d261be873439cf0618188c25784bfdf38c8a7452 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 15 Aug 2022 17:19:04 +0200 Subject: [PATCH 05/31] added visual tests --- .../Editor/TestSceneObjectMerging.cs | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs new file mode 100644 index 0000000000..8387f9c74c --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs @@ -0,0 +1,179 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Utils; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + public class TestSceneObjectMerging : TestSceneOsuEditor + { + [Test] + public void TestSimpleMerge() + { + HitCircle circle1 = null; + HitCircle circle2 = null; + + AddStep("select first two circles", () => + { + circle1 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle); + circle2 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle && h != circle1); + EditorClock.Seek(circle1.StartTime); + EditorBeatmap.SelectedHitObjects.Add(circle1); + EditorBeatmap.SelectedHitObjects.Add(circle2); + }); + + mergeSelection(); + + AddAssert("slider created", () => sliderCreatedFor( + (pos: circle1.Position, pathType: PathType.Linear), + (pos: circle2.Position, pathType: null))); + + AddStep("undo", () => Editor.Undo()); + AddAssert("merged objects restored", () => objectsRestored(circle1, circle2)); + } + + [Test] + public void TestMergeCircleSlider() + { + HitCircle circle1 = null; + Slider slider = null; + HitCircle circle2 = null; + + AddStep("select a circle, slider, circle", () => + { + circle1 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle); + slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider && h.StartTime > circle1.StartTime); + circle2 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle && h.StartTime > slider.StartTime); + EditorClock.Seek(circle1.StartTime); + EditorBeatmap.SelectedHitObjects.Add(circle1); + EditorBeatmap.SelectedHitObjects.Add(slider); + EditorBeatmap.SelectedHitObjects.Add(circle2); + }); + + mergeSelection(); + + AddAssert("slider created", () => + { + var controlPoints = slider.Path.ControlPoints; + (Vector2, PathType?)[] args = new (Vector2, PathType?)[controlPoints.Count + 2]; + args[0] = (circle1.Position, PathType.Linear); + + for (int i = 0; i < controlPoints.Count; i++) + { + args[i + 1] = (controlPoints[i].Position + slider.Position, i == controlPoints.Count - 1 ? PathType.Linear : controlPoints[i].Type); + } + + args[^1] = (circle2.Position, null); + return sliderCreatedFor(args); + }); + + AddStep("undo", () => Editor.Undo()); + AddAssert("merged objects restored", () => objectsRestored(circle1, slider, circle2)); + } + + [Test] + public void TestMergeSliderSlider() + { + Slider slider1 = null; + SliderPath slider1Path = null; + Slider slider2 = null; + + AddStep("select two sliders", () => + { + slider1 = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider); + slider1Path = new SliderPath(slider1.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray(), slider1.Path.ExpectedDistance.Value); + slider2 = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider && h.StartTime > slider1.StartTime); + EditorClock.Seek(slider1.StartTime); + EditorBeatmap.SelectedHitObjects.Add(slider1); + EditorBeatmap.SelectedHitObjects.Add(slider2); + }); + + mergeSelection(); + + AddAssert("slider created", () => + { + var controlPoints1 = slider1Path.ControlPoints; + var controlPoints2 = slider2.Path.ControlPoints; + (Vector2, PathType?)[] args = new (Vector2, PathType?)[controlPoints1.Count + controlPoints2.Count - 1]; + + for (int i = 0; i < controlPoints1.Count - 1; i++) + { + args[i] = (controlPoints1[i].Position + slider1.Position, controlPoints1[i].Type); + } + + for (int i = 0; i < controlPoints2.Count; i++) + { + args[i + controlPoints1.Count - 1] = (controlPoints2[i].Position + controlPoints1[^1].Position + slider1.Position, controlPoints2[i].Type); + } + + return sliderCreatedFor(args); + }); + + AddAssert("merged slider matches first slider", () => + { + var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First(); + return mergedSlider.HeadCircle.Samples.SequenceEqual(slider1.HeadCircle.Samples) + && mergedSlider.TailCircle.Samples.SequenceEqual(slider1.TailCircle.Samples) + && mergedSlider.Samples.SequenceEqual(slider1.Samples) + && mergedSlider.SampleControlPoint.IsRedundant(slider1.SampleControlPoint); + }); + + AddAssert("slider end is at same completion for last slider", () => + { + var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First(); + return Precision.AlmostEquals(mergedSlider.Path.Distance, slider1Path.CalculatedDistance + slider2.Path.Distance); + }); + } + + private void mergeSelection() + { + AddStep("merge selection", () => + { + InputManager.PressKey(Key.LControl); + InputManager.PressKey(Key.LShift); + InputManager.Key(Key.M); + InputManager.ReleaseKey(Key.LShift); + InputManager.ReleaseKey(Key.LControl); + }); + } + + private bool sliderCreatedFor(params (Vector2 pos, PathType? pathType)[] expectedControlPoints) + { + if (EditorBeatmap.SelectedHitObjects.Count != 1) + return false; + + var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First(); + int i = 0; + + foreach ((Vector2 pos, PathType? pathType) in expectedControlPoints) + { + var controlPoint = mergedSlider.Path.ControlPoints[i++]; + + if (!Precision.AlmostEquals(controlPoint.Position + mergedSlider.Position, pos) || controlPoint.Type != pathType) + return false; + } + + return true; + } + + private bool objectsRestored(params HitObject[] objects) + { + foreach (var hitObject in objects) + { + if (EditorBeatmap.HitObjects.Contains(hitObject)) + return false; + } + + return true; + } + } +} From b5e54113485d5c511bc179a67a2f0794cd78ec55 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 15 Aug 2022 18:07:55 +0200 Subject: [PATCH 06/31] remove copyright notice from new file --- osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs index 8387f9c74c..9b90c0ef05 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs @@ -1,6 +1,3 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - #nullable disable using System.Linq; From 7c2ada9b209f1a887e026aac48c9cadeb3d7905f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 15 Aug 2022 21:11:06 +0200 Subject: [PATCH 07/31] Revert "remove copyright notice from new file" This reverts commit b5e54113485d5c511bc179a67a2f0794cd78ec55. --- osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs index 9b90c0ef05..8387f9c74c 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + #nullable disable using System.Linq; From d140e0df14224da4163c9e5049aa3eec0bf5ce1d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 15 Aug 2022 21:15:23 +0200 Subject: [PATCH 08/31] remove nullable disable annotation --- .../Editor/TestSceneObjectMerging.cs | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs index 8387f9c74c..63e6760514 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Utils; @@ -19,8 +17,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] public void TestSimpleMerge() { - HitCircle circle1 = null; - HitCircle circle2 = null; + HitCircle? circle1 = null; + HitCircle? circle2 = null; AddStep("select first two circles", () => { @@ -33,20 +31,20 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor mergeSelection(); - AddAssert("slider created", () => sliderCreatedFor( + AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor( (pos: circle1.Position, pathType: PathType.Linear), (pos: circle2.Position, pathType: null))); AddStep("undo", () => Editor.Undo()); - AddAssert("merged objects restored", () => objectsRestored(circle1, circle2)); + AddAssert("merged objects restored", () => circle1 is not null && circle2 is not null && objectsRestored(circle1, circle2)); } [Test] public void TestMergeCircleSlider() { - HitCircle circle1 = null; - Slider slider = null; - HitCircle circle2 = null; + HitCircle? circle1 = null; + Slider? slider = null; + HitCircle? circle2 = null; AddStep("select a circle, slider, circle", () => { @@ -63,6 +61,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider created", () => { + if (circle1 is null || circle2 is null || slider is null) + return false; + var controlPoints = slider.Path.ControlPoints; (Vector2, PathType?)[] args = new (Vector2, PathType?)[controlPoints.Count + 2]; args[0] = (circle1.Position, PathType.Linear); @@ -77,15 +78,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); AddStep("undo", () => Editor.Undo()); - AddAssert("merged objects restored", () => objectsRestored(circle1, slider, circle2)); + AddAssert("merged objects restored", () => circle1 is not null && circle2 is not null && slider is not null && objectsRestored(circle1, slider, circle2)); } [Test] public void TestMergeSliderSlider() { - Slider slider1 = null; - SliderPath slider1Path = null; - Slider slider2 = null; + Slider? slider1 = null; + SliderPath? slider1Path = null; + Slider? slider2 = null; AddStep("select two sliders", () => { @@ -101,6 +102,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider created", () => { + if (slider1 is null || slider2 is null || slider1Path is null) + return false; + var controlPoints1 = slider1Path.ControlPoints; var controlPoints2 = slider2.Path.ControlPoints; (Vector2, PathType?)[] args = new (Vector2, PathType?)[controlPoints1.Count + controlPoints2.Count - 1]; @@ -121,14 +125,17 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("merged slider matches first slider", () => { var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First(); - return mergedSlider.HeadCircle.Samples.SequenceEqual(slider1.HeadCircle.Samples) - && mergedSlider.TailCircle.Samples.SequenceEqual(slider1.TailCircle.Samples) - && mergedSlider.Samples.SequenceEqual(slider1.Samples) - && mergedSlider.SampleControlPoint.IsRedundant(slider1.SampleControlPoint); + return slider1 is not null && mergedSlider.HeadCircle.Samples.SequenceEqual(slider1.HeadCircle.Samples) + && mergedSlider.TailCircle.Samples.SequenceEqual(slider1.TailCircle.Samples) + && mergedSlider.Samples.SequenceEqual(slider1.Samples) + && mergedSlider.SampleControlPoint.IsRedundant(slider1.SampleControlPoint); }); AddAssert("slider end is at same completion for last slider", () => { + if (slider1Path is null || slider2 is null) + return false; + var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First(); return Precision.AlmostEquals(mergedSlider.Path.Distance, slider1Path.CalculatedDistance + slider2.Path.Distance); }); From b33e0f5e1cd3421503eacfc6283210cd2be7c0d6 Mon Sep 17 00:00:00 2001 From: 63411 <62799417+molneya@users.noreply.github.com> Date: Tue, 16 Aug 2022 11:03:18 +0800 Subject: [PATCH 09/31] update comment and deltaTime check --- osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs index 657dfbb575..d5cee47178 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -78,8 +78,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills individualStrains[column] = applyDecay(individualStrains[column], startTime - startTimes[column], individual_decay_base); individualStrains[column] += 2.0 * holdFactor; - // individualStrain should be the hardest individualStrain column for notes in a chord - individualStrain = maniaCurrent.DeltaTime == 0 ? Math.Max(individualStrain, individualStrains[column]) : individualStrains[column]; + // For notes at the same time (in a chord), the individualStrain should be the hardest individualStrain out of those columns + individualStrain = maniaCurrent.DeltaTime <= 1 ? Math.Max(individualStrain, individualStrains[column]) : individualStrains[column]; // Decay and increase overallStrain overallStrain = applyDecay(overallStrain, current.DeltaTime, overall_decay_base); From ea50936d718bd21a78fea574811cc7b560a651f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Aug 2022 15:26:29 +0900 Subject: [PATCH 10/31] Fix slider ticks playing back at infinite rate while making changes to a slider in the editor --- osu.Game/Screens/Edit/Editor.cs | 19 ++++++------------- osu.Game/Screens/Edit/EditorBeatmap.cs | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 89f9aec5ee..90e912bd6d 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -225,8 +225,6 @@ namespace osu.Game.Screens.Edit dependencies.CacheAs(clock); AddInternal(clock); - clock.SeekingOrStopped.BindValueChanged(_ => updateSampleDisabledState()); - // todo: remove caching of this and consume via editorBeatmap? dependencies.Cache(beatDivisor); @@ -428,7 +426,12 @@ namespace osu.Game.Screens.Edit protected override void Update() { base.Update(); + clock.ProcessFrame(); + + samplePlaybackDisabled.Value = clock.SeekingOrStopped.Value + || currentScreen is not ComposeScreen + || editorBeatmap.UpdateInProgress; } public bool OnPressed(KeyBindingPressEvent e) @@ -822,16 +825,10 @@ namespace osu.Game.Screens.Edit } finally { - updateSampleDisabledState(); rebindClipboardBindables(); } } - private void updateSampleDisabledState() - { - samplePlaybackDisabled.Value = clock.SeekingOrStopped.Value || !(currentScreen is ComposeScreen); - } - private void seek(UIEvent e, int direction) { double amount = e.ShiftPressed ? 4 : 1; @@ -936,11 +933,7 @@ namespace osu.Game.Screens.Edit protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleSwitchToExistingDifficulty(nextBeatmap, GetState(nextBeatmap.Ruleset)); - private void cancelExit() - { - updateSampleDisabledState(); - loader?.CancelPendingDifficultySwitch(); - } + private void cancelExit() => loader?.CancelPendingDifficultySwitch(); public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 96425e8bc8..b9b0879fa4 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -10,6 +10,7 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Legacy; @@ -22,6 +23,15 @@ namespace osu.Game.Screens.Edit { public class EditorBeatmap : TransactionalCommitComponent, IBeatmap, IBeatSnapProvider { + /// + /// While performing updates on hitobjects, this will momentarily become true. + /// + /// + /// This is intended to be used to avoid performing operations (like playback of samples) + /// while mutating hitobjects. + /// + public bool UpdateInProgress { get; private set; } + /// /// Invoked when a is added to this . /// @@ -225,8 +235,12 @@ namespace osu.Game.Screens.Edit { // updates are debounced regardless of whether a batch is active. batchPendingUpdates.Add(hitObject); + + advertiseUpdateInProgress(); } + private ScheduledDelegate updateCompleteDelegate; + /// /// Update all hit objects with potentially changed difficulty or control point data. /// @@ -234,6 +248,8 @@ namespace osu.Game.Screens.Edit { foreach (var h in HitObjects) batchPendingUpdates.Add(h); + + advertiseUpdateInProgress(); } /// @@ -333,6 +349,15 @@ namespace osu.Game.Screens.Edit /// public void Clear() => RemoveRange(HitObjects.ToArray()); + private void advertiseUpdateInProgress() + { + UpdateInProgress = true; + + // Debounce is arbitrarily high enough to avoid flip-flopping the value each other frame. + updateCompleteDelegate?.Cancel(); + updateCompleteDelegate = Scheduler.AddDelayed(() => UpdateInProgress = false, 50); + } + private void processHitObject(HitObject hitObject) => hitObject.ApplyDefaults(ControlPointInfo, PlayableBeatmap.Difficulty); private void trackStartTime(HitObject hitObject) From e636fcd9b8312be89cf884ca98066becf4720288 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Aug 2022 15:35:32 +0900 Subject: [PATCH 11/31] Use DI'd components from parent class rather than fetching locally --- .../Edit/OsuSelectionHandler.cs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index e3cd31f6a3..44fbbd9591 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -16,7 +16,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; -using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components; using osuTK; using osuTK.Input; @@ -28,12 +27,6 @@ namespace osu.Game.Rulesets.Osu.Edit [Resolved(CanBeNull = true)] private IDistanceSnapProvider? snapProvider { get; set; } - [Resolved(CanBeNull = true)] - private EditorBeatmap? editorBeatmap { get; set; } - - [Resolved(CanBeNull = true)] - private IEditorChangeHandler? changeHandler { get; set; } - /// /// During a transform, the initial origin is stored so it can be used throughout the operation. /// @@ -355,10 +348,10 @@ namespace osu.Game.Rulesets.Osu.Edit private void mergeSelection() { - if (editorBeatmap == null || changeHandler == null || selectedMergeableObjects.Length < 2) + if (EditorBeatmap == null || ChangeHandler == null || selectedMergeableObjects.Length < 2) return; - changeHandler.BeginChange(); + ChangeHandler.BeginChange(); // Have an initial slider object. var firstHitObject = selectedMergeableObjects[0]; @@ -416,24 +409,24 @@ namespace osu.Game.Rulesets.Osu.Edit { foreach (var selectedMergeableObject in selectedMergeableObjects.Skip(1)) { - editorBeatmap.Remove(selectedMergeableObject); + EditorBeatmap.Remove(selectedMergeableObject); } } else { foreach (var selectedMergeableObject in selectedMergeableObjects) { - editorBeatmap.Remove(selectedMergeableObject); + EditorBeatmap.Remove(selectedMergeableObject); } - editorBeatmap.Add(mergedHitObject); + EditorBeatmap.Add(mergedHitObject); } // Make sure the merged hitobject is selected. SelectedItems.Clear(); SelectedItems.Add(mergedHitObject); - changeHandler.EndChange(); + ChangeHandler.EndChange(); } protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) From ecb9351babd55217c0b28a5e20c3274be44669b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Aug 2022 15:36:43 +0900 Subject: [PATCH 12/31] Remove unnecessary null pre-checks --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 44fbbd9591..363a191e34 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -348,10 +348,10 @@ namespace osu.Game.Rulesets.Osu.Edit private void mergeSelection() { - if (EditorBeatmap == null || ChangeHandler == null || selectedMergeableObjects.Length < 2) + if (selectedMergeableObjects.Length < 2) return; - ChangeHandler.BeginChange(); + ChangeHandler?.BeginChange(); // Have an initial slider object. var firstHitObject = selectedMergeableObjects[0]; @@ -426,7 +426,7 @@ namespace osu.Game.Rulesets.Osu.Edit SelectedItems.Clear(); SelectedItems.Add(mergedHitObject); - ChangeHandler.EndChange(); + ChangeHandler?.EndChange(); } protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) From 7cc9fdbaa0d19f847cdc57264bc6652746ac91b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Aug 2022 15:37:38 +0900 Subject: [PATCH 13/31] Simplify context menu check by using existing mergeable object list --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 363a191e34..663bf03a00 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -335,7 +335,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// All osu! hitobjects which can be moved/rotated/scaled. /// private OsuHitObject[] selectedMovableObjects => SelectedItems.OfType() - .Where(h => !(h is Spinner)) + .Where(h => h is not Spinner) .ToArray(); /// @@ -434,7 +434,7 @@ namespace osu.Game.Rulesets.Osu.Edit foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; - if (selection.Count() > 1 && selection.All(o => o.Item is HitCircle or Slider)) + if (selectedMergeableObjects.Length > 1) yield return new OsuMenuItem("Merge selection", MenuItemType.Destructive, mergeSelection); } } From 0833a6fb9a3dec55c464f211340f1d51a1bbdb97 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Aug 2022 15:38:13 +0900 Subject: [PATCH 14/31] Avoid multiple iteration of LINQ query using a local --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 663bf03a00..f3c0a05bc2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -348,13 +348,15 @@ namespace osu.Game.Rulesets.Osu.Edit private void mergeSelection() { - if (selectedMergeableObjects.Length < 2) + var mergeableObjects = selectedMergeableObjects; + + if (mergeableObjects.Length < 2) return; ChangeHandler?.BeginChange(); // Have an initial slider object. - var firstHitObject = selectedMergeableObjects[0]; + var firstHitObject = mergeableObjects[0]; var mergedHitObject = firstHitObject as Slider ?? new Slider { StartTime = firstHitObject.StartTime, @@ -371,7 +373,7 @@ namespace osu.Game.Rulesets.Osu.Edit // Merge all the selected hit objects into one slider path. bool lastCircle = firstHitObject is HitCircle; - foreach (var selectedMergeableObject in selectedMergeableObjects.Skip(1)) + foreach (var selectedMergeableObject in mergeableObjects.Skip(1)) { if (selectedMergeableObject is IHasPath hasPath) { @@ -407,14 +409,14 @@ namespace osu.Game.Rulesets.Osu.Edit // Make sure only the merged hit object is in the beatmap. if (firstHitObject is Slider) { - foreach (var selectedMergeableObject in selectedMergeableObjects.Skip(1)) + foreach (var selectedMergeableObject in mergeableObjects.Skip(1)) { EditorBeatmap.Remove(selectedMergeableObject); } } else { - foreach (var selectedMergeableObject in selectedMergeableObjects) + foreach (var selectedMergeableObject in mergeableObjects) { EditorBeatmap.Remove(selectedMergeableObject); } From 43e471c2a5832621ca91a4eea98650511a678bff Mon Sep 17 00:00:00 2001 From: StanR Date: Tue, 16 Aug 2022 16:12:13 +0300 Subject: [PATCH 15/31] Clamp effective miss count to maximum amount of possible braks --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 3c82c2dc33..fb0eff5cb2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -270,8 +270,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo); } - // Clamp miss count since it's derived from combo and can be higher than total hits and that breaks some calculations - comboBasedMissCount = Math.Min(comboBasedMissCount, totalHits); + // Clamp miss count to maximum amount of possible breaks + comboBasedMissCount = Math.Min(comboBasedMissCount, countOk + countMeh + countMiss); return Math.Max(countMiss, comboBasedMissCount); } From d021218d78cf102b45bf52d3670e5be9fc507a64 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 16 Aug 2022 23:05:35 +0200 Subject: [PATCH 16/31] added test for objects which cant be merged --- .../Editor/TestSceneObjectMerging.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs index 63e6760514..b68231ce64 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs @@ -141,6 +141,33 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); } + [Test] + public void TestNonMerge() + { + HitCircle? circle1 = null; + HitCircle? circle2 = null; + Spinner? spinner = null; + + AddStep("select first two circles and spinner", () => + { + circle1 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle); + circle2 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle && h != circle1); + spinner = (Spinner)EditorBeatmap.HitObjects.First(h => h is Spinner); + EditorClock.Seek(spinner.StartTime); + EditorBeatmap.SelectedHitObjects.Add(circle1); + EditorBeatmap.SelectedHitObjects.Add(circle2); + EditorBeatmap.SelectedHitObjects.Add(spinner); + }); + + mergeSelection(); + + AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor( + (pos: circle1.Position, pathType: PathType.Linear), + (pos: circle2.Position, pathType: null))); + + AddAssert("spinner not merged", () => EditorBeatmap.HitObjects.Contains(spinner)); + } + private void mergeSelection() { AddStep("merge selection", () => From 37799e3b31edc2fe4b71ce8308fbceecb2ec8646 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Aug 2022 13:20:24 +0900 Subject: [PATCH 17/31] Allow preparing preview point without looping --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 4 ++-- osu.Game/Screens/Menu/MainMenu.cs | 2 +- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 548341cc77..a39766abe1 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -134,6 +134,6 @@ namespace osu.Game.Beatmaps /// /// Reads the correct track restart point from beatmap metadata and sets looping to enabled. /// - void PrepareTrackForPreviewLooping(); + void PrepareTrackForPreview(bool looping); } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 16464932e0..301610ee58 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -110,9 +110,9 @@ namespace osu.Game.Beatmaps public Track LoadTrack() => track = GetBeatmapTrack() ?? GetVirtualTrack(1000); - public void PrepareTrackForPreviewLooping() + public void PrepareTrackForPreview(bool looping) { - Track.Looping = true; + Track.Looping = looping; Track.RestartPoint = Metadata.PreviewTime; if (Track.RestartPoint == -1) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 066a37055c..0071ada05a 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -192,7 +192,7 @@ namespace osu.Game.Screens.Menu // presume the track is the current beatmap's track. not sure how correct this assumption is but it has worked until now. if (!track.IsRunning) { - Beatmap.Value.PrepareTrackForPreviewLooping(); + Beatmap.Value.PrepareTrackForPreview(false); track.Restart(); } } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 25f2a94a3c..03216180fb 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -485,7 +485,7 @@ namespace osu.Game.Screens.OnlinePlay.Match if (track != null) { - Beatmap.Value.PrepareTrackForPreviewLooping(); + Beatmap.Value.PrepareTrackForPreview(true); music?.EnsurePlayingSomething(); } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 33ff31857f..0c2ca6d4af 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -683,7 +683,7 @@ namespace osu.Game.Screens.Select } private void ensureTrackLooping(IWorkingBeatmap beatmap, TrackChangeDirection changeDirection) - => beatmap.PrepareTrackForPreviewLooping(); + => beatmap.PrepareTrackForPreview(true); public override bool OnBackButton() { From d9346abb9c3b390357d060441fc7020915573c9a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Aug 2022 13:20:40 +0900 Subject: [PATCH 18/31] Tweak intro timings more and ensure non-theme tracks play from preview point --- osu.Game/Screens/Menu/IntroCircles.cs | 10 +++------- osu.Game/Screens/Menu/IntroScreen.cs | 14 ++++++++++---- osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroCircles.cs b/osu.Game/Screens/Menu/IntroCircles.cs index 5f481ed00e..7a4bdb231f 100644 --- a/osu.Game/Screens/Menu/IntroCircles.cs +++ b/osu.Game/Screens/Menu/IntroCircles.cs @@ -19,11 +19,9 @@ namespace osu.Game.Screens.Menu protected override string BeatmapFile => "circles.osz"; - public const double TRACK_START_DELAY_NON_THEMED = 1000; - private const double track_start_delay_themed = 600; + public const double TRACK_START_DELAY = 600; private const double delay_for_menu = 2900; - private const double delay_step_one = 2300; private Sample welcome; @@ -47,16 +45,14 @@ namespace osu.Game.Screens.Menu { welcome?.Play(); - double trackStartDelay = UsingThemedIntro ? track_start_delay_themed : TRACK_START_DELAY_NON_THEMED; - Scheduler.AddDelayed(delegate { StartTrack(); PrepareMenuLoad(); - Scheduler.AddDelayed(LoadMenu, delay_for_menu - trackStartDelay); - }, trackStartDelay); + Scheduler.AddDelayed(LoadMenu, delay_for_menu - TRACK_START_DELAY); + }, TRACK_START_DELAY); logo.ScaleTo(1); logo.FadeIn(); diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index bf713997f7..bd49eba663 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -276,12 +276,18 @@ namespace osu.Game.Screens.Menu { var drawableTrack = musicController.CurrentTrack; - drawableTrack.Start(); - if (!UsingThemedIntro) { - drawableTrack.VolumeTo(0).Then() - .VolumeTo(1, 2000, Easing.OutQuint); + initialBeatmap.PrepareTrackForPreview(false); + + drawableTrack.VolumeTo(0); + drawableTrack.Restart(); + drawableTrack.VolumeTo(1, 2200, Easing.InCubic); + } + else + + { + drawableTrack.Restart(); } } diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 4ec79b852a..d777f78df2 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -88,7 +88,7 @@ namespace osu.Game.Screens.Menu // If the user has requested no theme, fallback to the same intro voice and delay as IntroCircles. // The triangles intro voice and theme are combined which makes it impossible to use. welcome?.Play(); - Scheduler.AddDelayed(StartTrack, IntroCircles.TRACK_START_DELAY_NON_THEMED); + Scheduler.AddDelayed(StartTrack, IntroCircles.TRACK_START_DELAY); } else StartTrack(); From 0ff5547b8356fe9e06f1c3dc1325f2073bdfd608 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Aug 2022 13:35:44 +0900 Subject: [PATCH 19/31] Make `changeHandler` optional in `convertToStream` method to match new implementation --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index bd11cc826f..794551dab7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -251,13 +251,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void convertToStream() { - if (editorBeatmap == null || changeHandler == null || beatDivisor == null) + if (editorBeatmap == null || beatDivisor == null) return; var timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(HitObject.StartTime); double streamSpacing = timingPoint.BeatLength / beatDivisor.Value; - changeHandler.BeginChange(); + changeHandler?.BeginChange(); int i = 0; double time = HitObject.StartTime; @@ -292,7 +292,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders editorBeatmap.Remove(HitObject); - changeHandler.EndChange(); + changeHandler?.EndChange(); } public override MenuItem[] ContextMenuItems => new MenuItem[] From 8b5ac55fca7b2cb3a5bf6a826de7f461aee14b3e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 17 Aug 2022 13:48:06 +0900 Subject: [PATCH 20/31] Remove newline --- osu.Game/Screens/Menu/IntroScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index bd49eba663..a07ebb463a 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -285,7 +285,6 @@ namespace osu.Game.Screens.Menu drawableTrack.VolumeTo(1, 2200, Easing.InCubic); } else - { drawableTrack.Restart(); } From 8ce50e98a6ef52132350f77f186d9c01dec2b681 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Aug 2022 14:04:55 +0900 Subject: [PATCH 21/31] Move delegate debounce logic to `Editor` itself --- osu.Game/Screens/Edit/Editor.cs | 21 ++++++++++++++++++++- osu.Game/Screens/Edit/EditorBeatmap.cs | 24 ++++++++---------------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 90e912bd6d..9cf0a0fc0c 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -24,6 +24,7 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Framework.Threading; using osu.Framework.Timing; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -231,6 +232,8 @@ namespace osu.Game.Screens.Edit AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, loadableBeatmap.GetSkin(), loadableBeatmap.BeatmapInfo)); dependencies.CacheAs(editorBeatmap); + editorBeatmap.UpdateInProgress.BindValueChanged(updateInProgress); + canSave = editorBeatmap.BeatmapInfo.Ruleset.CreateInstance() is ILegacyRuleset; if (canSave) @@ -431,7 +434,7 @@ namespace osu.Game.Screens.Edit samplePlaybackDisabled.Value = clock.SeekingOrStopped.Value || currentScreen is not ComposeScreen - || editorBeatmap.UpdateInProgress; + || temporaryMuteFromUpdateInProgress; } public bool OnPressed(KeyBindingPressEvent e) @@ -717,6 +720,22 @@ namespace osu.Game.Screens.Edit this.Exit(); } + #region Mute from update application + + private ScheduledDelegate temporaryMuteRestorationDelegate; + private bool temporaryMuteFromUpdateInProgress; + + private void updateInProgress(ValueChangedEvent obj) + { + temporaryMuteFromUpdateInProgress = true; + + // Debounce is arbitrarily high enough to avoid flip-flopping the value each other frame. + temporaryMuteRestorationDelegate?.Cancel(); + temporaryMuteRestorationDelegate = Scheduler.AddDelayed(() => temporaryMuteFromUpdateInProgress = false, 50); + } + + #endregion + #region Clipboard support private EditorMenuItem cutMenuItem; diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index b9b0879fa4..6154485bc2 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -10,7 +10,6 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Legacy; @@ -24,13 +23,15 @@ namespace osu.Game.Screens.Edit public class EditorBeatmap : TransactionalCommitComponent, IBeatmap, IBeatSnapProvider { /// - /// While performing updates on hitobjects, this will momentarily become true. + /// Will become true when a new update is queued, and false when all updates have been applied. /// /// /// This is intended to be used to avoid performing operations (like playback of samples) /// while mutating hitobjects. /// - public bool UpdateInProgress { get; private set; } + public IBindable UpdateInProgress => updateInProgress; + + private readonly BindableBool updateInProgress = new BindableBool(); /// /// Invoked when a is added to this . @@ -236,11 +237,9 @@ namespace osu.Game.Screens.Edit // updates are debounced regardless of whether a batch is active. batchPendingUpdates.Add(hitObject); - advertiseUpdateInProgress(); + updateInProgress.Value = true; } - private ScheduledDelegate updateCompleteDelegate; - /// /// Update all hit objects with potentially changed difficulty or control point data. /// @@ -249,7 +248,7 @@ namespace osu.Game.Screens.Edit foreach (var h in HitObjects) batchPendingUpdates.Add(h); - advertiseUpdateInProgress(); + updateInProgress.Value = true; } /// @@ -342,6 +341,8 @@ namespace osu.Game.Screens.Edit foreach (var h in deletes) HitObjectRemoved?.Invoke(h); foreach (var h in inserts) HitObjectAdded?.Invoke(h); foreach (var h in updates) HitObjectUpdated?.Invoke(h); + + updateInProgress.Value = false; } /// @@ -349,15 +350,6 @@ namespace osu.Game.Screens.Edit /// public void Clear() => RemoveRange(HitObjects.ToArray()); - private void advertiseUpdateInProgress() - { - UpdateInProgress = true; - - // Debounce is arbitrarily high enough to avoid flip-flopping the value each other frame. - updateCompleteDelegate?.Cancel(); - updateCompleteDelegate = Scheduler.AddDelayed(() => UpdateInProgress = false, 50); - } - private void processHitObject(HitObject hitObject) => hitObject.ApplyDefaults(ControlPointInfo, PlayableBeatmap.Difficulty); private void trackStartTime(HitObject hitObject) From 6b9dec599698d8ee371837eb47b84e6b182d165a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Aug 2022 14:32:21 +0900 Subject: [PATCH 22/31] Restore original event flow to allow for `OnSuspend` case to work correctly --- osu.Game/Screens/Edit/Editor.cs | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 9cf0a0fc0c..a7cbe1f1ad 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -226,6 +226,8 @@ namespace osu.Game.Screens.Edit dependencies.CacheAs(clock); AddInternal(clock); + clock.SeekingOrStopped.BindValueChanged(_ => updateSampleDisabledState()); + // todo: remove caching of this and consume via editorBeatmap? dependencies.Cache(beatDivisor); @@ -429,12 +431,7 @@ namespace osu.Game.Screens.Edit protected override void Update() { base.Update(); - clock.ProcessFrame(); - - samplePlaybackDisabled.Value = clock.SeekingOrStopped.Value - || currentScreen is not ComposeScreen - || temporaryMuteFromUpdateInProgress; } public bool OnPressed(KeyBindingPressEvent e) @@ -728,10 +725,15 @@ namespace osu.Game.Screens.Edit private void updateInProgress(ValueChangedEvent obj) { temporaryMuteFromUpdateInProgress = true; + updateSampleDisabledState(); // Debounce is arbitrarily high enough to avoid flip-flopping the value each other frame. temporaryMuteRestorationDelegate?.Cancel(); - temporaryMuteRestorationDelegate = Scheduler.AddDelayed(() => temporaryMuteFromUpdateInProgress = false, 50); + temporaryMuteRestorationDelegate = Scheduler.AddDelayed(() => + { + temporaryMuteFromUpdateInProgress = false; + updateSampleDisabledState(); + }, 50); } #endregion @@ -844,10 +846,18 @@ namespace osu.Game.Screens.Edit } finally { + updateSampleDisabledState(); rebindClipboardBindables(); } } + private void updateSampleDisabledState() + { + samplePlaybackDisabled.Value = clock.SeekingOrStopped.Value + || currentScreen is not ComposeScreen + || temporaryMuteFromUpdateInProgress; + } + private void seek(UIEvent e, int direction) { double amount = e.ShiftPressed ? 4 : 1; @@ -952,7 +962,11 @@ namespace osu.Game.Screens.Edit protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleSwitchToExistingDifficulty(nextBeatmap, GetState(nextBeatmap.Ruleset)); - private void cancelExit() => loader?.CancelPendingDifficultySwitch(); + private void cancelExit() + { + updateSampleDisabledState(); + loader?.CancelPendingDifficultySwitch(); + } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); From 21b364cd7758f9574b94f3bdd97970e905c6bd18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Aug 2022 14:48:00 +0900 Subject: [PATCH 23/31] Fix nullref in tests as `initialBeatmap` may be null --- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index a07ebb463a..409c7d6c8d 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -278,7 +278,7 @@ namespace osu.Game.Screens.Menu if (!UsingThemedIntro) { - initialBeatmap.PrepareTrackForPreview(false); + initialBeatmap?.PrepareTrackForPreview(false); drawableTrack.VolumeTo(0); drawableTrack.Restart(); From 7191fbb6d615c327b3a67dfe3a871fa5d0ba8fbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Aug 2022 16:40:07 +0900 Subject: [PATCH 24/31] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 247140ceef..74e5b49167 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c17d45e84a..c3c9834724 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 5bfb53bc9d..da48550efb 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From a5ac69a5549a11c4d12f2614a7afe589e438a136 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Aug 2022 16:42:34 +0900 Subject: [PATCH 25/31] Update various dependencies --- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game/osu.Game.csproj | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 4349d25cb3..36c40c0fe2 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 7615b3e8be..65679deb01 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c3c9834724..d67f8415e7 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,9 +23,9 @@ - - - + + + @@ -35,13 +35,13 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + - + From 3a0017c87b31a05b3b872f35793027356cb4e063 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 17 Aug 2022 17:07:06 +0900 Subject: [PATCH 26/31] Fix flaky quick retry test --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index a38089e023..b0d7eadaa7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -432,7 +432,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("restart completed", () => getCurrentPlayer() != null && getCurrentPlayer() != previousPlayer); AddStep("release quick retry key", () => InputManager.ReleaseKey(Key.Tilde)); - AddUntilStep("wait for load", () => getCurrentPlayer()?.LoadedBeatmapSuccessfully == true); + AddUntilStep("wait for player", () => getCurrentPlayer()?.LoadState == LoadState.Ready); AddUntilStep("time reached zero", () => getCurrentPlayer()?.GameplayClockContainer.CurrentTime > 0); AddUntilStep("skip button not visible", () => !checkSkipButtonVisible()); From 243fe56b1dc13987b12203537db12ab9a559ff3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Aug 2022 17:11:17 +0900 Subject: [PATCH 27/31] Realm bump for props --- osu.Android.props | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 74e5b49167..6dbc6cc377 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -56,6 +56,6 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index da48550efb..463af1143f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -89,6 +89,6 @@ - + From fca076b9886f8552797e24b23c9f52603c718e5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Aug 2022 17:17:22 +0900 Subject: [PATCH 28/31] Fix edge case of realm backup cascading failure --- osu.Game/Database/RealmAccess.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index cdaf35a1fb..dd219cc1aa 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -902,7 +902,14 @@ namespace osu.Game.Database { using (var source = storage.GetStream(Filename, mode: FileMode.Open)) using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) + { + // source may not exist. + if (source == null) + return; + source.CopyTo(destination); + } + return; } catch (IOException) From e1e6be039a234a3d0ba2cd44588a0cd0881dac0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Aug 2022 18:20:47 +0900 Subject: [PATCH 29/31] Don't create destination stream if backup source doesn't exist --- osu.Game/Database/RealmAccess.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index dd219cc1aa..0f2e724567 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -901,13 +901,13 @@ namespace osu.Game.Database try { using (var source = storage.GetStream(Filename, mode: FileMode.Open)) - using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) { // source may not exist. if (source == null) return; - source.CopyTo(destination); + using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) + source.CopyTo(destination); } return; From 203b8b22b90216a26a02ae69294d52a41a248711 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 17 Aug 2022 19:02:13 +0900 Subject: [PATCH 30/31] Adjust tests --- .../ManiaDifficultyCalculatorTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs index 9e4db1f1c9..4ae6cb9c7c 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs @@ -16,11 +16,11 @@ namespace osu.Game.Rulesets.Mania.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; - [TestCase(2.3449735700206298d, 242, "diffcalc-test")] + [TestCase(2.3493769750220914d, 242, "diffcalc-test")] public void Test(double expectedStarRating, int expectedMaxCombo, string name) => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(2.7879104989252959d, 242, "diffcalc-test")] + [TestCase(2.797245912537965d, 242, "diffcalc-test")] public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new ManiaModDoubleTime()); From f381bc91159db110cd5535bf46d2d0d878e93822 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 17 Aug 2022 19:03:48 +0900 Subject: [PATCH 31/31] Add explanatory comment --- osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs index d5cee47178..2c7c84de97 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -89,6 +89,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills startTimes[column] = startTime; endTimes[column] = endTime; + // By subtracting CurrentStrain, this skill effectively only considers the maximum strain of any one hitobject within each strain section. return individualStrain + overallStrain - CurrentStrain; }