From e4cfa7c86f793042c311e706ec1c70fcf86a1a9f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 17 Sep 2024 11:27:23 +0200 Subject: [PATCH 01/86] Add view menu toggle for sample points --- osu.Game/Configuration/OsuConfigManager.cs | 4 +++- osu.Game/Localisation/EditorStrings.cs | 5 +++++ .../Components/Timeline/SamplePointPiece.cs | 15 ++++++++++++++- osu.Game/Screens/Edit/Editor.cs | 6 ++++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 8d6c244b35..ae2025fb50 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -205,6 +205,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true); SetDefault(OsuSetting.EditorTimelineShowTicks, true); + SetDefault(OsuSetting.EditorTimelineShowSamples, true); SetDefault(OsuSetting.AlwaysShowHoldForMenuButton, false); } @@ -431,6 +432,7 @@ namespace osu.Game.Configuration HideCountryFlags, EditorTimelineShowTimingChanges, EditorTimelineShowTicks, - AlwaysShowHoldForMenuButton + AlwaysShowHoldForMenuButton, + EditorTimelineShowSamples } } diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index bcffc18d4d..f72a0f4a26 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -139,6 +139,11 @@ namespace osu.Game.Localisation /// public static LocalisableString TimelineShowTicks => new TranslatableString(getKey(@"timeline_show_ticks"), @"Show ticks"); + /// + /// "Show samples" + /// + public static LocalisableString TimelineShowSamples => new TranslatableString(getKey(@"timeline_show_samples"), @"Show samples"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index a8cf8723f2..3843d56d06 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -16,6 +16,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Audio; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; @@ -40,6 +41,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private Editor? editor { get; set; } + private Bindable samplesVisible = null!; + public SamplePointPiece(HitObject hitObject) { HitObject = hitObject; @@ -53,13 +56,23 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected virtual double GetTime() => HitObject is IHasRepeats r ? HitObject.StartTime + r.Duration / r.SpanCount() / 2 : HitObject.StartTime; [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { HitObject.DefaultsApplied += _ => updateText(); updateText(); if (editor != null) editor.ShowSampleEditPopoverRequested += onShowSampleEditPopoverRequested; + + samplesVisible = config.GetBindable(OsuSetting.EditorTimelineShowSamples); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + samplesVisible.BindValueChanged(visible => this.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint)); + this.FadeTo(samplesVisible.Value ? 1 : 0); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 9bb91af806..b059557b28 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -215,6 +215,7 @@ namespace osu.Game.Screens.Edit private Bindable editorLimitedDistanceSnap; private Bindable editorTimelineShowTimingChanges; private Bindable editorTimelineShowTicks; + private Bindable editorTimelineShowSamples; /// /// This controls the opacity of components like the timelines, sidebars, etc. @@ -323,6 +324,7 @@ namespace osu.Game.Screens.Edit editorLimitedDistanceSnap = config.GetBindable(OsuSetting.EditorLimitedDistanceSnap); editorTimelineShowTimingChanges = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); editorTimelineShowTicks = config.GetBindable(OsuSetting.EditorTimelineShowTicks); + editorTimelineShowSamples = config.GetBindable(OsuSetting.EditorTimelineShowSamples); AddInternal(new OsuContextMenuContainer { @@ -388,6 +390,10 @@ namespace osu.Game.Screens.Edit { State = { BindTarget = editorTimelineShowTicks } }, + new ToggleMenuItem(EditorStrings.TimelineShowSamples) + { + State = { BindTarget = editorTimelineShowSamples } + } ] }, new BackgroundDimMenuItem(editorBackgroundDim), From 34087a0f1a303d90bf443fd2d4b71e07049ee450 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 19 Sep 2024 16:20:26 +0200 Subject: [PATCH 02/86] Reduce sample point pieces to pink dot if zoomed out --- .../Timeline/HitObjectPointPiece.cs | 4 ++- .../Components/Timeline/SamplePointPiece.cs | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs index 4b357d3a62..76323ac08c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs @@ -17,6 +17,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { protected OsuSpriteText Label { get; private set; } + protected Container LabelContainer { get; private set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -26,7 +28,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline InternalChildren = new Drawable[] { - new Container + LabelContainer = new Container { AutoSizeAxes = Axes.X, Height = 16, diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 3843d56d06..1638700735 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -41,6 +41,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private Editor? editor { get; set; } + [Resolved] + private Timeline? timeline { get; set; } + private Bindable samplesVisible = null!; public SamplePointPiece(HitObject hitObject) @@ -59,6 +62,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void load(OsuConfigManager config) { HitObject.DefaultsApplied += _ => updateText(); + Label.AllowMultiline = false; + LabelContainer.AutoSizeAxes = Axes.None; updateText(); if (editor != null) @@ -75,6 +80,30 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline this.FadeTo(samplesVisible.Value ? 1 : 0); } + private float lastZoom; + private const float zoom_threshold = 40f; + + protected override void Update() + { + base.Update(); + + // Retract visual state if the timeline is zoomed out too far. + if (timeline is null || timeline.Zoom == lastZoom) return; + + lastZoom = timeline.Zoom; + + if (timeline.Zoom < zoom_threshold) + { + Label.FadeOut(200, Easing.OutQuint); + LabelContainer.ResizeWidthTo(16, 200, Easing.OutQuint); + } + else + { + Label.FadeIn(200, Easing.OutQuint); + LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint); + } + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -100,6 +129,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateText() { Label.Text = $"{abbreviateBank(GetBankValue(GetSamples()))} {GetVolumeValue(GetSamples())}"; + LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint); } private static string? abbreviateBank(string? bank) From ce41dc46293875b9cb6ac9cd132c0e59c694c69c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2024 17:13:26 +0900 Subject: [PATCH 03/86] Use bindable flow for zoom handling --- .../Edit/ManiaHitObjectComposer.cs | 2 +- .../Components/Timeline/SamplePointPiece.cs | 39 ++++++++----------- .../Timeline/ZoomableScrollContainer.cs | 15 +++---- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 02a4f3a022..4a8f4aa279 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Mania.Edit base.Update(); if (screenWithTimeline?.TimelineArea.Timeline != null) - drawableRuleset.TimelineTimeRange = EditorClock.TrackLength / screenWithTimeline.TimelineArea.Timeline.CurrentZoom / 2; + drawableRuleset.TimelineTimeRange = EditorClock.TrackLength / screenWithTimeline.TimelineArea.Timeline.CurrentZoom.Value / 2; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 1638700735..fda907bf24 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -72,36 +72,31 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline samplesVisible = config.GetBindable(OsuSetting.EditorTimelineShowSamples); } + private BindableNumber? timelineZoom; + protected override void LoadComplete() { base.LoadComplete(); samplesVisible.BindValueChanged(visible => this.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint)); this.FadeTo(samplesVisible.Value ? 1 : 0); - } - private float lastZoom; - private const float zoom_threshold = 40f; - - protected override void Update() - { - base.Update(); - - // Retract visual state if the timeline is zoomed out too far. - if (timeline is null || timeline.Zoom == lastZoom) return; - - lastZoom = timeline.Zoom; - - if (timeline.Zoom < zoom_threshold) + timelineZoom = timeline?.CurrentZoom.GetBoundCopy(); + timelineZoom?.BindValueChanged(zoom => { - Label.FadeOut(200, Easing.OutQuint); - LabelContainer.ResizeWidthTo(16, 200, Easing.OutQuint); - } - else - { - Label.FadeIn(200, Easing.OutQuint); - LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint); - } + const float zoom_threshold = 40f; + + if (zoom.NewValue < zoom_threshold) + { + Label.FadeOut(200, Easing.OutQuint); + LabelContainer.ResizeWidthTo(16, 200, Easing.OutQuint); + } + else + { + Label.FadeIn(200, Easing.OutQuint); + LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint); + } + }, true); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 848c8f9a0f..31a0936eb4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Transforms; @@ -32,10 +33,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override Container Content => zoomedContent; /// - /// The current zoom level of . + /// The current (final) zoom level of . /// It may differ from during transitions. /// - public float CurrentZoom { get; private set; } = 1; + public BindableFloat CurrentZoom { get; private set; } = new BindableFloat(1); private bool isZoomSetUp; @@ -98,7 +99,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline minZoom = minimum; maxZoom = maximum; - CurrentZoom = zoomTarget = initial; + CurrentZoom.Value = zoomTarget = initial; zoomedContentWidthCache.Invalidate(); isZoomSetUp = true; @@ -124,7 +125,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (IsLoaded) setZoomTarget(newZoom, ToSpaceOfOtherDrawable(new Vector2(DrawWidth / 2, 0), zoomedContent).X); else - CurrentZoom = zoomTarget = newZoom; + CurrentZoom.Value = zoomTarget = newZoom; } protected override void UpdateAfterChildren() @@ -154,7 +155,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateZoomedContentWidth() { - zoomedContent.Width = DrawWidth * CurrentZoom; + zoomedContent.Width = DrawWidth * CurrentZoom.Value; zoomedContentWidthCache.Validate(); } @@ -238,7 +239,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline float expectedWidth = d.DrawWidth * newZoom; float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset; - d.CurrentZoom = newZoom; + d.CurrentZoom.Value = newZoom; d.updateZoomedContentWidth(); // Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area. @@ -247,7 +248,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline d.ScrollTo(targetOffset, false); } - protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.CurrentZoom; + protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.CurrentZoom.Value; } } } From 9dcce67b81032cedd9e7d0695d22a70edb70cff0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2024 17:24:24 +0900 Subject: [PATCH 04/86] Make points slightly smaller when contracted and fix width getting obliterated --- .../Components/Timeline/SamplePointPiece.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index fda907bf24..1076255b6a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -74,6 +74,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private BindableNumber? timelineZoom; + private bool contracted; + protected override void LoadComplete() { base.LoadComplete(); @@ -88,13 +90,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (zoom.NewValue < zoom_threshold) { + contracted = true; Label.FadeOut(200, Easing.OutQuint); - LabelContainer.ResizeWidthTo(16, 200, Easing.OutQuint); + LabelContainer.ResizeTo(new Vector2(12), 200, Easing.OutQuint); + LabelContainer.CornerRadius = 6; } else { + contracted = false; Label.FadeIn(200, Easing.OutQuint); - LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint); + LabelContainer.ResizeTo(new Vector2(Label.Width, 16), 200, Easing.OutQuint); + LabelContainer.CornerRadius = 8; } }, true); } @@ -124,7 +130,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateText() { Label.Text = $"{abbreviateBank(GetBankValue(GetSamples()))} {GetVolumeValue(GetSamples())}"; - LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint); + + if (!contracted) + LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint); } private static string? abbreviateBank(string? bank) From 48da758c3913f69bcc3cd8cb3096f5fe3a6d5406 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 5 Oct 2024 20:52:45 +0200 Subject: [PATCH 05/86] contract sample point pieces based on smallest gap on the timeline It finds the smallest distance between two sample point pieces on the alive timeline blueprints --- .../Components/Timeline/SamplePointPiece.cs | 20 ++++----- .../Timeline/TimelineBlueprintContainer.cs | 41 +++++++++++++++++++ 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 1076255b6a..ded7c66058 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Editor? editor { get; set; } [Resolved] - private Timeline? timeline { get; set; } + private TimelineBlueprintContainer? timelineBlueprintContainer { get; set; } private Bindable samplesVisible = null!; @@ -72,9 +72,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline samplesVisible = config.GetBindable(OsuSetting.EditorTimelineShowSamples); } - private BindableNumber? timelineZoom; - - private bool contracted; + private readonly Bindable contracted = new Bindable(); protected override void LoadComplete() { @@ -83,21 +81,19 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline samplesVisible.BindValueChanged(visible => this.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint)); this.FadeTo(samplesVisible.Value ? 1 : 0); - timelineZoom = timeline?.CurrentZoom.GetBoundCopy(); - timelineZoom?.BindValueChanged(zoom => - { - const float zoom_threshold = 40f; + if (timelineBlueprintContainer != null) + contracted.BindTo(timelineBlueprintContainer.SamplePointContracted); - if (zoom.NewValue < zoom_threshold) + contracted.BindValueChanged(v => + { + if (v.NewValue) { - contracted = true; Label.FadeOut(200, Easing.OutQuint); LabelContainer.ResizeTo(new Vector2(12), 200, Easing.OutQuint); LabelContainer.CornerRadius = 6; } else { - contracted = false; Label.FadeIn(200, Easing.OutQuint); LabelContainer.ResizeTo(new Vector2(Label.Width, 16), 200, Easing.OutQuint); LabelContainer.CornerRadius = 8; @@ -131,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Label.Text = $"{abbreviateBank(GetBankValue(GetSamples()))} {GetVolumeValue(GetSamples())}"; - if (!contracted) + if (!contracted.Value) LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 740f0b6aac..b50739c80f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -15,16 +15,19 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Layout; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { + [Cached] internal partial class TimelineBlueprintContainer : EditorBlueprintContainer { [Resolved(CanBeNull = true)] @@ -35,6 +38,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private bool hitObjectDragged; + private readonly LayoutValue samplePointContractedStateCache = new LayoutValue(Invalidation.DrawSize); + /// /// Positional input must be received outside the container's bounds, /// in order to handle timeline blueprints which are stacked offscreen. @@ -49,6 +54,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.Centre; Height = 0.6f; + + AddLayout(samplePointContractedStateCache); } [BackgroundDependencyLoader] @@ -116,11 +123,43 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Composer.Playfield.FutureLifetimeExtension = timeline.VisibleRange / 2; } + updateSamplePointContractedState(); + base.Update(); updateStacking(); } + public Bindable SamplePointContracted = new Bindable(); + + private void updateSamplePointContractedState() + { + if (samplePointContractedStateCache.IsValid) + return; + + const double minimum_gap = 28; + + // Find the smallest time gap between any two sample point pieces + double smallestTimeGap = double.PositiveInfinity; + double lastTime = double.PositiveInfinity; + + // The blueprints are ordered in reverse chronological order + foreach (var selectionBlueprint in SelectionBlueprints) + { + var hitObject = selectionBlueprint.Item; + + if (hitObject is IHasRepeats hasRepeats) + smallestTimeGap = Math.Min(smallestTimeGap, hasRepeats.Duration / hasRepeats.SpanCount() / 2); + + smallestTimeGap = Math.Min(smallestTimeGap, lastTime - hitObject.GetEndTime()); + lastTime = hitObject.StartTime; + } + + double smallestAbsoluteGap = ((TimelineSelectionBlueprintContainer)SelectionBlueprints).ContentRelativeToAbsoluteFactor.X * smallestTimeGap; + SamplePointContracted.Value = smallestAbsoluteGap < minimum_gap; + samplePointContractedStateCache.Validate(); + } + private readonly Stack currentConcurrentObjects = new Stack(); private void updateStacking() @@ -288,6 +327,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { protected override Container> Content { get; } + public Vector2 ContentRelativeToAbsoluteFactor => Content.RelativeToAbsoluteFactor; + public TimelineSelectionBlueprintContainer() { AddInternal(new TimelinePart>(Content = new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); From 88fa6f6bc61086e9b37c00618bb5025e2c39858a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 11:33:53 +0200 Subject: [PATCH 06/86] Optimize `PolygonGenerationPopover` --- .../Edit/PolygonGenerationPopover.cs | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index 6325de5851..c46e2650bc 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -118,10 +118,10 @@ namespace osu.Game.Rulesets.Osu.Edit changeHandler?.BeginChange(); began = true; - distanceSnapInput.Current.BindValueChanged(_ => tryCreatePolygon()); - offsetAngleInput.Current.BindValueChanged(_ => tryCreatePolygon()); - repeatCountInput.Current.BindValueChanged(_ => tryCreatePolygon()); - pointInput.Current.BindValueChanged(_ => tryCreatePolygon()); + distanceSnapInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); + offsetAngleInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); + repeatCountInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); + pointInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); tryCreatePolygon(); } @@ -136,23 +136,34 @@ namespace osu.Game.Rulesets.Osu.Edit double length = distanceSnapInput.Current.Value * velocity * timeSpacing; float polygonRadius = (float)(length / (2 * Math.Sin(double.Pi / pointInput.Current.Value))); - editorBeatmap.RemoveRange(insertedCircles); - insertedCircles.Clear(); + int totalPoints = pointInput.Current.Value * repeatCountInput.Current.Value; + + if (insertedCircles.Count > totalPoints) + { + editorBeatmap.RemoveRange(insertedCircles.GetRange(totalPoints + 1, insertedCircles.Count - totalPoints - 1)); + insertedCircles.RemoveRange(totalPoints + 1, insertedCircles.Count - totalPoints - 1); + } var selectionHandler = (EditorSelectionHandler)composer.BlueprintContainer.SelectionHandler; bool first = true; - for (int i = 1; i <= pointInput.Current.Value * repeatCountInput.Current.Value; ++i) + var newlyAdded = new List(); + + for (int i = 0; i < totalPoints; ++i) { - float angle = float.DegreesToRadians(offsetAngleInput.Current.Value) + i * (2 * float.Pi / pointInput.Current.Value); + float angle = float.DegreesToRadians(offsetAngleInput.Current.Value) + (i + 1) * (2 * float.Pi / pointInput.Current.Value); var position = OsuPlayfield.BASE_SIZE / 2 + new Vector2(polygonRadius * float.Cos(angle), polygonRadius * float.Sin(angle)); - var circle = new HitCircle - { - Position = position, - StartTime = startTime, - NewCombo = first && selectionHandler.SelectionNewComboState.Value == TernaryState.True, - }; + bool alreadyAdded = i < insertedCircles.Count; + + var circle = alreadyAdded + ? insertedCircles[i] + : new HitCircle(); + + circle.Position = position; + circle.StartTime = startTime; + circle.NewCombo = first && selectionHandler.SelectionNewComboState.Value == TernaryState.True; + // TODO: probably ensure samples also follow current ternary status (not trivial) circle.Samples.Add(circle.CreateHitSampleInfo()); @@ -162,13 +173,17 @@ namespace osu.Game.Rulesets.Osu.Edit return; } - insertedCircles.Add(circle); + if (!alreadyAdded) + newlyAdded.Add(circle); + startTime = beatSnapProvider.SnapTime(startTime + timeSpacing); first = false; } - editorBeatmap.AddRange(insertedCircles); + insertedCircles.AddRange(newlyAdded); + editorBeatmap.AddRange(newlyAdded); + commitButton.Enabled.Value = true; } From 96769897bfbbf7551dbd5ee04fd89e98936819b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 11:38:32 +0200 Subject: [PATCH 07/86] Extract method for scheduler call --- osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index c46e2650bc..91a8d56fc1 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -118,13 +118,15 @@ namespace osu.Game.Rulesets.Osu.Edit changeHandler?.BeginChange(); began = true; - distanceSnapInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); - offsetAngleInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); - repeatCountInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); - pointInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); + distanceSnapInput.Current.BindValueChanged(_ => scheduleRefresh()); + offsetAngleInput.Current.BindValueChanged(_ => scheduleRefresh()); + repeatCountInput.Current.BindValueChanged(_ => scheduleRefresh()); + pointInput.Current.BindValueChanged(_ => scheduleRefresh()); tryCreatePolygon(); } + private void scheduleRefresh() => Scheduler.AddOnce(tryCreatePolygon); + private void tryCreatePolygon() { double startTime = beatSnapProvider.SnapTime(editorClock.CurrentTime); From 5c74fceff6ca7e8ea49f14bd2319a56fa8016134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 11:46:57 +0200 Subject: [PATCH 08/86] Remove objects when polygon is invalid --- osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index 91a8d56fc1..fb4640db82 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -172,6 +172,8 @@ namespace osu.Game.Rulesets.Osu.Edit if (position.X < 0 || position.Y < 0 || position.X > OsuPlayfield.BASE_SIZE.X || position.Y > OsuPlayfield.BASE_SIZE.Y) { commitButton.Enabled.Value = false; + editorBeatmap.RemoveRange(insertedCircles); + insertedCircles.Clear(); return; } From 7e439be9eca9e626a080defc0cd24da933072710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 11:47:23 +0200 Subject: [PATCH 09/86] Fix crash from non-serializable hitobject samples --- osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index fb4640db82..9605393f70 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -166,9 +166,6 @@ namespace osu.Game.Rulesets.Osu.Edit circle.StartTime = startTime; circle.NewCombo = first && selectionHandler.SelectionNewComboState.Value == TernaryState.True; - // TODO: probably ensure samples also follow current ternary status (not trivial) - circle.Samples.Add(circle.CreateHitSampleInfo()); - if (position.X < 0 || position.Y < 0 || position.X > OsuPlayfield.BASE_SIZE.X || position.Y > OsuPlayfield.BASE_SIZE.Y) { commitButton.Enabled.Value = false; @@ -178,8 +175,13 @@ namespace osu.Game.Rulesets.Osu.Edit } if (!alreadyAdded) + { newlyAdded.Add(circle); + // TODO: probably ensure samples also follow current ternary status (not trivial) + circle.Samples.Add(circle.CreateHitSampleInfo()); + } + startTime = beatSnapProvider.SnapTime(startTime + timeSpacing); first = false; From 8e781c170d034d4395ae6b045f24b1887837bd4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 19:24:46 +0200 Subject: [PATCH 10/86] Inline scheduler calls --- osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index 9605393f70..a84494fb76 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -118,15 +118,13 @@ namespace osu.Game.Rulesets.Osu.Edit changeHandler?.BeginChange(); began = true; - distanceSnapInput.Current.BindValueChanged(_ => scheduleRefresh()); - offsetAngleInput.Current.BindValueChanged(_ => scheduleRefresh()); - repeatCountInput.Current.BindValueChanged(_ => scheduleRefresh()); - pointInput.Current.BindValueChanged(_ => scheduleRefresh()); + distanceSnapInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); + offsetAngleInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); + repeatCountInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); + pointInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); tryCreatePolygon(); } - private void scheduleRefresh() => Scheduler.AddOnce(tryCreatePolygon); - private void tryCreatePolygon() { double startTime = beatSnapProvider.SnapTime(editorClock.CurrentTime); From 9e97141e33f3f94064790f2db2d668d55243d287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 22:03:21 +0200 Subject: [PATCH 11/86] Fix off-by-one error --- osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index a84494fb76..b244e07adf 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -140,8 +140,8 @@ namespace osu.Game.Rulesets.Osu.Edit if (insertedCircles.Count > totalPoints) { - editorBeatmap.RemoveRange(insertedCircles.GetRange(totalPoints + 1, insertedCircles.Count - totalPoints - 1)); - insertedCircles.RemoveRange(totalPoints + 1, insertedCircles.Count - totalPoints - 1); + editorBeatmap.RemoveRange(insertedCircles.GetRange(totalPoints, insertedCircles.Count - totalPoints)); + insertedCircles.RemoveRange(totalPoints, insertedCircles.Count - totalPoints); } var selectionHandler = (EditorSelectionHandler)composer.BlueprintContainer.SelectionHandler; From f76a2d02f61e522ae69eef5f1c3e8af0836bb42d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 23:22:15 +0200 Subject: [PATCH 12/86] Make sure to properly update hitobjects --- .../Edit/PolygonGenerationPopover.cs | 13 +++++++++++-- .../Compose/Components/EditorSelectionHandler.cs | 3 ++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index b244e07adf..335b0f3ea6 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -50,9 +50,14 @@ namespace osu.Game.Rulesets.Osu.Edit [Resolved] private HitObjectComposer composer { get; set; } = null!; + private Bindable newComboState = null!; + [BackgroundDependencyLoader] private void load() { + var selectionHandler = (EditorSelectionHandler)composer.BlueprintContainer.SelectionHandler; + newComboState = selectionHandler.SelectionNewComboState.GetBoundCopy(); + Child = new FillFlowContainer { Width = 220, @@ -122,6 +127,7 @@ namespace osu.Game.Rulesets.Osu.Edit offsetAngleInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); repeatCountInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); pointInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); + newComboState.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); tryCreatePolygon(); } @@ -144,7 +150,6 @@ namespace osu.Game.Rulesets.Osu.Edit insertedCircles.RemoveRange(totalPoints, insertedCircles.Count - totalPoints); } - var selectionHandler = (EditorSelectionHandler)composer.BlueprintContainer.SelectionHandler; bool first = true; var newlyAdded = new List(); @@ -162,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Edit circle.Position = position; circle.StartTime = startTime; - circle.NewCombo = first && selectionHandler.SelectionNewComboState.Value == TernaryState.True; + circle.NewCombo = first && newComboState.Value == TernaryState.True; if (position.X < 0 || position.Y < 0 || position.X > OsuPlayfield.BASE_SIZE.X || position.Y > OsuPlayfield.BASE_SIZE.Y) { @@ -179,6 +184,10 @@ namespace osu.Game.Rulesets.Osu.Edit // TODO: probably ensure samples also follow current ternary status (not trivial) circle.Samples.Add(circle.CreateHitSampleInfo()); } + else + { + editorBeatmap.Update(circle); + } startTime = beatSnapProvider.SnapTime(startTime + timeSpacing); diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index dbbf767a7d..dfda93cedd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -176,7 +176,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected virtual void UpdateTernaryStates() { - SelectionNewComboState.Value = GetStateFromSelection(SelectedItems.OfType(), h => h.NewCombo); + if (SelectedItems.Any()) + SelectionNewComboState.Value = GetStateFromSelection(SelectedItems.OfType(), h => h.NewCombo); var samplesInSelection = SelectedItems.SelectMany(enumerateAllSamples).ToArray(); From 767ded43267f23b6e2a8a0b9b5221106605112ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 23:41:57 +0200 Subject: [PATCH 13/86] Restore previous NewCombo state when adding objects --- .../Edit/PolygonGenerationPopover.cs | 54 +++++++++++-------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index 335b0f3ea6..00abab0bae 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -150,24 +150,40 @@ namespace osu.Game.Rulesets.Osu.Edit insertedCircles.RemoveRange(totalPoints, insertedCircles.Count - totalPoints); } - bool first = true; - var newlyAdded = new List(); for (int i = 0; i < totalPoints; ++i) { float angle = float.DegreesToRadians(offsetAngleInput.Current.Value) + (i + 1) * (2 * float.Pi / pointInput.Current.Value); var position = OsuPlayfield.BASE_SIZE / 2 + new Vector2(polygonRadius * float.Cos(angle), polygonRadius * float.Sin(angle)); + bool newCombo = i == 0 && newComboState.Value == TernaryState.True; - bool alreadyAdded = i < insertedCircles.Count; + HitCircle circle; - var circle = alreadyAdded - ? insertedCircles[i] - : new HitCircle(); + if (i < insertedCircles.Count) + { + circle = insertedCircles[i]; - circle.Position = position; - circle.StartTime = startTime; - circle.NewCombo = first && newComboState.Value == TernaryState.True; + circle.Position = position; + circle.StartTime = startTime; + circle.NewCombo = newCombo; + + editorBeatmap.Update(circle); + } + else + { + circle = new HitCircle + { + Position = position, + StartTime = startTime, + NewCombo = newCombo, + }; + + newlyAdded.Add(circle); + + // TODO: probably ensure samples also follow current ternary status (not trivial) + circle.Samples.Add(circle.CreateHitSampleInfo()); + } if (position.X < 0 || position.Y < 0 || position.X > OsuPlayfield.BASE_SIZE.X || position.Y > OsuPlayfield.BASE_SIZE.Y) { @@ -177,26 +193,18 @@ namespace osu.Game.Rulesets.Osu.Edit return; } - if (!alreadyAdded) - { - newlyAdded.Add(circle); - - // TODO: probably ensure samples also follow current ternary status (not trivial) - circle.Samples.Add(circle.CreateHitSampleInfo()); - } - else - { - editorBeatmap.Update(circle); - } - startTime = beatSnapProvider.SnapTime(startTime + timeSpacing); - - first = false; } + var previousNewComboState = newComboState.Value; + insertedCircles.AddRange(newlyAdded); editorBeatmap.AddRange(newlyAdded); + // When adding new hitObjects, newCombo state will get reset to false when no objects are selected. + // Since this is the case when this popover is showing, we need to restore the previous newCombo state + newComboState.Value = previousNewComboState; + commitButton.Enabled.Value = true; } From 40339b8a1713aaf1b980550b602a932c24f42ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 23:49:52 +0200 Subject: [PATCH 14/86] Fix typo --- osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index 00abab0bae..2f073c2fc4 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -201,7 +201,7 @@ namespace osu.Game.Rulesets.Osu.Edit insertedCircles.AddRange(newlyAdded); editorBeatmap.AddRange(newlyAdded); - // When adding new hitObjects, newCombo state will get reset to false when no objects are selected. + // When adding new hitObjects, newCombo state will get reset to false when no objects are selected. // Since this is the case when this popover is showing, we need to restore the previous newCombo state newComboState.Value = previousNewComboState; From 0794fc61a0935274beed82de91ef780c4b1d34f2 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Fri, 11 Oct 2024 18:38:53 -0400 Subject: [PATCH 15/86] Initial Implementation (working, unsafe handling of cursor position) --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 106 +++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 +- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 12 +++ 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs new file mode 100644 index 0000000000..193b0142df --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -0,0 +1,106 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + + + +using System; +using System.Diagnostics; +using osu.Framework.Bindables; +using osu.Framework.Localisation; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; +using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Rulesets.Osu.UI.Cursor; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModBloom : Mod, IApplicableToScoreProcessor, IUpdatableByPlayfield, IApplicableToPlayer + { + public override string Name => "Bloom"; + public override string Acronym => "BM"; + public override ModType Type => ModType.Fun; + public override LocalisableString Description => "The cursor blooms into a.. larger cursor!"; + public override double ScoreMultiplier => 1; + protected const float MIN_SIZE = 1; + protected const float TRANSITION_DURATION = 100; + public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight), typeof(OsuModNoScope), typeof(OsuModObjectScaleTween), typeof(OsuModTouchDevice), typeof(OsuModAutopilot) }; + + + protected readonly BindableNumber CurrentCombo = new BindableInt(); + protected readonly IBindable IsBreakTime = new Bindable(); + protected float ComboBasedSize; + + + [SettingSource( + "Max Size at Combo", + "The combo count at which the cursor reaches its maximum size", + SettingControlType = typeof(SettingsSlider) + )] + public BindableInt MaxSizeComboCount { get; } = new BindableInt(50) + { + MinValue = 0, + MaxValue = 100, + }; + [SettingSource( + "Final Size Multiplier", + "The multiplier applied to cursor size when combo reaches maximum", + SettingControlType = typeof(SettingsSlider>) + )] + public BindableFloat MaxMulti { get; } = new BindableFloat(10f) + { + MinValue = 5f, + MaxValue = 15f, + Precision = 0.5f, + }; + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + public void ApplyToPlayer(Player player) + { + IsBreakTime.BindTo(player.IsBreakTime); + } + + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + if (MaxSizeComboCount.Value == 0) return; + + CurrentCombo.BindTo(scoreProcessor.Combo); + CurrentCombo.BindValueChanged(combo => + { + ComboBasedSize = Math.Min(MaxMulti.Value, 10 * ((float)combo.NewValue / MaxSizeComboCount.Value)); + ComboBasedSize = Math.Max(ComboBasedSize, MIN_SIZE); + }, true + ); + } + public void Update(Playfield playfield) + //terrible terrible handling on making sure cursor position stays accurate, will fix + { + bool beBaseSize = IsBreakTime.Value; + var osuPlayfield = (OsuPlayfield)playfield; + Debug.Assert(osuPlayfield.Cursor != null); + var realCursor = (OsuCursor)osuPlayfield.Cursor.ActiveCursor; + realCursor.isBloom = true; + if (beBaseSize) + { + realCursor.ComboSize = 1; + } + else + { + realCursor.ComboSize = ComboBasedSize; + } + } + + + + + } + public partial class MaxSizeSlider : RoundedSliderBar + { + public override LocalisableString TooltipText => Current.Value == 0 ? "always at max size" : base.TooltipText; + } + +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 2f928aaefa..25b1dd9b12 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -214,7 +214,8 @@ namespace osu.Game.Rulesets.Osu new OsuModFreezeFrame(), new OsuModBubbles(), new OsuModSynesthesia(), - new OsuModDepth() + new OsuModDepth(), + new OsuModBloom() }; case ModType.System: diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 0bb316e0aa..75a38826ad 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -39,6 +39,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public IBindable CursorScale => cursorScale; private readonly Bindable cursorScale = new BindableFloat(1); + public bool isBloom; + public float ComboSize; private Bindable userCursorScale = null!; private Bindable autoCursorScale = null!; @@ -68,6 +70,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor autoCursorScale.ValueChanged += _ => cursorScale.Value = CalculateCursorScale(); cursorScale.BindValueChanged(e => cursorScaleContainer.Scale = new Vector2(e.NewValue), true); + isBloom = false; } protected override void LoadComplete() @@ -75,6 +78,15 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor base.LoadComplete(); cursorScale.Value = CalculateCursorScale(); } + protected override void Update() + //this should not exist will implement sane fix + { + base.Update(); + if (isBloom) + { + cursorScale.Value = ComboSize; + } + } protected virtual Drawable CreateCursorContent() => cursorScaleContainer = new Container { From 4d367515dc2fbb8200b42ceba48cb33bb3bcf21c Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Fri, 11 Oct 2024 21:52:13 -0400 Subject: [PATCH 16/86] Smoother size changing using linear interpolation --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index 193b0142df..a9026e470b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Framework.Utils; namespace osu.Game.Rulesets.Osu.Mods { @@ -84,13 +85,14 @@ namespace osu.Game.Rulesets.Osu.Mods Debug.Assert(osuPlayfield.Cursor != null); var realCursor = (OsuCursor)osuPlayfield.Cursor.ActiveCursor; realCursor.isBloom = true; + float currentCombSize = (float)Interpolation.Lerp(osuPlayfield.Cursor.Scale.X, ComboBasedSize, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); if (beBaseSize) { realCursor.ComboSize = 1; } else { - realCursor.ComboSize = ComboBasedSize; + realCursor.ComboSize = currentCombSize; } } From 1a142ff94448dc93c7f88ad89493c15fc6159248 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Mon, 14 Oct 2024 01:54:02 -0400 Subject: [PATCH 17/86] minor changes --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 3 ++- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index a9026e470b..8ed0e60445 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -85,7 +85,8 @@ namespace osu.Game.Rulesets.Osu.Mods Debug.Assert(osuPlayfield.Cursor != null); var realCursor = (OsuCursor)osuPlayfield.Cursor.ActiveCursor; realCursor.isBloom = true; - float currentCombSize = (float)Interpolation.Lerp(osuPlayfield.Cursor.Scale.X, ComboBasedSize, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); + float currentCombSize = (float)Interpolation.Lerp(realCursor.ComboSize, ComboBasedSize, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); + Console.WriteLine(ComboBasedSize + " " + currentCombSize); if (beBaseSize) { realCursor.ComboSize = 1; diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 75a38826ad..f3a6b0da3d 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -17,6 +17,7 @@ using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; +using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.UI.Cursor { @@ -70,13 +71,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor autoCursorScale.ValueChanged += _ => cursorScale.Value = CalculateCursorScale(); cursorScale.BindValueChanged(e => cursorScaleContainer.Scale = new Vector2(e.NewValue), true); - isBloom = false; } protected override void LoadComplete() { base.LoadComplete(); cursorScale.Value = CalculateCursorScale(); + isBloom = false; } protected override void Update() //this should not exist will implement sane fix From bd71da012ad82cf828997e0cf3899789599315ea Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Tue, 15 Oct 2024 02:14:03 -0400 Subject: [PATCH 18/86] Refactors handling of scale to a function --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 13 +++++-------- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 17 ++++------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index 8ed0e60445..a249aec35c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -78,22 +78,19 @@ namespace osu.Game.Rulesets.Osu.Mods ); } public void Update(Playfield playfield) - //terrible terrible handling on making sure cursor position stays accurate, will fix { bool beBaseSize = IsBreakTime.Value; - var osuPlayfield = (OsuPlayfield)playfield; + OsuPlayfield osuPlayfield = (OsuPlayfield)playfield; Debug.Assert(osuPlayfield.Cursor != null); - var realCursor = (OsuCursor)osuPlayfield.Cursor.ActiveCursor; - realCursor.isBloom = true; - float currentCombSize = (float)Interpolation.Lerp(realCursor.ComboSize, ComboBasedSize, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); - Console.WriteLine(ComboBasedSize + " " + currentCombSize); + OsuCursor realCursor = (OsuCursor)osuPlayfield.Cursor.ActiveCursor; + float currentCombSize = (float)Interpolation.Lerp(realCursor.CursorScale.Value, ComboBasedSize, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); if (beBaseSize) { - realCursor.ComboSize = 1; + realCursor.UpdateSize(1); } else { - realCursor.ComboSize = currentCombSize; + realCursor.UpdateSize(currentCombSize); } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index f3a6b0da3d..a3a4f8a547 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -17,7 +17,6 @@ using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; -using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.UI.Cursor { @@ -40,8 +39,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public IBindable CursorScale => cursorScale; private readonly Bindable cursorScale = new BindableFloat(1); - public bool isBloom; - public float ComboSize; private Bindable userCursorScale = null!; private Bindable autoCursorScale = null!; @@ -77,16 +74,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { base.LoadComplete(); cursorScale.Value = CalculateCursorScale(); - isBloom = false; - } - protected override void Update() - //this should not exist will implement sane fix - { - base.Update(); - if (isBloom) - { - cursorScale.Value = ComboSize; - } } protected virtual Drawable CreateCursorContent() => cursorScaleContainer = new Container @@ -113,6 +100,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor return scale; } + public void UpdateSize(float size) + { + cursorScale.Value = size; + } protected override void SkinChanged(ISkinSource skin) { From 86a5760ef490bbb548e9539224814cd2eaebdcd3 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Tue, 15 Oct 2024 02:23:41 -0400 Subject: [PATCH 19/86] Remove handling for zero as MaxSizeComboCount, set lower limit as 5 --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index a249aec35c..a3cd91b49e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -41,11 +41,11 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource( "Max Size at Combo", "The combo count at which the cursor reaches its maximum size", - SettingControlType = typeof(SettingsSlider) + SettingControlType = typeof(SettingsSlider>) )] public BindableInt MaxSizeComboCount { get; } = new BindableInt(50) { - MinValue = 0, + MinValue = 5, MaxValue = 100, }; [SettingSource( @@ -93,14 +93,5 @@ namespace osu.Game.Rulesets.Osu.Mods realCursor.UpdateSize(currentCombSize); } } - - - - } - public partial class MaxSizeSlider : RoundedSliderBar - { - public override LocalisableString TooltipText => Current.Value == 0 ? "always at max size" : base.TooltipText; - } - } From f6566f91571addd8e52ea7d8b265859822eea96b Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Tue, 15 Oct 2024 15:37:05 -0400 Subject: [PATCH 20/86] Code quality touch-ups --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 16 +++++++++------- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 1 + 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index a3cd91b49e..eef9f0590d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.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. - - using System; using System.Diagnostics; using osu.Framework.Bindables; @@ -26,18 +24,16 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Name => "Bloom"; public override string Acronym => "BM"; public override ModType Type => ModType.Fun; - public override LocalisableString Description => "The cursor blooms into a.. larger cursor!"; + public override LocalisableString Description => "The cursor blooms into.. a larger cursor!"; public override double ScoreMultiplier => 1; protected const float MIN_SIZE = 1; protected const float TRANSITION_DURATION = 100; public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight), typeof(OsuModNoScope), typeof(OsuModObjectScaleTween), typeof(OsuModTouchDevice), typeof(OsuModAutopilot) }; - protected readonly BindableNumber CurrentCombo = new BindableInt(); protected readonly IBindable IsBreakTime = new Bindable(); protected float ComboBasedSize; - [SettingSource( "Max Size at Combo", "The combo count at which the cursor reaches its maximum size", @@ -48,6 +44,7 @@ namespace osu.Game.Rulesets.Osu.Mods MinValue = 5, MaxValue = 100, }; + [SettingSource( "Final Size Multiplier", "The multiplier applied to cursor size when combo reaches maximum", @@ -59,7 +56,9 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 15f, Precision = 0.5f, }; + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + public void ApplyToPlayer(Player player) { IsBreakTime.BindTo(player.IsBreakTime); @@ -74,20 +73,23 @@ namespace osu.Game.Rulesets.Osu.Mods { ComboBasedSize = Math.Min(MaxMulti.Value, 10 * ((float)combo.NewValue / MaxSizeComboCount.Value)); ComboBasedSize = Math.Max(ComboBasedSize, MIN_SIZE); - }, true - ); + }, true); } + public void Update(Playfield playfield) { bool beBaseSize = IsBreakTime.Value; OsuPlayfield osuPlayfield = (OsuPlayfield)playfield; Debug.Assert(osuPlayfield.Cursor != null); + OsuCursor realCursor = (OsuCursor)osuPlayfield.Cursor.ActiveCursor; float currentCombSize = (float)Interpolation.Lerp(realCursor.CursorScale.Value, ComboBasedSize, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); + if (beBaseSize) { realCursor.UpdateSize(1); } + else { realCursor.UpdateSize(currentCombSize); diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index a3a4f8a547..545cd15d32 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -100,6 +100,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor return scale; } + public void UpdateSize(float size) { cursorScale.Value = size; From bf970afddb92d45c30749630e52d6f42dd1ba2b7 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Tue, 15 Oct 2024 16:27:05 -0400 Subject: [PATCH 21/86] Formatting --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index eef9f0590d..a132e1d4c8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -89,7 +89,6 @@ namespace osu.Game.Rulesets.Osu.Mods { realCursor.UpdateSize(1); } - else { realCursor.UpdateSize(currentCombSize); From 9e1af18ac6aa7bec1021f91cb8e3954c2e304df5 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Wed, 16 Oct 2024 20:37:59 -0400 Subject: [PATCH 22/86] Fixed improper handling of size calculation when the max multiplier was changed in mod settings --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index a132e1d4c8..e1b05fa84b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -66,13 +66,10 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { - if (MaxSizeComboCount.Value == 0) return; - CurrentCombo.BindTo(scoreProcessor.Combo); CurrentCombo.BindValueChanged(combo => { - ComboBasedSize = Math.Min(MaxMulti.Value, 10 * ((float)combo.NewValue / MaxSizeComboCount.Value)); - ComboBasedSize = Math.Max(ComboBasedSize, MIN_SIZE); + ComboBasedSize = Math.Clamp(MaxMulti.Value * ((float)combo.NewValue / MaxSizeComboCount.Value), MIN_SIZE, MaxMulti.Value); }, true); } From c03017438e3c78fb0e13cdf28fb2fb426b13f363 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 19 Oct 2024 23:57:05 +0200 Subject: [PATCH 23/86] Use FinishTransforms to init visual state --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index ded7c66058..48178b2e19 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -78,9 +78,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.LoadComplete(); - samplesVisible.BindValueChanged(visible => this.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint)); - this.FadeTo(samplesVisible.Value ? 1 : 0); - if (timelineBlueprintContainer != null) contracted.BindTo(timelineBlueprintContainer.SamplePointContracted); @@ -99,6 +96,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline LabelContainer.CornerRadius = 8; } }, true); + + samplesVisible.BindValueChanged(visible => this.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint), true); + FinishTransforms(); } protected override void Dispose(bool isDisposing) From f137fed04e16b6a1829073b673571e6d6e475376 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 19 Oct 2024 23:57:41 +0200 Subject: [PATCH 24/86] Update sample point contracted state each frame --- .../Timeline/TimelineBlueprintContainer.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index b50739c80f..fec15517a4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Framework.Layout; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; @@ -38,8 +37,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private bool hitObjectDragged; - private readonly LayoutValue samplePointContractedStateCache = new LayoutValue(Invalidation.DrawSize); - /// /// Positional input must be received outside the container's bounds, /// in order to handle timeline blueprints which are stacked offscreen. @@ -54,8 +51,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.Centre; Height = 0.6f; - - AddLayout(samplePointContractedStateCache); } [BackgroundDependencyLoader] @@ -123,10 +118,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Composer.Playfield.FutureLifetimeExtension = timeline.VisibleRange / 2; } - updateSamplePointContractedState(); - base.Update(); + updateSamplePointContractedState(); updateStacking(); } @@ -134,8 +128,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateSamplePointContractedState() { - if (samplePointContractedStateCache.IsValid) - return; + // because only blueprints of objects which are alive (via pooling) are displayed in the timeline, it's feasible to do this every-update. const double minimum_gap = 28; @@ -157,7 +150,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline double smallestAbsoluteGap = ((TimelineSelectionBlueprintContainer)SelectionBlueprints).ContentRelativeToAbsoluteFactor.X * smallestTimeGap; SamplePointContracted.Value = smallestAbsoluteGap < minimum_gap; - samplePointContractedStateCache.Validate(); } private readonly Stack currentConcurrentObjects = new Stack(); From 22aef90cb7b1edaff97c86ec640ce49b750c6b1f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 20 Oct 2024 00:07:40 +0200 Subject: [PATCH 25/86] dont contract samples for stacked objects --- .../Components/Timeline/TimelineBlueprintContainer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index fec15517a4..c8448b745e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -144,7 +144,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (hitObject is IHasRepeats hasRepeats) smallestTimeGap = Math.Min(smallestTimeGap, hasRepeats.Duration / hasRepeats.SpanCount() / 2); - smallestTimeGap = Math.Min(smallestTimeGap, lastTime - hitObject.GetEndTime()); + double gap = lastTime - hitObject.GetEndTime(); + + // If the gap is less than 1ms, we can assume that the objects are stacked on top of each other + // Contracting doesn't make sense in this case + if (gap > 1 && gap < smallestTimeGap) + smallestTimeGap = gap; + lastTime = hitObject.StartTime; } From eb61da423299e50de211f10cd1e093f1ac444207 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 24 Oct 2024 21:22:09 +0200 Subject: [PATCH 26/86] only consider hit objects which are visible in timeline for contracted state --- .../Timeline/TimelineBlueprintContainer.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index c8448b745e..d4b9101747 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -32,6 +32,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved(CanBeNull = true)] private Timeline timeline { get; set; } + [Resolved(CanBeNull = true)] + private EditorClock editorClock { get; set; } + private Bindable placement; private SelectionBlueprint placementBlueprint; @@ -128,10 +131,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateSamplePointContractedState() { - // because only blueprints of objects which are alive (via pooling) are displayed in the timeline, it's feasible to do this every-update. - const double minimum_gap = 28; + if (timeline == null || editorClock == null) + return; + // Find the smallest time gap between any two sample point pieces double smallestTimeGap = double.PositiveInfinity; double lastTime = double.PositiveInfinity; @@ -141,6 +145,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { var hitObject = selectionBlueprint.Item; + // Only check the hit objects which are visible in the timeline + // SelectionBlueprints can contain hit objects which are not visible in the timeline due to selection keeping them alive + if (hitObject.StartTime > editorClock.CurrentTime + timeline.VisibleRange / 2) + continue; + + if (hitObject.GetEndTime() < editorClock.CurrentTime - timeline.VisibleRange / 2) + break; + if (hitObject is IHasRepeats hasRepeats) smallestTimeGap = Math.Min(smallestTimeGap, hasRepeats.Duration / hasRepeats.SpanCount() / 2); From 8f48682d0a8a8385d2ade6db7ce0f03eac1668b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 1 Nov 2024 18:36:01 +0100 Subject: [PATCH 27/86] Remove hide sample toggle In line with feedback from https://github.com/ppy/osu/pull/29896#issuecomment-2378676851. --- osu.Game/Configuration/OsuConfigManager.cs | 2 -- osu.Game/Localisation/EditorStrings.cs | 5 ----- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 5 ----- osu.Game/Screens/Edit/Editor.cs | 6 ------ 4 files changed, 18 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index ae2025fb50..8705253b7d 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -205,7 +205,6 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true); SetDefault(OsuSetting.EditorTimelineShowTicks, true); - SetDefault(OsuSetting.EditorTimelineShowSamples, true); SetDefault(OsuSetting.AlwaysShowHoldForMenuButton, false); } @@ -433,6 +432,5 @@ namespace osu.Game.Configuration EditorTimelineShowTimingChanges, EditorTimelineShowTicks, AlwaysShowHoldForMenuButton, - EditorTimelineShowSamples } } diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index f72a0f4a26..bcffc18d4d 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -139,11 +139,6 @@ namespace osu.Game.Localisation /// public static LocalisableString TimelineShowTicks => new TranslatableString(getKey(@"timeline_show_ticks"), @"Show ticks"); - /// - /// "Show samples" - /// - public static LocalisableString TimelineShowSamples => new TranslatableString(getKey(@"timeline_show_samples"), @"Show samples"); - private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 48178b2e19..0cfaeb8c38 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -44,8 +44,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private TimelineBlueprintContainer? timelineBlueprintContainer { get; set; } - private Bindable samplesVisible = null!; - public SamplePointPiece(HitObject hitObject) { HitObject = hitObject; @@ -68,8 +66,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (editor != null) editor.ShowSampleEditPopoverRequested += onShowSampleEditPopoverRequested; - - samplesVisible = config.GetBindable(OsuSetting.EditorTimelineShowSamples); } private readonly Bindable contracted = new Bindable(); @@ -97,7 +93,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } }, true); - samplesVisible.BindValueChanged(visible => this.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint), true); FinishTransforms(); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b059557b28..9bb91af806 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -215,7 +215,6 @@ namespace osu.Game.Screens.Edit private Bindable editorLimitedDistanceSnap; private Bindable editorTimelineShowTimingChanges; private Bindable editorTimelineShowTicks; - private Bindable editorTimelineShowSamples; /// /// This controls the opacity of components like the timelines, sidebars, etc. @@ -324,7 +323,6 @@ namespace osu.Game.Screens.Edit editorLimitedDistanceSnap = config.GetBindable(OsuSetting.EditorLimitedDistanceSnap); editorTimelineShowTimingChanges = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); editorTimelineShowTicks = config.GetBindable(OsuSetting.EditorTimelineShowTicks); - editorTimelineShowSamples = config.GetBindable(OsuSetting.EditorTimelineShowSamples); AddInternal(new OsuContextMenuContainer { @@ -390,10 +388,6 @@ namespace osu.Game.Screens.Edit { State = { BindTarget = editorTimelineShowTicks } }, - new ToggleMenuItem(EditorStrings.TimelineShowSamples) - { - State = { BindTarget = editorTimelineShowSamples } - } ] }, new BackgroundDimMenuItem(editorBackgroundDim), From 32190fdb580821dde55310fcd759a1b696657603 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 5 Nov 2024 16:39:13 +0900 Subject: [PATCH 28/86] Hide cursor trail from draw visualiser --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index a4bccb0aff..5132dc2859 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Rendering.Vertices; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shaders.Types; using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics.Visualisation; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Timing; @@ -23,6 +24,7 @@ using osuTK.Graphics.ES30; namespace osu.Game.Rulesets.Osu.UI.Cursor { + [DrawVisualiserHidden] public partial class CursorTrail : Drawable, IRequireHighFrequencyMousePosition { private const int max_sprites = 2048; From dfda5e1b66b6274f565be99532fa1b1c7162f758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Nov 2024 15:23:09 +0100 Subject: [PATCH 29/86] Improve visual appearance of mania selection blueprints --- .../Components/EditHoldNoteEndPiece.cs | 13 +++++++-- .../Blueprints/Components/EditNotePiece.cs | 29 ++++++++++++++++--- .../Blueprints/HoldNoteSelectionBlueprint.cs | 5 ++++ .../Edit/Blueprints/NoteSelectionBlueprint.cs | 11 ++++++- 4 files changed, 51 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs index 0aa72c28b8..285226e8b4 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Graphics; @@ -29,7 +30,11 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components CornerRadius = 5; Masking = true; - InternalChild = new DefaultNotePiece(); + InternalChild = new EditNotePiece + { + RelativeSizeAxes = Axes.Both, + Height = 1, + }; } protected override void LoadComplete() @@ -60,19 +65,23 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { base.OnDrag(e); Dragging?.Invoke(e.ScreenSpaceMousePosition); + updateState(); } protected override void OnDragEnd(DragEndEvent e) { base.OnDragEnd(e); DragEnded?.Invoke(); + updateState(); } private void updateState() { + InternalChild.Colour = Colour4.White; + var colour = colours.Yellow; - if (IsHovered) + if (IsHovered || IsDragged) colour = colour.Lighten(1); Colour = colour; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs index 48dde29a9f..63f67d6150 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs @@ -1,10 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Skinning.Default; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { @@ -12,12 +16,17 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { public EditNotePiece() { + Masking = true; + BorderThickness = 9; // organoleptically chosen to look good enough for all default skins + BorderColour = Color4.White; Height = DefaultNotePiece.NOTE_HEIGHT; - CornerRadius = 5; - Masking = true; - - InternalChild = new DefaultNotePiece(); + InternalChild = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }; } [BackgroundDependencyLoader] @@ -25,5 +34,17 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { Colour = colours.Yellow; } + + protected override void Update() + { + base.Update(); + + // from anecdotal experience, there are generally two types of user skins: + // one type uses rectangles for notes, and the other uses circles / squarish sprites (various stepmania-likes). + // this is a crude heuristic that attempts to choose the best of both worlds based on aspect ratio alone. + float aspectRatio = DrawWidth / DrawHeight; + bool isSquarish = aspectRatio > 4f / 5 && aspectRatio < 5f / 4; + CornerRadius = isSquarish ? Math.Min(DrawWidth, DrawHeight) / 2 : 5; + } } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index b8e6aa26a0..2f2ad4bbba 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -10,6 +10,7 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Screens.Edit; using osuTK; @@ -32,6 +33,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private EditHoldNoteEndPiece head = null!; private EditHoldNoteEndPiece tail = null!; + protected new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject; + public HoldNoteSelectionBlueprint(HoldNote hold) : base(hold) { @@ -99,7 +102,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.Update(); + head.Height = DrawableObject.Head.DrawHeight; head.Y = HitObjectContainer.PositionAtTime(HitObject.Head.StartTime, HitObject.StartTime); + tail.Height = DrawableObject.Tail.DrawHeight; tail.Y = HitObjectContainer.PositionAtTime(HitObject.Tail.StartTime, HitObject.StartTime); Height = HitObjectContainer.LengthAtTime(HitObject.StartTime, HitObject.EndTime) + tail.DrawHeight; } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs index 01c7bd502a..c2df09e92a 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs @@ -9,10 +9,19 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { public partial class NoteSelectionBlueprint : ManiaSelectionBlueprint { + private readonly EditNotePiece notePiece; + public NoteSelectionBlueprint(Note note) : base(note) { - AddInternal(new EditNotePiece { RelativeSizeAxes = Axes.X }); + AddInternal(notePiece = new EditNotePiece { RelativeSizeAxes = Axes.X }); + } + + protected override void Update() + { + base.Update(); + + notePiece.Height = DrawableObject.DrawHeight; } } } From 3d4f5d4d105cc14cde1730818680625365686a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Nov 2024 15:29:52 +0100 Subject: [PATCH 30/86] Fix test --- .../Editor/TestSceneManiaSelectionHandler.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs index a70b90789d..0f7d696c7f 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs @@ -5,6 +5,8 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; using osu.Game.Screens.Edit.Compose.Components; @@ -106,7 +108,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); AddStep("start drag", () => { - InputManager.MoveMouseTo(this.ChildrenOfType().First()); + InputManager.MoveMouseTo(this.ChildrenOfType().Single(blueprint => blueprint.IsSelected && blueprint.HitObject.StartTime == 0)); InputManager.PressButton(MouseButton.Left); }); AddStep("end drag", () => From fd179666057d23d28db13eacb9612cfba84431c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 7 Nov 2024 08:39:58 +0100 Subject: [PATCH 31/86] Remove unused using directive --- .../Editor/TestSceneManiaSelectionHandler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs index 0f7d696c7f..a920b3470f 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Edit.Blueprints; -using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; using osu.Game.Screens.Edit.Compose.Components; From aede94a3f3edcac2c0495f05c1193c4b08578b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 7 Nov 2024 08:41:29 +0100 Subject: [PATCH 32/86] Use box variant of selection always --- .../Edit/Blueprints/Components/EditNotePiece.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs index 63f67d6150..a2a5955a9d 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -17,6 +16,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components public EditNotePiece() { Masking = true; + CornerRadius = 5; BorderThickness = 9; // organoleptically chosen to look good enough for all default skins BorderColour = Color4.White; Height = DefaultNotePiece.NOTE_HEIGHT; @@ -34,17 +34,5 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { Colour = colours.Yellow; } - - protected override void Update() - { - base.Update(); - - // from anecdotal experience, there are generally two types of user skins: - // one type uses rectangles for notes, and the other uses circles / squarish sprites (various stepmania-likes). - // this is a crude heuristic that attempts to choose the best of both worlds based on aspect ratio alone. - float aspectRatio = DrawWidth / DrawHeight; - bool isSquarish = aspectRatio > 4f / 5 && aspectRatio < 5f / 4; - CornerRadius = isSquarish ? Math.Min(DrawWidth, DrawHeight) / 2 : 5; - } } } From 98ae45d763cbf8a2ad34d7be780eba906fff34b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 7 Nov 2024 09:18:57 +0100 Subject: [PATCH 33/86] Adjust appearance further in response to feedback --- .../Blueprints/Components/EditNotePiece.cs | 20 +++++++++++++++--- .../Blueprints/HoldNoteSelectionBlueprint.cs | 21 ++++++++++++++++++- .../Blueprints/ManiaSelectionBlueprint.cs | 10 ++------- .../Edit/Blueprints/NoteSelectionBlueprint.cs | 17 ++++++++++++++- 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs index a2a5955a9d..c626e8011e 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs @@ -3,22 +3,28 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Skinning.Default; -using osuTK.Graphics; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { public partial class EditNotePiece : CompositeDrawable { + [Resolved] + private Column? column { get; set; } + public EditNotePiece() { Masking = true; CornerRadius = 5; - BorderThickness = 9; // organoleptically chosen to look good enough for all default skins - BorderColour = Color4.White; + BorderThickness = 6; + BorderColour = ColourInfo.GradientVertical(Colour4.White.Opacity(0.5f), Colour4.White); Height = DefaultNotePiece.NOTE_HEIGHT; InternalChild = new Box @@ -34,5 +40,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { Colour = colours.Yellow; } + + protected override void Update() + { + base.Update(); + + if (column != null) + Scale = new Vector2(1, column.ScrollingInfo.Direction.Value == ScrollingDirection.Down ? 1 : -1); + } } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 2f2ad4bbba..66b914d320 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; @@ -11,6 +12,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; using osuTK; @@ -32,6 +34,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private EditHoldNoteEndPiece head = null!; private EditHoldNoteEndPiece tail = null!; + private Container body = null!; protected new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject; @@ -48,6 +51,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints head = new EditHoldNoteEndPiece { RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, DragStarted = () => changeHandler?.BeginChange(), Dragging = pos => { @@ -67,6 +72,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints tail = new EditHoldNoteEndPiece { RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, DragStarted = () => changeHandler?.BeginChange(), Dragging = pos => { @@ -82,10 +89,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints }, DragEnded = () => changeHandler?.EndChange(), }, - new Container + body = new Container { RelativeSizeAxes = Axes.Both, Masking = true, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, BorderThickness = 1, BorderColour = colours.Yellow, Child = new Box @@ -109,6 +118,16 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints Height = HitObjectContainer.LengthAtTime(HitObject.StartTime, HitObject.EndTime) + tail.DrawHeight; } + protected override void OnDirectionChanged(ValueChangedEvent direction) + { + Origin = direction.NewValue == ScrollingDirection.Down ? Anchor.BottomCentre : Anchor.TopCentre; + + foreach (var child in InternalChildren) + child.Anchor = Origin; + + head.Scale = tail.Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Down ? 1 : -1); + } + public override Quad SelectionQuad => ScreenSpaceDrawQuad; public override Vector2 ScreenSpaceSelectionPoint => head.ScreenSpaceDrawQuad.Centre; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index c645ddd98d..4bb9d5f5c1 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -37,16 +37,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints protected override void LoadComplete() { base.LoadComplete(); - directionBindable.BindValueChanged(onDirectionChanged, true); + directionBindable.BindValueChanged(OnDirectionChanged, true); } - private void onDirectionChanged(ValueChangedEvent direction) - { - var anchor = direction.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; - Anchor = Origin = anchor; - foreach (var child in InternalChildren) - child.Anchor = child.Origin = anchor; - } + protected abstract void OnDirectionChanged(ValueChangedEvent direction); protected override void Update() { diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs index c2df09e92a..3476f91568 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs @@ -1,9 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { @@ -14,7 +17,14 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints public NoteSelectionBlueprint(Note note) : base(note) { - AddInternal(notePiece = new EditNotePiece { RelativeSizeAxes = Axes.X }); + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + AddInternal(notePiece = new EditNotePiece + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }); } protected override void Update() @@ -23,5 +33,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints notePiece.Height = DrawableObject.DrawHeight; } + + protected override void OnDirectionChanged(ValueChangedEvent direction) + { + notePiece.Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Down ? 1 : -1); + } } } From e058f98bad3978234a7bbaf98edc8d061a7c4d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 7 Nov 2024 09:36:28 +0100 Subject: [PATCH 34/86] Remove unused field --- .../Edit/Blueprints/HoldNoteSelectionBlueprint.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 66b914d320..4e0b0cea6c 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -34,7 +34,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private EditHoldNoteEndPiece head = null!; private EditHoldNoteEndPiece tail = null!; - private Container body = null!; protected new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject; @@ -89,7 +88,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints }, DragEnded = () => changeHandler?.EndChange(), }, - body = new Container + new Container { RelativeSizeAxes = Axes.Both, Masking = true, From dba065302daa5bca92e8d26fe0ba8199bc67f874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 7 Nov 2024 13:41:41 +0100 Subject: [PATCH 35/86] Adjust appearance once more --- .../Blueprints/Components/EditNotePiece.cs | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs index c626e8011e..3f15515ba6 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mania.Skinning.Default; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { @@ -23,15 +24,31 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { Masking = true; CornerRadius = 5; - BorderThickness = 6; - BorderColour = ColourInfo.GradientVertical(Colour4.White.Opacity(0.5f), Colour4.White); Height = DefaultNotePiece.NOTE_HEIGHT; - InternalChild = new Box + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 5, + BorderThickness = 5, + BorderColour = Color4.White.Opacity(0.7f), + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + }, + new Box + { + RelativeSizeAxes = Axes.X, + Height = 10, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }, }; } From e1d93a7d9c819eb2ae792934c299fbf00139aab3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Nov 2024 15:09:13 +0900 Subject: [PATCH 36/86] Merge implementations of ConvertHitObjectParser Having these be separate implementations sounded awesome at the time, but it only ever led to confusion. There's no practical difference if, for example, catch sees hitobjects with `IHasPosition` instead of `IHasXPosition`. --- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 45 ++----- .../Objects/Legacy/Catch/ConvertHit.cs | 20 --- .../Legacy/Catch/ConvertHitObjectParser.cs | 63 --------- .../Objects/Legacy/Catch/ConvertSlider.cs | 20 --- .../Objects/Legacy/Catch/ConvertSpinner.cs | 19 --- .../Objects/Legacy/ConvertHitCircle.cs | 13 ++ .../Objects/Legacy/ConvertHitObject.cs | 14 +- .../Objects/Legacy/ConvertHitObjectParser.cs | 123 ++++++++++++------ .../ConvertSpinner.cs => ConvertHold.cs} | 11 +- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 12 +- .../Legacy/{Taiko => }/ConvertSpinner.cs | 7 +- .../Objects/Legacy/Mania/ConvertHit.cs | 15 --- .../Legacy/Mania/ConvertHitObjectParser.cs | 58 --------- .../Objects/Legacy/Mania/ConvertHold.cs | 16 --- .../Objects/Legacy/Mania/ConvertSlider.cs | 15 --- .../Rulesets/Objects/Legacy/Osu/ConvertHit.cs | 20 --- .../Legacy/Osu/ConvertHitObjectParser.cs | 64 --------- .../Objects/Legacy/Osu/ConvertSlider.cs | 22 ---- .../Objects/Legacy/Osu/ConvertSpinner.cs | 24 ---- .../Objects/Legacy/Taiko/ConvertHit.cs | 12 -- .../Legacy/Taiko/ConvertHitObjectParser.cs | 51 -------- .../Objects/Legacy/Taiko/ConvertSlider.cs | 12 -- 22 files changed, 134 insertions(+), 522 deletions(-) delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs create mode 100644 osu.Game/Rulesets/Objects/Legacy/ConvertHitCircle.cs rename osu.Game/Rulesets/Objects/Legacy/{Mania/ConvertSpinner.cs => ConvertHold.cs} (54%) rename osu.Game/Rulesets/Objects/Legacy/{Taiko => }/ConvertSpinner.cs (70%) delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index b068c87fbb..3d8c8a6e7a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -38,8 +38,7 @@ namespace osu.Game.Beatmaps.Formats internal static RulesetStore? RulesetStore; private Beatmap beatmap = null!; - - private ConvertHitObjectParser? parser; + private ConvertHitObjectParser parser = null!; private LegacySampleBank defaultSampleBank; private int defaultSampleVolume = 100; @@ -80,6 +79,7 @@ namespace osu.Game.Beatmaps.Formats { this.beatmap = beatmap; this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion; + parser = new ConvertHitObjectParser(getOffsetTime(), FormatVersion); applyLegacyDefaults(this.beatmap.BeatmapInfo); @@ -162,7 +162,8 @@ namespace osu.Game.Beatmaps.Formats { if (hitObject is IHasRepeats hasRepeats) { - SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.StartTime + CONTROL_POINT_LENIENCY + 1) ?? SampleControlPoint.DEFAULT; + SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.StartTime + CONTROL_POINT_LENIENCY + 1) + ?? SampleControlPoint.DEFAULT; hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList(); for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) @@ -175,7 +176,8 @@ namespace osu.Game.Beatmaps.Formats } else { - SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + CONTROL_POINT_LENIENCY) ?? SampleControlPoint.DEFAULT; + SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + CONTROL_POINT_LENIENCY) + ?? SampleControlPoint.DEFAULT; hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList(); } } @@ -263,29 +265,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"Mode": - int rulesetID = Parsing.ParseInt(pair.Value); - - beatmap.BeatmapInfo.Ruleset = RulesetStore?.GetRuleset(rulesetID) ?? throw new ArgumentException("Ruleset is not available locally."); - - switch (rulesetID) - { - case 0: - parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion); - break; - - case 1: - parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser(getOffsetTime(), FormatVersion); - break; - - case 2: - parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser(getOffsetTime(), FormatVersion); - break; - - case 3: - parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser(getOffsetTime(), FormatVersion); - break; - } - + beatmap.BeatmapInfo.Ruleset = RulesetStore?.GetRuleset(Parsing.ParseInt(pair.Value)) ?? throw new ArgumentException("Ruleset is not available locally."); break; case @"LetterboxInBreaks": @@ -617,17 +597,10 @@ namespace osu.Game.Beatmaps.Formats private void handleHitObject(string line) { - // If the ruleset wasn't specified, assume the osu!standard ruleset. - parser ??= new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion); - var obj = parser.Parse(line); + obj.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); - if (obj != null) - { - obj.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); - - beatmap.HitObjects.Add(obj); - } + beatmap.HitObjects.Add(obj); } private int getOffsetTime(int time) => time + (ApplyOffsets ? offset : 0); diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs deleted file mode 100644 index 96c779e79b..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Objects.Types; -using osuTK; - -namespace osu.Game.Rulesets.Objects.Legacy.Catch -{ - /// - /// Legacy osu!catch Hit-type, used for parsing Beatmaps. - /// - internal sealed class ConvertHit : ConvertHitObject, IHasPosition - { - public float X => Position.X; - - public float Y => Position.Y; - - public Vector2 Position { get; set; } - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs deleted file mode 100644 index a5c1a73fa7..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ /dev/null @@ -1,63 +0,0 @@ -// 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 osuTK; -using osu.Game.Audio; -using System.Collections.Generic; - -namespace osu.Game.Rulesets.Objects.Legacy.Catch -{ - /// - /// A HitObjectParser to parse legacy osu!catch Beatmaps. - /// - public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser - { - private ConvertHitObject lastObject; - - public ConvertHitObjectParser(double offset, int formatVersion) - : base(offset, formatVersion) - { - } - - protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) - { - return lastObject = new ConvertHit - { - Position = position, - NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo, - ComboOffset = newCombo ? comboOffset : 0 - }; - } - - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, - IList> nodeSamples) - { - return lastObject = new ConvertSlider - { - Position = position, - NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo, - ComboOffset = newCombo ? comboOffset : 0, - Path = new SliderPath(controlPoints, length), - NodeSamples = nodeSamples, - RepeatCount = repeatCount - }; - } - - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) - { - return lastObject = new ConvertSpinner - { - Duration = duration, - NewCombo = newCombo - // Spinners cannot have combo offset. - }; - } - - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) - { - return lastObject = null; - } - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs deleted file mode 100644 index bcf1c7fae2..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Objects.Types; -using osuTK; - -namespace osu.Game.Rulesets.Objects.Legacy.Catch -{ - /// - /// Legacy osu!catch Slider-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition - { - public float X => Position.X; - - public float Y => Position.Y; - - public Vector2 Position { get; set; } - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs deleted file mode 100644 index 5ef3d51cb3..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Catch -{ - /// - /// Legacy osu!catch Spinner-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition - { - public double EndTime => StartTime + Duration; - - public double Duration { get; set; } - - public float X => 256; // Required for CatchBeatmapConverter - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitCircle.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitCircle.cs new file mode 100644 index 0000000000..d1852d7032 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitCircle.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.Objects.Legacy +{ + /// + /// Legacy "HitCircle" hit object type. + /// + /// + /// Only used for parsing beatmaps and not gameplay. + /// + internal sealed class ConvertHitCircle : ConvertHitObject; +} diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs index bb36aab0b3..d1b0d3c6c2 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs @@ -4,18 +4,28 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Rulesets.Objects.Legacy { /// - /// A hit object only used for conversion, not actual gameplay. + /// Represents a legacy hit object. /// - internal abstract class ConvertHitObject : HitObject, IHasCombo + /// + /// Only used for parsing beatmaps and not gameplay. + /// + internal abstract class ConvertHitObject : HitObject, IHasCombo, IHasPosition { public bool NewCombo { get; set; } public int ComboOffset { get; set; } + public float X => Position.X; + + public float Y => Position.Y; + + public Vector2 Position { get; set; } + public override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 89fd5f7384..646431c071 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.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 osuTK; using osu.Game.Rulesets.Objects.Types; using System; @@ -11,7 +9,6 @@ using System.IO; using osu.Game.Beatmaps.Formats; using osu.Game.Audio; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Legacy; @@ -24,24 +21,32 @@ namespace osu.Game.Rulesets.Objects.Legacy /// /// A HitObjectParser to parse legacy Beatmaps. /// - public abstract class ConvertHitObjectParser : HitObjectParser + public class ConvertHitObjectParser : HitObjectParser { /// /// The offset to apply to all time values. /// - protected readonly double Offset; + private readonly double offset; /// /// The .osu format (beatmap) version. /// - protected readonly int FormatVersion; + private readonly int formatVersion; - protected bool FirstObject { get; private set; } = true; + /// + /// Whether the current hitobject is the first hitobject in the beatmap. + /// + private bool firstObject = true; - protected ConvertHitObjectParser(double offset, int formatVersion) + /// + /// The last parsed hitobject. + /// + private ConvertHitObject? lastObject; + + internal ConvertHitObjectParser(double offset, int formatVersion) { - Offset = offset; - FormatVersion = formatVersion; + this.offset = offset; + this.formatVersion = formatVersion; } public override HitObject Parse(string text) @@ -49,11 +54,11 @@ namespace osu.Game.Rulesets.Objects.Legacy string[] split = text.Split(','); Vector2 pos = - FormatVersion >= LegacyBeatmapEncoder.FIRST_LAZER_VERSION + formatVersion >= LegacyBeatmapEncoder.FIRST_LAZER_VERSION ? new Vector2(Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE)) : new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE)); - double startTime = Parsing.ParseDouble(split[2]) + Offset; + double startTime = Parsing.ParseDouble(split[2]) + offset; LegacyHitObjectType type = (LegacyHitObjectType)Parsing.ParseInt(split[3]); @@ -66,11 +71,11 @@ namespace osu.Game.Rulesets.Objects.Legacy var soundType = (LegacyHitSoundType)Parsing.ParseInt(split[4]); var bankInfo = new SampleBankInfo(); - HitObject result = null; + HitObject? result = null; if (type.HasFlag(LegacyHitObjectType.Circle)) { - result = CreateHit(pos, combo, comboOffset); + result = createHitCircle(pos, combo, comboOffset); if (split.Length > 5) readCustomSampleBanks(split[5], bankInfo); @@ -145,13 +150,13 @@ namespace osu.Game.Rulesets.Objects.Legacy for (int i = 0; i < nodes; i++) nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); - result = CreateSlider(pos, combo, comboOffset, convertPathString(split[5], pos), length, repeatCount, nodeSamples); + result = createSlider(pos, combo, comboOffset, convertPathString(split[5], pos), length, repeatCount, nodeSamples); } else if (type.HasFlag(LegacyHitObjectType.Spinner)) { - double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + Offset - startTime); + double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + offset - startTime); - result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, duration); + result = createSpinner(new Vector2(512, 384) / 2, combo, comboOffset, duration); if (split.Length > 6) readCustomSampleBanks(split[6], bankInfo); @@ -169,7 +174,7 @@ namespace osu.Game.Rulesets.Objects.Legacy readCustomSampleBanks(string.Join(':', ss.Skip(1)), bankInfo); } - result = CreateHold(pos, combo, comboOffset, endTime + Offset - startTime); + result = createHold(pos, combo, comboOffset, endTime + offset - startTime); } if (result == null) @@ -180,7 +185,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (result.Samples.Count == 0) result.Samples = convertSoundType(soundType, bankInfo); - FirstObject = false; + firstObject = false; return result; } @@ -200,10 +205,11 @@ namespace osu.Game.Rulesets.Objects.Legacy if (!Enum.IsDefined(addBank)) addBank = LegacySampleBank.Normal; - string stringBank = bank.ToString().ToLowerInvariant(); + string? stringBank = bank.ToString().ToLowerInvariant(); + string? stringAddBank = addBank.ToString().ToLowerInvariant(); + if (stringBank == @"none") stringBank = null; - string stringAddBank = addBank.ToString().ToLowerInvariant(); if (stringAddBank == @"none") { @@ -357,7 +363,7 @@ namespace osu.Game.Rulesets.Objects.Legacy { int endPointLength = endPoint == null ? 0 : 1; - if (FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION) + if (formatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION) { if (vertices.Length + endPointLength != 3) type = PathType.BEZIER; @@ -393,7 +399,7 @@ namespace osu.Game.Rulesets.Objects.Legacy // Legacy CATMULL sliders don't support multiple segments, so adjacent CATMULL segments should be treated as a single one. // Importantly, this is not applied to the first control point, which may duplicate the slider path's position // resulting in a duplicate (0,0) control point in the resultant list. - if (type == PathType.CATMULL && endIndex > 1 && FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION) + if (type == PathType.CATMULL && endIndex > 1 && formatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION) continue; // The last control point of each segment is not allowed to start a new implicit segment. @@ -442,7 +448,15 @@ namespace osu.Game.Rulesets.Objects.Legacy /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. /// The hit object. - protected abstract HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset); + private HitObject createHitCircle(Vector2 position, bool newCombo, int comboOffset) + { + return lastObject = new ConvertHitCircle + { + Position = position, + NewCombo = firstObject || lastObject is ConvertSpinner || newCombo, + ComboOffset = newCombo ? comboOffset : 0 + }; + } /// /// Creats a legacy Slider-type hit object. @@ -455,8 +469,19 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The slider repeat count. /// The samples to be played when the slider nodes are hit. This includes the head and tail of the slider. /// The hit object. - protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, - IList> nodeSamples); + private HitObject createSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, + IList> nodeSamples) + { + return lastObject = new ConvertSlider + { + Position = position, + NewCombo = firstObject || lastObject is ConvertSpinner || newCombo, + ComboOffset = newCombo ? comboOffset : 0, + Path = new SliderPath(controlPoints, length), + NodeSamples = nodeSamples, + RepeatCount = repeatCount + }; + } /// /// Creates a legacy Spinner-type hit object. @@ -466,7 +491,16 @@ namespace osu.Game.Rulesets.Objects.Legacy /// When starting a new combo, the offset of the new combo relative to the current one. /// The spinner duration. /// The hit object. - protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration); + private HitObject createSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) + { + return lastObject = new ConvertSpinner + { + Position = position, + Duration = duration, + NewCombo = newCombo + // Spinners cannot have combo offset. + }; + } /// /// Creates a legacy Hold-type hit object. @@ -475,7 +509,14 @@ namespace osu.Game.Rulesets.Objects.Legacy /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. /// The hold duration. - protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration); + private HitObject createHold(Vector2 position, bool newCombo, int comboOffset, double duration) + { + return lastObject = new ConvertHold + { + Position = position, + Duration = duration + }; + } private List convertSoundType(LegacyHitSoundType type, SampleBankInfo bankInfo) { @@ -511,21 +552,19 @@ namespace osu.Game.Rulesets.Objects.Legacy /// /// An optional overriding filename which causes all bank/sample specifications to be ignored. /// - public string Filename; + public string? Filename; /// /// The bank identifier to use for the base ("hitnormal") sample. /// Transferred to when appropriate. /// - [CanBeNull] - public string BankForNormal; + public string? BankForNormal; /// /// The bank identifier to use for additions ("hitwhistle", "hitfinish", "hitclap"). /// Transferred to when appropriate. /// - [CanBeNull] - public string BankForAdditions; + public string? BankForAdditions; /// /// Hit sample volume (0-100). @@ -548,8 +587,6 @@ namespace osu.Game.Rulesets.Objects.Legacy public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone(); } -#nullable enable - public class LegacyHitSampleInfo : HitSampleInfo, IEquatable { public readonly int CustomSampleBank; @@ -577,13 +614,14 @@ namespace osu.Game.Rulesets.Objects.Legacy IsLayered = isLayered; } - public sealed override HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default, Optional newEditorAutoBank = default) + public sealed override HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default, + Optional newEditorAutoBank = default) => With(newName, newBank, newVolume, newEditorAutoBank); - public virtual LegacyHitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newVolume = default, Optional newEditorAutoBank = default, - Optional newCustomSampleBank = default, - Optional newIsLayered = default) - => new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newEditorAutoBank.GetOr(EditorAutoBank), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered)); + public virtual LegacyHitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newVolume = default, + Optional newEditorAutoBank = default, Optional newCustomSampleBank = default, Optional newIsLayered = default) + => new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newEditorAutoBank.GetOr(EditorAutoBank), newCustomSampleBank.GetOr(CustomSampleBank), + newIsLayered.GetOr(IsLayered)); public bool Equals(LegacyHitSampleInfo? other) // The additions to equality checks here are *required* to ensure that pooling works correctly. @@ -615,9 +653,8 @@ namespace osu.Game.Rulesets.Objects.Legacy Path.ChangeExtension(Filename, null) }; - public sealed override LegacyHitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newVolume = default, Optional newEditorAutoBank = default, - Optional newCustomSampleBank = default, - Optional newIsLayered = default) + public sealed override LegacyHitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newVolume = default, + Optional newEditorAutoBank = default, Optional newCustomSampleBank = default, Optional newIsLayered = default) => new FileHitSampleInfo(Filename, newVolume.GetOr(Volume)); public bool Equals(FileHitSampleInfo? other) diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHold.cs similarity index 54% rename from osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs rename to osu.Game/Rulesets/Objects/Legacy/ConvertHold.cs index c05aaceb9c..d74224892b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHold.cs @@ -3,17 +3,18 @@ using osu.Game.Rulesets.Objects.Types; -namespace osu.Game.Rulesets.Objects.Legacy.Mania +namespace osu.Game.Rulesets.Objects.Legacy { /// - /// Legacy osu!mania Spinner-type, used for parsing Beatmaps. + /// Legacy "Hold" hit object type. Generally only valid in the mania ruleset. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition + /// + /// Only used for parsing beatmaps and not gameplay. + /// + internal sealed class ConvertHold : ConvertHitObject, IHasDuration { public double Duration { get; set; } public double EndTime => StartTime + Duration; - - public float X { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 683eefa8f4..fee68f2f11 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.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 osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using Newtonsoft.Json; @@ -13,7 +11,13 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Objects.Legacy { - internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasSliderVelocity + /// + /// Legacy "Slider" hit object type. + /// + /// + /// Only used for parsing beatmaps and not gameplay. + /// + internal class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasSliderVelocity, IHasGenerateTicks { /// /// Scoring distance with a speed-adjusted beat length of 1 second. @@ -50,6 +54,8 @@ namespace osu.Game.Rulesets.Objects.Legacy set => SliderVelocityMultiplierBindable.Value = value; } + public bool GenerateTicks { get; set; } = true; + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSpinner.cs similarity index 70% rename from osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs rename to osu.Game/Rulesets/Objects/Legacy/ConvertSpinner.cs index 1d5ecb1ef3..59551cd37a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSpinner.cs @@ -3,11 +3,14 @@ using osu.Game.Rulesets.Objects.Types; -namespace osu.Game.Rulesets.Objects.Legacy.Taiko +namespace osu.Game.Rulesets.Objects.Legacy { /// - /// Legacy osu!taiko Spinner-type, used for parsing Beatmaps. + /// Legacy "Spinner" hit object type. /// + /// + /// Only used for parsing beatmaps and not gameplay. + /// internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration { public double Duration { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs deleted file mode 100644 index 0b69817c13..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Mania -{ - /// - /// Legacy osu!mania Hit-type, used for parsing Beatmaps. - /// - internal sealed class ConvertHit : ConvertHitObject, IHasXPosition - { - public float X { get; set; } - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs deleted file mode 100644 index 386eb8d3ee..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osuTK; -using osu.Game.Audio; -using System.Collections.Generic; - -namespace osu.Game.Rulesets.Objects.Legacy.Mania -{ - /// - /// A HitObjectParser to parse legacy osu!mania Beatmaps. - /// - public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser - { - public ConvertHitObjectParser(double offset, int formatVersion) - : base(offset, formatVersion) - { - } - - protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) - { - return new ConvertHit - { - X = position.X - }; - } - - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, - IList> nodeSamples) - { - return new ConvertSlider - { - X = position.X, - Path = new SliderPath(controlPoints, length), - NodeSamples = nodeSamples, - RepeatCount = repeatCount - }; - } - - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) - { - return new ConvertSpinner - { - X = position.X, - Duration = duration - }; - } - - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) - { - return new ConvertHold - { - X = position.X, - Duration = duration - }; - } - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs deleted file mode 100644 index 2fa4766c1d..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Mania -{ - internal sealed class ConvertHold : ConvertHitObject, IHasXPosition, IHasDuration - { - public float X { get; set; } - - public double Duration { get; set; } - - public double EndTime => StartTime + Duration; - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs deleted file mode 100644 index 84cde5fa95..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Mania -{ - /// - /// Legacy osu!mania Slider-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition - { - public float X { get; set; } - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs deleted file mode 100644 index b7cd4b0dcc..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Objects.Types; -using osuTK; - -namespace osu.Game.Rulesets.Objects.Legacy.Osu -{ - /// - /// Legacy osu! Hit-type, used for parsing Beatmaps. - /// - internal sealed class ConvertHit : ConvertHitObject, IHasPosition - { - public Vector2 Position { get; set; } - - public float X => Position.X; - - public float Y => Position.Y; - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs deleted file mode 100644 index 43c346b621..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ /dev/null @@ -1,64 +0,0 @@ -// 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 osuTK; -using System.Collections.Generic; -using osu.Game.Audio; - -namespace osu.Game.Rulesets.Objects.Legacy.Osu -{ - /// - /// A HitObjectParser to parse legacy osu! Beatmaps. - /// - public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser - { - private ConvertHitObject lastObject; - - public ConvertHitObjectParser(double offset, int formatVersion) - : base(offset, formatVersion) - { - } - - protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) - { - return lastObject = new ConvertHit - { - Position = position, - NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo, - ComboOffset = newCombo ? comboOffset : 0 - }; - } - - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, - IList> nodeSamples) - { - return lastObject = new ConvertSlider - { - Position = position, - NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo, - ComboOffset = newCombo ? comboOffset : 0, - Path = new SliderPath(controlPoints, length), - NodeSamples = nodeSamples, - RepeatCount = repeatCount - }; - } - - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) - { - return lastObject = new ConvertSpinner - { - Position = position, - Duration = duration, - NewCombo = newCombo - // Spinners cannot have combo offset. - }; - } - - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) - { - return lastObject = null; - } - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs deleted file mode 100644 index 8c37154f95..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Objects.Types; -using osuTK; - -namespace osu.Game.Rulesets.Objects.Legacy.Osu -{ - /// - /// Legacy osu! Slider-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasGenerateTicks - { - public Vector2 Position { get; set; } - - public float X => Position.X; - - public float Y => Position.Y; - - public bool GenerateTicks { get; set; } = true; - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs deleted file mode 100644 index d6e24b6bbf..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Objects.Types; -using osuTK; - -namespace osu.Game.Rulesets.Objects.Legacy.Osu -{ - /// - /// Legacy osu! Spinner-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasPosition - { - public double Duration { get; set; } - - public double EndTime => StartTime + Duration; - - public Vector2 Position { get; set; } - - public float X => Position.X; - - public float Y => Position.Y; - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs deleted file mode 100644 index cb5178ce48..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs +++ /dev/null @@ -1,12 +0,0 @@ -// 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.Objects.Legacy.Taiko -{ - /// - /// Legacy osu!taiko Hit-type, used for parsing Beatmaps. - /// - internal sealed class ConvertHit : ConvertHitObject - { - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs deleted file mode 100644 index d62e8cd04c..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ /dev/null @@ -1,51 +0,0 @@ -// 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 osuTK; -using System.Collections.Generic; -using osu.Game.Audio; - -namespace osu.Game.Rulesets.Objects.Legacy.Taiko -{ - /// - /// A HitObjectParser to parse legacy osu!taiko Beatmaps. - /// - public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser - { - public ConvertHitObjectParser(double offset, int formatVersion) - : base(offset, formatVersion) - { - } - - protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) - { - return new ConvertHit(); - } - - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, - IList> nodeSamples) - { - return new ConvertSlider - { - Path = new SliderPath(controlPoints, length), - NodeSamples = nodeSamples, - RepeatCount = repeatCount - }; - } - - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) - { - return new ConvertSpinner - { - Duration = duration - }; - } - - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) - { - return null; - } - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs deleted file mode 100644 index 821554f7ee..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs +++ /dev/null @@ -1,12 +0,0 @@ -// 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.Objects.Legacy.Taiko -{ - /// - /// Legacy osu!taiko Slider-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSlider : Legacy.ConvertSlider - { - } -} From 7206e97b7beb637c411a39f0418c50db0f89c427 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Nov 2024 15:23:23 +0900 Subject: [PATCH 37/86] Add `IHasLegacyHitObjectType` to ConvertHitObject --- .../Beatmaps/Legacy/LegacyHitObjectType.cs | 2 +- .../Objects/Legacy/ConvertHitObject.cs | 5 ++++- .../Objects/Legacy/ConvertHitObjectParser.cs | 13 +++++++------ .../Objects/Legacy/IHasLegacyHitObjectType.cs | 18 ++++++++++++++++++ 4 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Legacy/IHasLegacyHitObjectType.cs diff --git a/osu.Game/Beatmaps/Legacy/LegacyHitObjectType.cs b/osu.Game/Beatmaps/Legacy/LegacyHitObjectType.cs index 07f170f996..6fab66bf70 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyHitObjectType.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyHitObjectType.cs @@ -6,7 +6,7 @@ using System; namespace osu.Game.Beatmaps.Legacy { [Flags] - internal enum LegacyHitObjectType + public enum LegacyHitObjectType { Circle = 1, Slider = 1 << 1, diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs index d1b0d3c6c2..28683583ee 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; @@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// /// Only used for parsing beatmaps and not gameplay. /// - internal abstract class ConvertHitObject : HitObject, IHasCombo, IHasPosition + internal abstract class ConvertHitObject : HitObject, IHasCombo, IHasPosition, IHasLegacyHitObjectType { public bool NewCombo { get; set; } @@ -26,6 +27,8 @@ namespace osu.Game.Rulesets.Objects.Legacy public Vector2 Position { get; set; } + public LegacyHitObjectType LegacyType { get; set; } + public override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 646431c071..1c933392c0 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Objects.Legacy var soundType = (LegacyHitSoundType)Parsing.ParseInt(split[4]); var bankInfo = new SampleBankInfo(); - HitObject? result = null; + ConvertHitObject? result = null; if (type.HasFlag(LegacyHitObjectType.Circle)) { @@ -181,6 +181,7 @@ namespace osu.Game.Rulesets.Objects.Legacy throw new InvalidDataException($"Unknown hit object type: {split[3]}"); result.StartTime = startTime; + result.LegacyType = type; if (result.Samples.Count == 0) result.Samples = convertSoundType(soundType, bankInfo); @@ -448,7 +449,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. /// The hit object. - private HitObject createHitCircle(Vector2 position, bool newCombo, int comboOffset) + private ConvertHitObject createHitCircle(Vector2 position, bool newCombo, int comboOffset) { return lastObject = new ConvertHitCircle { @@ -469,8 +470,8 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The slider repeat count. /// The samples to be played when the slider nodes are hit. This includes the head and tail of the slider. /// The hit object. - private HitObject createSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, - IList> nodeSamples) + private ConvertHitObject createSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, + IList> nodeSamples) { return lastObject = new ConvertSlider { @@ -491,7 +492,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// When starting a new combo, the offset of the new combo relative to the current one. /// The spinner duration. /// The hit object. - private HitObject createSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) + private ConvertHitObject createSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { return lastObject = new ConvertSpinner { @@ -509,7 +510,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. /// The hold duration. - private HitObject createHold(Vector2 position, bool newCombo, int comboOffset, double duration) + private ConvertHitObject createHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return lastObject = new ConvertHold { diff --git a/osu.Game/Rulesets/Objects/Legacy/IHasLegacyHitObjectType.cs b/osu.Game/Rulesets/Objects/Legacy/IHasLegacyHitObjectType.cs new file mode 100644 index 0000000000..71af57700d --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/IHasLegacyHitObjectType.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps.Legacy; + +namespace osu.Game.Rulesets.Objects.Legacy +{ + /// + /// A hit object from a legacy beatmap representation. + /// + public interface IHasLegacyHitObjectType + { + /// + /// The hit object type. + /// + LegacyHitObjectType LegacyType { get; } + } +} From 8c68db0a369bbfafe726accf1ff23ff902d3c6ed Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Nov 2024 15:27:45 +0900 Subject: [PATCH 38/86] Remove unused params --- .../Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 1c933392c0..f8bc0ce112 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Objects.Legacy { double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + offset - startTime); - result = createSpinner(new Vector2(512, 384) / 2, combo, comboOffset, duration); + result = createSpinner(new Vector2(512, 384) / 2, combo, duration); if (split.Length > 6) readCustomSampleBanks(split[6], bankInfo); @@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Objects.Legacy readCustomSampleBanks(string.Join(':', ss.Skip(1)), bankInfo); } - result = createHold(pos, combo, comboOffset, endTime + offset - startTime); + result = createHold(pos, endTime + offset - startTime); } if (result == null) @@ -489,10 +489,9 @@ namespace osu.Game.Rulesets.Objects.Legacy /// /// The position of the hit object. /// Whether the hit object creates a new combo. - /// When starting a new combo, the offset of the new combo relative to the current one. /// The spinner duration. /// The hit object. - private ConvertHitObject createSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) + private ConvertHitObject createSpinner(Vector2 position, bool newCombo, double duration) { return lastObject = new ConvertSpinner { @@ -507,10 +506,8 @@ namespace osu.Game.Rulesets.Objects.Legacy /// Creates a legacy Hold-type hit object. /// /// The position of the hit object. - /// Whether the hit object creates a new combo. - /// When starting a new combo, the offset of the new combo relative to the current one. /// The hold duration. - private ConvertHitObject createHold(Vector2 position, bool newCombo, int comboOffset, double duration) + private ConvertHitObject createHold(Vector2 position, double duration) { return lastObject = new ConvertHold { From 06380f91fca4656f96a3c32c82960d3c55aab9bc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Nov 2024 16:25:05 +0900 Subject: [PATCH 39/86] Update test --- .../SongSelect/TestSceneBeatmapInfoWedge.cs | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index fd102da026..d8573b2d03 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -1,12 +1,9 @@ // 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; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -23,7 +20,6 @@ using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko; @@ -35,15 +31,11 @@ namespace osu.Game.Tests.Visual.SongSelect [TestFixture] public partial class TestSceneBeatmapInfoWedge : OsuTestScene { - private RulesetStore rulesets; - private TestBeatmapInfoWedge infoWedge; - private readonly List beatmaps = new List(); + [Resolved] + private RulesetStore rulesets { get; set; } = null!; - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - this.rulesets = rulesets; - } + private TestBeatmapInfoWedge infoWedge = null!; + private readonly List beatmaps = new List(); protected override void LoadComplete() { @@ -156,7 +148,7 @@ namespace osu.Game.Tests.Visual.SongSelect IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo); beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 60 * 1000 / bpm }); - OsuModDoubleTime doubleTime = null; + OsuModDoubleTime doubleTime = null!; selectBeatmap(beatmap); checkDisplayedBPM($"{bpm}"); @@ -173,7 +165,7 @@ namespace osu.Game.Tests.Visual.SongSelect [TestCase(120, 120.4, null, "120")] [TestCase(120, 120.6, "DT", "180-182 (mostly 180)")] [TestCase(120, 120.4, "DT", "180")] - public void TestVaryingBPM(double commonBpm, double otherBpm, string mod, string expectedDisplay) + public void TestVaryingBPM(double commonBpm, double otherBpm, string? mod, string expectedDisplay) { IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo); beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 60 * 1000 / commonBpm }); @@ -203,7 +195,7 @@ namespace osu.Game.Tests.Visual.SongSelect double drain = beatmap.CalculateDrainLength(); beatmap.BeatmapInfo.Length = drain; - OsuModDoubleTime doubleTime = null; + OsuModDoubleTime doubleTime = null!; selectBeatmap(beatmap); checkDisplayedLength(drain); @@ -221,14 +213,15 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep($"check map drain ({displayedLength})", () => { - var label = infoWedge.DisplayedContent.ChildrenOfType().Single(l => l.Statistic.Name == BeatmapsetsStrings.ShowStatsTotalLength(displayedLength)); + var label = infoWedge.DisplayedContent.ChildrenOfType() + .Single(l => l.Statistic.Name == BeatmapsetsStrings.ShowStatsTotalLength(displayedLength)); return label.Statistic.Content == displayedLength.ToString(); }); } private void setRuleset(RulesetInfo rulesetInfo) { - Container containerBefore = null; + Container? containerBefore = null; AddStep("set ruleset", () => { @@ -242,9 +235,9 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore); } - private void selectBeatmap([CanBeNull] IBeatmap b) + private void selectBeatmap(IBeatmap? b) { - Container containerBefore = null; + Container? containerBefore = null; AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () => { @@ -307,11 +300,6 @@ namespace osu.Game.Tests.Visual.SongSelect public new WedgeInfoText Info => base.Info; } - private class TestHitObject : ConvertHitObject, IHasPosition - { - public float X => 0; - public float Y => 0; - public Vector2 Position { get; } = Vector2.Zero; - } + private class TestHitObject : ConvertHitObject; } } From 1c3a30a2976a19fb6aa099281919e871cf7c6ca1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Nov 2024 16:30:26 +0900 Subject: [PATCH 40/86] Fix the other test class too --- .../Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs index 35bd4ee958..fbbab3a604 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs @@ -14,9 +14,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Select; -using osuTK; namespace osu.Game.Tests.Visual.SongSelectV2 { @@ -209,11 +207,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 public new WedgeInfoText? Info => base.Info; } - private class TestHitObject : ConvertHitObject, IHasPosition - { - public float X => 0; - public float Y => 0; - public Vector2 Position { get; } = Vector2.Zero; - } + private class TestHitObject : ConvertHitObject; } } From 84587564021072da9ecd70259a859b6965ac5bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 12 Nov 2024 14:26:13 +0100 Subject: [PATCH 41/86] Adjust appearance a third time --- .../Blueprints/Components/EditBodyPiece.cs | 33 +++++++++++++++---- .../Components/EditHoldNoteEndPiece.cs | 3 -- .../Blueprints/Components/EditNotePiece.cs | 22 +++++-------- .../Blueprints/HoldNoteSelectionBlueprint.cs | 21 ++++-------- 4 files changed, 42 insertions(+), 37 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs index 6a12ec5088..652baccaa6 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs @@ -3,21 +3,42 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; using osu.Game.Rulesets.Mania.Skinning.Default; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { - public partial class EditBodyPiece : DefaultBodyPiece + public partial class EditBodyPiece : CompositeDrawable { + private readonly Container border; + + public EditBodyPiece() + { + InternalChildren = new Drawable[] + { + border = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = 3, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + }, + }; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { - AccentColour.Value = colours.Yellow; - - Background.Alpha = 0.5f; + border.BorderColour = colours.YellowDarker; } - - protected override Drawable CreateForeground() => base.CreateForeground().With(d => d.Alpha = 0); } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs index 285226e8b4..d4b61b4661 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs @@ -27,9 +27,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { Height = DefaultNotePiece.NOTE_HEIGHT; - CornerRadius = 5; - Masking = true; - InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs index 3f15515ba6..1f9f26b5e2 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs @@ -2,12 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Rulesets.Mania.Skinning.Default; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -17,24 +15,21 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { public partial class EditNotePiece : CompositeDrawable { + private readonly Container border; + private readonly Box box; + [Resolved] private Column? column { get; set; } public EditNotePiece() { - Masking = true; - CornerRadius = 5; - Height = DefaultNotePiece.NOTE_HEIGHT; - InternalChildren = new Drawable[] { - new Container + border = new Container { RelativeSizeAxes = Axes.Both, Masking = true, - CornerRadius = 5, - BorderThickness = 5, - BorderColour = Color4.White.Opacity(0.7f), + BorderThickness = 3, Child = new Box { RelativeSizeAxes = Axes.Both, @@ -42,10 +37,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components AlwaysPresent = true, }, }, - new Box + box = new Box { RelativeSizeAxes = Axes.X, - Height = 10, + Height = 3, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, }, @@ -55,7 +50,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components [BackgroundDependencyLoader] private void load(OsuColour colours) { - Colour = colours.Yellow; + border.BorderColour = colours.YellowDark; + box.Colour = colours.YellowLight; } protected override void Update() diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 4e0b0cea6c..8c8d58654e 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -47,6 +47,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { InternalChildren = new Drawable[] { + new EditBodyPiece + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }, head = new EditHoldNoteEndPiece { RelativeSizeAxes = Axes.X, @@ -88,21 +94,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints }, DragEnded = () => changeHandler?.EndChange(), }, - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - BorderThickness = 1, - BorderColour = colours.Yellow, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - } - } }; } From af4a3785e91725d1aae10f141e4113807d879a4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Nov 2024 14:38:34 +0900 Subject: [PATCH 42/86] Fix up code quality and isolate scale better --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 38 ++++++++------------ osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 14 ++++---- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index e1b05fa84b..425db55c6e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -2,20 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Localisation; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; +using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Rulesets.Osu.UI.Cursor; -using osu.Framework.Utils; namespace osu.Game.Rulesets.Osu.Mods { @@ -32,10 +30,11 @@ namespace osu.Game.Rulesets.Osu.Mods protected readonly BindableNumber CurrentCombo = new BindableInt(); protected readonly IBindable IsBreakTime = new Bindable(); - protected float ComboBasedSize; + + private float currentSize; [SettingSource( - "Max Size at Combo", + "Max size at combo", "The combo count at which the cursor reaches its maximum size", SettingControlType = typeof(SettingsSlider>) )] @@ -46,11 +45,11 @@ namespace osu.Game.Rulesets.Osu.Mods }; [SettingSource( - "Final Size Multiplier", + "Final size multiplier", "The multiplier applied to cursor size when combo reaches maximum", SettingControlType = typeof(SettingsSlider>) )] - public BindableFloat MaxMulti { get; } = new BindableFloat(10f) + public BindableFloat MaxCursorSize { get; } = new BindableFloat(10f) { MinValue = 5f, MaxValue = 15f, @@ -69,27 +68,18 @@ namespace osu.Game.Rulesets.Osu.Mods CurrentCombo.BindTo(scoreProcessor.Combo); CurrentCombo.BindValueChanged(combo => { - ComboBasedSize = Math.Clamp(MaxMulti.Value * ((float)combo.NewValue / MaxSizeComboCount.Value), MIN_SIZE, MaxMulti.Value); + currentSize = Math.Clamp(MaxCursorSize.Value * ((float)combo.NewValue / MaxSizeComboCount.Value), MIN_SIZE, MaxCursorSize.Value); }, true); } public void Update(Playfield playfield) { - bool beBaseSize = IsBreakTime.Value; - OsuPlayfield osuPlayfield = (OsuPlayfield)playfield; - Debug.Assert(osuPlayfield.Cursor != null); + OsuCursor cursor = (OsuCursor)(playfield.Cursor!.ActiveCursor); - OsuCursor realCursor = (OsuCursor)osuPlayfield.Cursor.ActiveCursor; - float currentCombSize = (float)Interpolation.Lerp(realCursor.CursorScale.Value, ComboBasedSize, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); - - if (beBaseSize) - { - realCursor.UpdateSize(1); - } + if (IsBreakTime.Value) + cursor.ModScaleAdjust.Value = 1; else - { - realCursor.UpdateSize(currentCombSize); - } + cursor.ModScaleAdjust.Value = (float)Interpolation.Lerp(cursor.ModScaleAdjust.Value, currentSize, Math.Clamp(cursor.Time.Elapsed / TRANSITION_DURATION, 0, 1)); } } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 545cd15d32..c2f7d84f5e 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -38,6 +38,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public IBindable CursorScale => cursorScale; + /// + /// Mods which want to adjust cursor size should do so via this bindable. + /// + public readonly Bindable ModScaleAdjust = new Bindable(1); + private readonly Bindable cursorScale = new BindableFloat(1); private Bindable userCursorScale = null!; @@ -67,6 +72,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); autoCursorScale.ValueChanged += _ => cursorScale.Value = CalculateCursorScale(); + ModScaleAdjust.ValueChanged += _ => cursorScale.Value = CalculateCursorScale(); + cursorScale.BindValueChanged(e => cursorScaleContainer.Scale = new Vector2(e.NewValue), true); } @@ -90,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor protected virtual float CalculateCursorScale() { - float scale = userCursorScale.Value; + float scale = userCursorScale.Value * ModScaleAdjust.Value; if (autoCursorScale.Value && state != null) { @@ -101,11 +108,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor return scale; } - public void UpdateSize(float size) - { - cursorScale.Value = size; - } - protected override void SkinChanged(ISkinSource skin) { cursorExpand = skin.GetConfig(OsuSkinConfiguration.CursorExpand)?.Value ?? true; From 2dbc275bb8e27cab2370eff682422a1be1d1fc8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Nov 2024 17:54:05 +0900 Subject: [PATCH 43/86] Make quick restart even faster --- osu.Game/Screens/Play/PlayerLoader.cs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 9b4bf7d633..51268e5dce 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -51,6 +51,8 @@ namespace osu.Game.Screens.Play public override bool? AllowGlobalTrackControl => false; + public override float BackgroundParallaxAmount => quickRestart ? 0 : 1; + // Here because IsHovered will not update unless we do so. public override bool HandlePositionalInput => true; @@ -387,7 +389,7 @@ namespace osu.Game.Screens.Play // We need to perform this check here rather than in OnHover as any number of children of VisualSettings // may also be handling the hover events. - if (inputManager.HoveredDrawables.Contains(VisualSettings)) + if (inputManager.HoveredDrawables.Contains(VisualSettings) || quickRestart) { // Preview user-defined background dim and blur when hovered on the visual settings panel. ApplyToBackground(b => @@ -458,8 +460,9 @@ namespace osu.Game.Screens.Play if (quickRestart) { - prepareNewPlayer(); - content.ScaleTo(1, 650, Easing.OutQuint); + // A slight delay is added here to avoid an awkward stutter during the initial animation. + Scheduler.AddDelayed(prepareNewPlayer, 100); + content.ScaleTo(1); } else { @@ -467,15 +470,15 @@ namespace osu.Game.Screens.Play .ScaleTo(1, 650, Easing.OutQuint) .Then() .Schedule(prepareNewPlayer); - } - using (BeginDelayedSequence(delayBeforeSideDisplays)) - { - settingsScroll.FadeInFromZero(500, Easing.Out) - .MoveToX(0, 500, Easing.OutQuint); + using (BeginDelayedSequence(delayBeforeSideDisplays)) + { + settingsScroll.FadeInFromZero(500, Easing.Out) + .MoveToX(0, 500, Easing.OutQuint); - disclaimers.FadeInFromZero(500, Easing.Out) - .MoveToX(0, 500, Easing.OutQuint); + disclaimers.FadeInFromZero(500, Easing.Out) + .MoveToX(0, 500, Easing.OutQuint); + } } AddRangeInternal(new[] @@ -565,7 +568,7 @@ namespace osu.Game.Screens.Play else this.Exit(); }); - }, 500); + }, quickRestart ? 0 : 500); } private void cancelLoad() From f15b6b1d71a0b9d45b66a0861fc5d3878755a4fa Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 13 Nov 2024 14:09:19 +0100 Subject: [PATCH 44/86] Create LegacyBeatmapExporterTest --- .../Beatmaps/IO/LegacyBeatmapExporterTest.cs | 64 ++++++++++++++++++ .../Archives/decimal-timing-beatmap.olz | Bin 0 -> 990 bytes 2 files changed, 64 insertions(+) create mode 100644 osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs create mode 100644 osu.Game.Tests/Resources/Archives/decimal-timing-beatmap.olz diff --git a/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs b/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs new file mode 100644 index 0000000000..7973867114 --- /dev/null +++ b/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.IO; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual; +using MemoryStream = System.IO.MemoryStream; + +namespace osu.Game.Tests.Beatmaps.IO +{ + [HeadlessTest] + public partial class LegacyBeatmapExporterTest : OsuTestScene + { + [Resolved] + private BeatmapManager beatmaps { get; set; } = null!; + + [Test] + public void TestObjectsSnappedAfterTruncatingExport() + { + IWorkingBeatmap beatmap = null!; + MemoryStream outStream = null!; + + // Ensure importer encoding is correct + AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"decimal-timing-beatmap.olz")); + AddAssert("timing point has decimal offset", () => Math.Abs(beatmap.Beatmap.ControlPointInfo.TimingPoints[0].Time - 284.725) < 0.001); + AddAssert("kiai has decimal offset", () => Math.Abs(beatmap.Beatmap.ControlPointInfo.EffectPoints[0].Time - 28520.019) < 0.001); + AddAssert("hit object has decimal offset", () => Math.Abs(beatmap.Beatmap.HitObjects[0].StartTime - 28520.019) < 0.001); + + // Ensure exporter legacy conversion is correct + AddStep("export", () => + { + outStream = new MemoryStream(); + + new LegacyBeatmapExporter(LocalStorage) + .ExportToStream((BeatmapSetInfo)beatmap.BeatmapInfo.BeatmapSet!, outStream, null); + }); + + AddStep("import beatmap again", () => beatmap = importBeatmapFromStream(outStream)); + AddAssert("timing point has truncated offset", () => Math.Abs(beatmap.Beatmap.ControlPointInfo.TimingPoints[0].Time - 284) < 0.001); + AddAssert("kiai is snapped", () => Math.Abs(beatmap.Beatmap.ControlPointInfo.EffectPoints[0].Time - 28519) < 0.001); + AddAssert("hit object is snapped", () => Math.Abs(beatmap.Beatmap.HitObjects[0].StartTime - 28519) < 0.001); + } + + private IWorkingBeatmap importBeatmapFromStream(Stream stream) + { + var imported = beatmaps.Import(new ImportTask(stream, "filename.osz")).GetResultSafely(); + return imported.AsNonNull().PerformRead(s => beatmaps.GetWorkingBeatmap(s.Beatmaps[0])); + } + + private IWorkingBeatmap importBeatmapFromArchives(string filename) + { + var imported = beatmaps.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely(); + return imported.AsNonNull().PerformRead(s => beatmaps.GetWorkingBeatmap(s.Beatmaps[0])); + } + } +} diff --git a/osu.Game.Tests/Resources/Archives/decimal-timing-beatmap.olz b/osu.Game.Tests/Resources/Archives/decimal-timing-beatmap.olz new file mode 100644 index 0000000000000000000000000000000000000000..38dedc35d15f1a11baa49f00917acf93ee2b23eb GIT binary patch literal 990 zcmWIWW@Zs#U|`^2m|l<@`RKWA-YF&q1~FC!26v!nXiFO#b zw&N&j>KL znO3-0PWb%6t>+K57#VpNeE9Hmt9*dd^{Hx?cku7*jW;-d@=k8K>={M>7u^ek+@IYv zoj>3CgTO4~DH^7hy}CR}=M~dEPXG8CSux{4?Ys5|*7u$6t27v#H=L`x*!%6$qWV`w zn`f53%jw*-K8Ec~qH=#!{|i>jkF{kB8-JcNwOeBtS@cOD#_l8g{o^JJC)s%JoBKQ8 zU{1T6mHY$ep4)*hMK)!}mT{kX%IA3eUq;NuJ&xgD&i&r8yq8@A-FR8}pm; zlYYnlVAouHmh*1ASkV0=EPq|!I6cYAnl-^HB0l#sTh;x^+h=?>{4?)k_V!~p9V5#3 z-MmM?2AGz!%` zar*FwypriXi3$8=yG3F*OL}&lvF`WtH+)+@x81~N&!*$6&1PkWetyODN*{pU6T+^F&&vvb6;ZXM071Pt! z{XC79`erV<5EK^S7rR{LeA<#Vqq literal 0 HcmV?d00001 From b6eff38520b632d1fe311458ce761e27e0be1ce5 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 13 Nov 2024 14:23:28 +0100 Subject: [PATCH 45/86] Fix timing point truncation in legacy beatmap export --- osu.Game/Database/LegacyBeatmapExporter.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index 17c2c8c88d..eb48425588 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -59,7 +59,25 @@ namespace osu.Game.Database }; // Convert beatmap elements to be compatible with legacy format - // So we truncate time and position values to integers, and convert paths with multiple segments to bezier curves + // So we truncate time and position values to integers, and convert paths with multiple segments to Bézier curves + + // We must first truncate all timing points and move all objects in the timing section with it to ensure everything stays snapped + for (int i = 0; i < playableBeatmap.ControlPointInfo.TimingPoints.Count; i++) + { + var timingPoint = playableBeatmap.ControlPointInfo.TimingPoints[i]; + double offset = Math.Floor(timingPoint.Time) - timingPoint.Time; + double nextTimingPointTime = i + 1 < playableBeatmap.ControlPointInfo.TimingPoints.Count + ? playableBeatmap.ControlPointInfo.TimingPoints[i + 1].Time + : double.PositiveInfinity; + + // Offset all control points in the timing section (including the current one) + foreach (var controlPoint in playableBeatmap.ControlPointInfo.AllControlPoints.Where(o => o.Time >= timingPoint.Time && o.Time < nextTimingPointTime)) + controlPoint.Time += offset; + + // Offset all hit objects in the timing section + foreach (var hitObject in playableBeatmap.HitObjects.Where(o => o.StartTime >= timingPoint.Time && o.StartTime < nextTimingPointTime)) + hitObject.StartTime += offset; + } foreach (var controlPoint in playableBeatmap.ControlPointInfo.AllControlPoints) controlPoint.Time = Math.Floor(controlPoint.Time); From f597568476bdce8930bbdb30fa6fc937c1f2964f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 01:23:21 +0900 Subject: [PATCH 46/86] Fix test failure due to restart happening too fast --- 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 1949808dfe..cf813cfd51 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -523,7 +523,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 player", () => getCurrentPlayer()?.LoadState == LoadState.Ready); + AddUntilStep("wait for player", () => getCurrentPlayer()?.LoadState >= LoadState.Ready); AddUntilStep("time reached zero", () => getCurrentPlayer()?.GameplayClockContainer.CurrentTime > 0); AddUntilStep("skip button not visible", () => !checkSkipButtonVisible()); From 7e96ae6da4492fc26fe7e4c0cf8f13cfb26c6739 Mon Sep 17 00:00:00 2001 From: Shin Morisawa Date: Thu, 14 Nov 2024 06:54:29 +0900 Subject: [PATCH 47/86] change stuff. (idk what exactly) --- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 36ddb6030e..5d84bbc8ad 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -146,7 +146,7 @@ namespace osu.Game.Beatmaps.Drawables approachRate.Text = @" AR: " + adjustedDifficulty.ApproachRate.ToString(@"0.##"); overallDifficulty.Text = @" OD: " + adjustedDifficulty.OverallDifficulty.ToString(@"0.##"); - length.Text = "Length: " + TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate).ToString(@"mm\:ss"); + length.Text = "Length: " + TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate).ToString(@"hh\:mm\:ss"); bpm.Text = " BPM: " + Math.Round(bpmAdjusted, 0); } From a3da10ff44979139349f8dc2a59c2ccbcbb12b77 Mon Sep 17 00:00:00 2001 From: Shin Morisawa Date: Thu, 14 Nov 2024 07:19:18 +0900 Subject: [PATCH 48/86] fixed said issue --- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 5d84bbc8ad..6c74ce5738 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -146,7 +146,10 @@ namespace osu.Game.Beatmaps.Drawables approachRate.Text = @" AR: " + adjustedDifficulty.ApproachRate.ToString(@"0.##"); overallDifficulty.Text = @" OD: " + adjustedDifficulty.OverallDifficulty.ToString(@"0.##"); - length.Text = "Length: " + TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate).ToString(@"hh\:mm\:ss"); + TimeSpan lengthTimeSpan = TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate); + length.Text = "Length: " + (lengthTimeSpan.Hours > 0 + ? lengthTimeSpan.ToString(@"hh\:mm\:ss") + : lengthTimeSpan.ToString(@"mm\:ss")); bpm.Text = " BPM: " + Math.Round(bpmAdjusted, 0); } From 46f59907319b870fae52e017239f109c64dad393 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Wed, 13 Nov 2024 21:12:29 -0500 Subject: [PATCH 49/86] Ensure mod incompatibility consistency across files --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 3 ++- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs | 2 ++ osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs | 2 +- 6 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index b45b4fea13..8e9b14edeb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Osu.Mods typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel), - typeof(ModTouchDevice) + typeof(ModTouchDevice), + typeof(OsuModBloom) }; private OsuInputManager inputManager = null!; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index 425db55c6e..442465bf29 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; protected const float MIN_SIZE = 1; protected const float TRANSITION_DURATION = 100; - public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight), typeof(OsuModNoScope), typeof(OsuModObjectScaleTween), typeof(OsuModTouchDevice), typeof(OsuModAutopilot) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight), typeof(OsuModNoScope), typeof(OsuModObjectScaleTween), typeof(ModTouchDevice), typeof(OsuModAutopilot) }; protected readonly BindableNumber CurrentCombo = new BindableInt(); protected readonly IBindable IsBreakTime = new Bindable(); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 5a6cc50082..3009530b50 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods public partial class OsuModFlashlight : ModFlashlight, IApplicableToDrawableHitObject { public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModBlinds)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModBloom), typeof(OsuModBlinds) }).ToArray(); private const double default_follow_delay = 120; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs index d1bbae8e1a..57d540a7d4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Mods { public override LocalisableString Description => "Where's the cursor?"; + public override Type[] IncompatibleMods => new[] { typeof(OsuModBloom) }; + private PeriodTracker spinnerPeriods = null!; public override BindableInt HiddenComboCount { get; } = new BindableInt(10) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs index 1df344648a..a851ba0bbd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods protected virtual float EndScale => 1; - public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween), typeof(OsuModDepth) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween), typeof(OsuModDepth), typeof(OsuModBloom) }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs index 917685cdad..a364190a00 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModTouchDevice : ModTouchDevice { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModBloom) }).ToArray(); public override bool Ranked => UsesDefaultConfiguration; } } From 695f156f5f405c6c729c26e73572a1523f922944 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 14:12:13 +0900 Subject: [PATCH 50/86] Change collection management dialog to handle changes more correctly --- .../Collections/DrawableCollectionList.cs | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 6fe38a3229..96013314c7 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -43,8 +43,25 @@ namespace osu.Game.Collections private void collectionsChanged(IRealmCollection collections, ChangeSet? changes) { - Items.Clear(); - Items.AddRange(collections.AsEnumerable().Select(c => c.ToLive(realm))); + if (changes == null) + { + Items.AddRange(collections.AsEnumerable().Select(c => c.ToLive(realm))); + return; + } + + foreach (int i in changes.DeletedIndices.OrderDescending()) + Items.RemoveAt(i); + + foreach (int i in changes.InsertedIndices) + Items.Insert(i, collections[i].ToLive(realm)); + + foreach (int i in changes.NewModifiedIndices) + { + var updatedItem = collections[i]; + + Items.RemoveAt(i); + Items.Insert(i, updatedItem.ToLive(realm)); + } } protected override OsuRearrangeableListItem> CreateOsuDrawable(Live item) From ebe9a9f0837cd4e039069b46371b7ff3e6339182 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 14:12:31 +0900 Subject: [PATCH 51/86] Avoid writing changes to realm when collection name wasn't actually changed --- .../Collections/DrawableCollectionList.cs | 25 ++++++++++++++++- .../Collections/DrawableCollectionListItem.cs | 27 +++++++++---------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 96013314c7..e87a79f2e8 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -140,12 +140,35 @@ namespace osu.Game.Collections var previous = PlaceholderItem; placeholderContainer.Clear(false); - placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new BeatmapCollection().ToLiveUnmanaged(), false)); + placeholderContainer.Add(PlaceholderItem = new NewCollectionEntryItem()); return previous; } } + private class NewCollectionEntryItem : DrawableCollectionListItem + { + [Resolved] + private RealmAccess realm { get; set; } = null!; + + public NewCollectionEntryItem() + : base(new BeatmapCollection().ToLiveUnmanaged(), false) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + TextBox.OnCommit += (sender, newText) => + { + if (newText && !string.IsNullOrEmpty(TextBox.Current.Value)) + realm.Write(r => r.Add(new BeatmapCollection(TextBox.Current.Value))); + TextBox.Text = string.Empty; + }; + } + } + /// /// The flow of . Disables layout easing unless a drag is in progress. /// diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index e71368c079..a17a50e232 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -28,6 +28,10 @@ namespace osu.Game.Collections private const float item_height = 35; private const float button_width = item_height * 0.75f; + protected TextBox TextBox => content.TextBox; + + private ItemContent content = null!; + /// /// Creates a new . /// @@ -48,7 +52,7 @@ namespace osu.Game.Collections CornerRadius = item_height / 2; } - protected override Drawable CreateContent() => new ItemContent(Model); + protected override Drawable CreateContent() => content = new ItemContent(Model); /// /// The main content of the . @@ -57,10 +61,7 @@ namespace osu.Game.Collections { private readonly Live collection; - private ItemTextBox textBox = null!; - - [Resolved] - private RealmAccess realm { get; set; } = null!; + public ItemTextBox TextBox { get; private set; } = null!; public ItemContent(Live collection) { @@ -80,7 +81,7 @@ namespace osu.Game.Collections { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - IsTextBoxHovered = v => textBox.ReceivePositionalInputAt(v) + IsTextBoxHovered = v => TextBox.ReceivePositionalInputAt(v) } : Empty(), new Container @@ -89,7 +90,7 @@ namespace osu.Game.Collections Padding = new MarginPadding { Right = collection.IsManaged ? button_width : 0 }, Children = new Drawable[] { - textBox = new ItemTextBox + TextBox = new ItemTextBox { RelativeSizeAxes = Axes.Both, Size = Vector2.One, @@ -107,18 +108,14 @@ namespace osu.Game.Collections base.LoadComplete(); // Bind late, as the collection name may change externally while still loading. - textBox.Current.Value = collection.PerformRead(c => c.IsValid ? c.Name : string.Empty); - textBox.OnCommit += onCommit; + TextBox.Current.Value = collection.PerformRead(c => c.IsValid ? c.Name : string.Empty); + TextBox.OnCommit += onCommit; } private void onCommit(TextBox sender, bool newText) { - if (collection.IsManaged) - collection.PerformWrite(c => c.Name = textBox.Current.Value); - else if (!string.IsNullOrEmpty(textBox.Current.Value)) - realm.Write(r => r.Add(new BeatmapCollection(textBox.Current.Value))); - - textBox.Text = string.Empty; + if (collection.IsManaged && newText) + collection.PerformWrite(c => c.Name = TextBox.Current.Value); } } From 17b1888c597434326db9db5da21f022be5b26fe3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 14:23:43 +0900 Subject: [PATCH 52/86] Avoid using `newTexst` as it doesn't work well with tests --- osu.Game/Collections/DrawableCollectionList.cs | 6 ++++-- osu.Game/Collections/DrawableCollectionListItem.cs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index e87a79f2e8..aaef41eee1 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -162,8 +162,10 @@ namespace osu.Game.Collections TextBox.OnCommit += (sender, newText) => { - if (newText && !string.IsNullOrEmpty(TextBox.Current.Value)) - realm.Write(r => r.Add(new BeatmapCollection(TextBox.Current.Value))); + if (string.IsNullOrEmpty(TextBox.Text)) + return; + + realm.Write(r => r.Add(new BeatmapCollection(TextBox.Text))); TextBox.Text = string.Empty; }; } diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index a17a50e232..f07ec87353 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -114,7 +114,7 @@ namespace osu.Game.Collections private void onCommit(TextBox sender, bool newText) { - if (collection.IsManaged && newText) + if (collection.IsManaged && collection.Value.Name != TextBox.Current.Value) collection.PerformWrite(c => c.Name = TextBox.Current.Value); } } From d66daf15a56579b1bf6ae905555df944fd846fdf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 14:36:58 +0900 Subject: [PATCH 53/86] Fix tests clicking wrong delete buttons due to internal drawable ordering --- .../Collections/TestSceneManageCollectionsDialog.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 747cf73baf..46bf6dfafd 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -205,7 +205,9 @@ namespace osu.Game.Tests.Visual.Collections AddStep("click first delete button", () => { - InputManager.MoveMouseTo(dialog.ChildrenOfType().First(), new Vector2(5, 0)); + InputManager.MoveMouseTo(dialog + .ChildrenOfType().Single(i => i.Model.Value.Name == "1") + .ChildrenOfType().Single(), new Vector2(5, 0)); InputManager.Click(MouseButton.Left); }); @@ -213,9 +215,11 @@ namespace osu.Game.Tests.Visual.Collections assertCollectionCount(1); assertCollectionName(0, "2"); - AddStep("click first delete button", () => + AddStep("click second delete button", () => { - InputManager.MoveMouseTo(dialog.ChildrenOfType().First(), new Vector2(5, 0)); + InputManager.MoveMouseTo(dialog + .ChildrenOfType().Single(i => i.Model.Value.Name == "2") + .ChildrenOfType().Single(), new Vector2(5, 0)); InputManager.Click(MouseButton.Left); }); @@ -310,7 +314,7 @@ namespace osu.Game.Tests.Visual.Collections AddStep("focus first collection", () => { - InputManager.MoveMouseTo(firstItem = dialog.ChildrenOfType().First()); + InputManager.MoveMouseTo(firstItem = dialog.ChildrenOfType().Single(i => i.Model.Value.Name == "1")); InputManager.Click(MouseButton.Left); }); From fea6a544325854877637d13e587864afeb4ca1ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 14:46:58 +0900 Subject: [PATCH 54/86] Fix more tests reading in wrong order --- .../Visual/Collections/TestSceneManageCollectionsDialog.cs | 3 ++- osu.Game/Collections/DrawableCollectionList.cs | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 46bf6dfafd..56e7b4d39f 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -265,6 +265,7 @@ namespace osu.Game.Tests.Visual.Collections } [Test] + [Solo] public void TestCollectionRenamedExternal() { BeatmapCollection first = null!; @@ -341,6 +342,6 @@ namespace osu.Game.Tests.Visual.Collections => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count() == count + 1); // +1 for placeholder private void assertCollectionName(int index, string name) - => AddUntilStep($"item {index + 1} has correct name", () => dialog.ChildrenOfType().ElementAt(index).ChildrenOfType().First().Text == name); + => AddUntilStep($"item {index + 1} has correct name", () => dialog.ChildrenOfType().Single().OrderedItems.ElementAt(index).ChildrenOfType().First().Text == name); } } diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index aaef41eee1..9f7494b556 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; @@ -29,7 +30,11 @@ namespace osu.Game.Collections private IDisposable? realmSubscription; - protected override FillFlowContainer>> CreateListFillFlowContainer() => new Flow + private Flow flow = null!; + + public IEnumerable OrderedItems => flow.FlowingChildren; + + protected override FillFlowContainer>> CreateListFillFlowContainer() => flow = new Flow { DragActive = { BindTarget = DragActive } }; From ce818f59e74b21e2ebffe8afd3690d7701f72f3f Mon Sep 17 00:00:00 2001 From: Nathen Date: Thu, 14 Nov 2024 01:42:07 -0500 Subject: [PATCH 55/86] Fix NaN PP values when SR is 0 --- osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index 1cb6b69f91..1944b54e0c 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -70,10 +70,11 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// public virtual double CountTopWeightedStrains() { - if (ObjectStrains.Count == 0) + double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical + + if (ObjectStrains.Count == 0 || consistentTopStrain == 0) return 0.0; - double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical // Use a weighted sum of all strains. Constants are arbitrary and give nice values return ObjectStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); } From 2ea2e5f1db40dacf02a297f571a481eec3351c6f Mon Sep 17 00:00:00 2001 From: Nathen Date: Thu, 14 Nov 2024 01:47:24 -0500 Subject: [PATCH 56/86] Be doubly careful --- osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index 1944b54e0c..56249f19a7 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -70,9 +70,12 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// public virtual double CountTopWeightedStrains() { + if (ObjectStrains.Count == 0) + return 0.0; + double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical - if (ObjectStrains.Count == 0 || consistentTopStrain == 0) + if (consistentTopStrain == 0) return 0.0; // Use a weighted sum of all strains. Constants are arbitrary and give nice values From d0e793a3b395026d2b26bc51538fd5f9a015ac7c Mon Sep 17 00:00:00 2001 From: Nathen Date: Thu, 14 Nov 2024 01:50:05 -0500 Subject: [PATCH 57/86] More correct but not too important --- osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index 56249f19a7..3ba67793dc 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical if (consistentTopStrain == 0) - return 0.0; + return ObjectStrains.Count; // Use a weighted sum of all strains. Constants are arbitrary and give nice values return ObjectStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); From 9fcf8342f0430c3c17a9fc9c5a2a00e3dc902cef Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 14 Nov 2024 08:59:03 +0200 Subject: [PATCH 58/86] initial commit --- .../Difficulty/Skills/OsuStrainSkill.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 559a871df1..10b5ee0133 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -23,8 +23,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// protected virtual double ReducedStrainBaseline => 0.75; - protected double Difficulty; - protected OsuStrainSkill(Mod[] mods) : base(mods) { @@ -32,7 +30,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public override double DifficultyValue() { - Difficulty = 0; double weight = 1; // Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871). @@ -48,15 +45,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills strains[i] *= Interpolation.Lerp(ReducedStrainBaseline, 1.0, scale); } + double difficulty = 0; + // Difficulty is the weighted sum of the highest strains from every section. // We're sorting from highest to lowest strain. foreach (double strain in strains.OrderDescending()) { - Difficulty += strain * weight; + difficulty += strain * weight; weight *= DecayWeight; } - return Difficulty; + return difficulty; } public static double DifficultyToPerformance(double difficulty) => Math.Pow(5.0 * Math.Max(1.0, difficulty / 0.0675) - 4.0, 3.0) / 100000.0; From 1b6952c42ab4977fa54026d8794af4a85210d677 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 16:03:00 +0900 Subject: [PATCH 59/86] Add failing test showing crash when adjusting offset with no `HitEvent`s --- .../Ranking/TestSceneHitEventTimingDistributionGraph.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 3e38b66029..760210c370 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -40,6 +40,13 @@ namespace osu.Game.Tests.Visual.Ranking AddSliderStep("height", 0.0f, 1000.0f, height.Value, height.Set); } + [Test] + public void TestZeroEvents() + { + createTest(new List()); + AddStep("update offset", () => graph.UpdateOffset(10)); + } + [Test] public void TestManyDistributedEventsOffset() { From b086c276ad82d4d983aa41837bdf6c2748be45cb Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 14 Nov 2024 09:03:08 +0200 Subject: [PATCH 60/86] moved back to the top --- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 10b5ee0133..6823512cef 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -30,6 +30,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public override double DifficultyValue() { + double difficulty = 0; double weight = 1; // Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871). @@ -45,8 +46,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills strains[i] *= Interpolation.Lerp(ReducedStrainBaseline, 1.0, scale); } - double difficulty = 0; - // Difficulty is the weighted sum of the highest strains from every section. // We're sorting from highest to lowest strain. foreach (double strain in strains.OrderDescending()) From 88d220f4c5227467227c1ab2df6ad545121fbe79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 16:03:12 +0900 Subject: [PATCH 61/86] Fix crash when resetting offset after a play with no hit events Closes https://github.com/ppy/osu/issues/30573. --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 207e19a716..a9b93e0ffc 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -176,7 +176,7 @@ namespace osu.Game.Screens.Ranking.Statistics for (int i = 1; i <= axis_points; i++) { double axisValue = i * axisValueStep; - float position = (float)(axisValue / maxValue); + float position = maxValue == 0 ? 0 : (float)(axisValue / maxValue); float alpha = 1f - position * 0.8f; axisFlow.Add(new OsuSpriteText @@ -348,7 +348,7 @@ namespace osu.Game.Screens.Ranking.Statistics boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); } - private float offsetForValue(float value) => (1 - minimum_height) * value / maxValue; + private float offsetForValue(float value) => maxValue == 0 ? 0 : (1 - minimum_height) * value / maxValue; private float heightForValue(float value) => minimum_height + offsetForValue(value); } From 64e7e44f28c961c9de6bf0e0987a0f06b6cd392a Mon Sep 17 00:00:00 2001 From: Shin Morisawa Date: Thu, 14 Nov 2024 16:29:03 +0900 Subject: [PATCH 62/86] fix issue --- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 6c74ce5738..491882b53a 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -147,9 +147,7 @@ namespace osu.Game.Beatmaps.Drawables overallDifficulty.Text = @" OD: " + adjustedDifficulty.OverallDifficulty.ToString(@"0.##"); TimeSpan lengthTimeSpan = TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate); - length.Text = "Length: " + (lengthTimeSpan.Hours > 0 - ? lengthTimeSpan.ToString(@"hh\:mm\:ss") - : lengthTimeSpan.ToString(@"mm\:ss")); + length.Text = "Length: " + (lengthTimeSpan.TotalHours >= 1 ? lengthTimeSpan.ToString(@"hh\:mm\:ss") : lengthTimeSpan.ToString(@"mm\:ss")); bpm.Text = " BPM: " + Math.Round(bpmAdjusted, 0); } From 4957a517aacca9975cb168155e3aa3ca04bab396 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 16:38:26 +0900 Subject: [PATCH 63/86] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index f271bdfaaf..757e5659d0 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index ecb9ae2c8d..25639f84f4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 7dff243e6a48a7b6c429042075dce13cea9f793f Mon Sep 17 00:00:00 2001 From: Shin Morisawa Date: Thu, 14 Nov 2024 16:59:03 +0900 Subject: [PATCH 64/86] use .ToFormattedDuration --- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 491882b53a..8182fe24b2 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; @@ -147,7 +148,7 @@ namespace osu.Game.Beatmaps.Drawables overallDifficulty.Text = @" OD: " + adjustedDifficulty.OverallDifficulty.ToString(@"0.##"); TimeSpan lengthTimeSpan = TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate); - length.Text = "Length: " + (lengthTimeSpan.TotalHours >= 1 ? lengthTimeSpan.ToString(@"hh\:mm\:ss") : lengthTimeSpan.ToString(@"mm\:ss")); + length.Text = "Length: " + lengthTimeSpan.ToFormattedDuration(); bpm.Text = " BPM: " + Math.Round(bpmAdjusted, 0); } From 88aea70429513151076b97dd8a2790aaedc9d27c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Nov 2024 10:07:09 +0100 Subject: [PATCH 65/86] Do not permit new combo toggle to remain in indeterminate state on deselect --- .../Screens/Edit/Compose/Components/EditorSelectionHandler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index a252649bb9..a9dbfc29a9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -258,6 +258,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private void resetTernaryStates() { + if (SelectionNewComboState.Value == TernaryState.Indeterminate) + SelectionNewComboState.Value = TernaryState.False; AutoSelectionBankEnabled.Value = true; SelectionAdditionBanksEnabled.Value = true; SelectionBankStates[HIT_BANK_AUTO].Value = TernaryState.True; From 9849a88eefc7d9e8534eba47ac230b947b1cb0c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 18:27:26 +0900 Subject: [PATCH 66/86] Adjust transition further to avoid brief "jumpscare" display of metadata --- osu.Game/Screens/Play/PlayerLoader.cs | 92 ++++++++++++++++++--------- 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 51268e5dce..5843c28f3a 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -10,6 +10,7 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Transforms; using osu.Framework.Input; @@ -88,9 +89,13 @@ namespace osu.Game.Screens.Play private SkinnableSound sampleRestart = null!; + private Box? quickRestartBlackLayer; + [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + private const double quick_restart_initial_delay = 500; + protected bool BackgroundBrightnessReduction { set @@ -307,6 +312,9 @@ namespace osu.Game.Screens.Play { base.OnSuspending(e); + quickRestartBlackLayer?.FadeOut(500, Easing.OutQuint); + quickRestartBlackLayer = null; + BackgroundBrightnessReduction = false; // we're moving to player, so a period of silence is upcoming. @@ -350,7 +358,14 @@ namespace osu.Game.Screens.Play if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.OutQuint); logo.ScaleTo(new Vector2(0.15f), duration, Easing.OutQuint); - logo.FadeIn(350); + + if (quickRestart) + { + logo.Delay(quick_restart_initial_delay) + .FadeIn(350); + } + else + logo.FadeIn(350); Scheduler.AddDelayed(() => { @@ -456,17 +471,33 @@ namespace osu.Game.Screens.Play { MetadataInfo.Loading = true; - content.FadeInFromZero(500, Easing.OutQuint); - if (quickRestart) { - // A slight delay is added here to avoid an awkward stutter during the initial animation. - Scheduler.AddDelayed(prepareNewPlayer, 100); - content.ScaleTo(1); + // A quick restart starts by triggering a fade to black + AddInternal(quickRestartBlackLayer = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue + }); + + quickRestartBlackLayer + .Delay(50) + .FadeOut(5000, Easing.OutQuint); + + prepareNewPlayer(); + + content + .Delay(quick_restart_initial_delay) + .ScaleTo(1) + .FadeInFromZero(500, Easing.OutQuint); } else { + content.FadeInFromZero(500, Easing.OutQuint); + content + .ScaleTo(0.7f) .ScaleTo(1, 650, Easing.OutQuint) .Then() .Schedule(prepareNewPlayer); @@ -542,33 +573,36 @@ namespace osu.Game.Screens.Play highPerformanceSession ??= highPerformanceSessionManager?.BeginSession(); scheduledPushPlayer = Scheduler.AddDelayed(() => - { - // ensure that once we have reached this "point of no return", readyForPush will be false for all future checks (until a new player instance is prepared). - var consumedPlayer = consumePlayer(); - - ContentOut(); - - TransformSequence pushSequence = this.Delay(0); - - // This goes hand-in-hand with the restoration of low pass filter in contentOut(). - this.TransformBindableTo(volumeAdjustment, 0, CONTENT_OUT_DURATION, Easing.OutCubic); - - pushSequence.Schedule(() => { - if (!this.IsCurrentScreen()) return; + // ensure that once we have reached this "point of no return", readyForPush will be false for all future checks (until a new player instance is prepared). + var consumedPlayer = consumePlayer(); - LoadTask = null; + ContentOut(); - // By default, we want to load the player and never be returned to. - // Note that this may change if the player we load requested a re-run. - ValidForResume = false; + TransformSequence pushSequence = this.Delay(0); - if (consumedPlayer.LoadedBeatmapSuccessfully) - this.Push(consumedPlayer); - else - this.Exit(); - }); - }, quickRestart ? 0 : 500); + // This goes hand-in-hand with the restoration of low pass filter in contentOut(). + this.TransformBindableTo(volumeAdjustment, 0, CONTENT_OUT_DURATION, Easing.OutCubic); + + pushSequence.Schedule(() => + { + if (!this.IsCurrentScreen()) return; + + LoadTask = null; + + // By default, we want to load the player and never be returned to. + // Note that this may change if the player we load requested a re-run. + ValidForResume = false; + + if (consumedPlayer.LoadedBeatmapSuccessfully) + this.Push(consumedPlayer); + else + this.Exit(); + }); + }, + // When a quick restart is activated, the metadata content will display some time later if it's taking too long. + // To avoid it appearing too briefly, if it begins to fade in let's induce a standard delay. + quickRestart && content.Alpha == 0 ? 0 : 500); } private void cancelLoad() From 36d48a01c841e9997fbc8a46adaa1962ed7f9c06 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 19:33:34 +0900 Subject: [PATCH 67/86] Add missing expire call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 5843c28f3a..cf867e31f8 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -312,7 +312,7 @@ namespace osu.Game.Screens.Play { base.OnSuspending(e); - quickRestartBlackLayer?.FadeOut(500, Easing.OutQuint); + quickRestartBlackLayer?.FadeOut(500, Easing.OutQuint).Expire(); quickRestartBlackLayer = null; BackgroundBrightnessReduction = false; From f9489690cb64bfc30ed1b2c96c8cb1b011364efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Nov 2024 11:58:57 +0100 Subject: [PATCH 68/86] Fix code inspection --- osu.Game/Collections/DrawableCollectionList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 9f7494b556..164ec558a4 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -151,7 +151,7 @@ namespace osu.Game.Collections } } - private class NewCollectionEntryItem : DrawableCollectionListItem + private partial class NewCollectionEntryItem : DrawableCollectionListItem { [Resolved] private RealmAccess realm { get; set; } = null!; From bbe2c87837d7315f6511bd3e5783e222a5ee47e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Nov 2024 12:57:45 +0100 Subject: [PATCH 69/86] Add failing test case --- .../Gameplay/TestSceneBeatmapOffsetControl.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index c7f1eabab2..0f47c3cd27 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -1,14 +1,18 @@ // 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 System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Tests.Resources; @@ -52,6 +56,39 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); } + [Test] + public void TestNotEnoughTimedHitEvents() + { + AddStep("Set short reference score", () => + { + List hitEvents = + [ + // 10 events total. one of them (head circle) being timed / having hitwindows, rest having no hitwindows + new HitEvent(30, 1, HitResult.LargeTickHit, new SliderHeadCircle { ClassicSliderBehaviour = true }, null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + ]; + + foreach (var ev in hitEvents) + ev.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + offsetControl.ReferenceScore.Value = new ScoreInfo + { + HitEvents = hitEvents, + BeatmapInfo = Beatmap.Value.BeatmapInfo, + }; + }); + + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + } + [Test] public void TestScoreFromDifferentBeatmap() { From bd1d3cad49188a90a81bff5803a3cf706b18603a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Nov 2024 13:16:32 +0100 Subject: [PATCH 70/86] Do not show timing distribution graph in offset control if there's not enough timed hits Intended to address concerns raised in https://github.com/ppy/osu/pull/30620#issuecomment-2475744164. --- osu.Game/Rulesets/Scoring/HitEventExtensions.cs | 6 +++--- .../Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index 6e2852676a..fc4eef13ba 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Scoring foreach (var e in hitEvents) { - if (!affectsUnstableRate(e)) + if (!AffectsUnstableRate(e)) continue; count++; @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Scoring /// public static double? CalculateAverageHitError(this IEnumerable hitEvents) { - double[] timeOffsets = hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset).ToArray(); + double[] timeOffsets = hitEvents.Where(AffectsUnstableRate).Select(ev => ev.TimeOffset).ToArray(); if (timeOffsets.Length == 0) return null; @@ -65,6 +65,6 @@ namespace osu.Game.Rulesets.Scoring return timeOffsets.Average(); } - private static bool affectsUnstableRate(HitEvent e) => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit(); + public static bool AffectsUnstableRate(HitEvent e) => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit(); } } diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index f312fb0ec5..74b887481f 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -195,7 +195,10 @@ namespace osu.Game.Screens.Play.PlayerSettings }, }; - if (hitEvents.Count < 10) + // affecting unstable rate here is used as a substitute of determining if a hit event represents a *timed* hit event, + // i.e. an user input that the user had to *time to the track*, + // i.e. one that it *makes sense to use* when doing anything with timing and offsets. + if (hitEvents.Count(HitEventExtensions.AffectsUnstableRate) < 10) { referenceScoreContainer.AddRange(new Drawable[] { From afeb138ea002767d8c9a9d6049b00be21d0edfac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 21:47:38 +0900 Subject: [PATCH 71/86] Fix occasional flash when quick exiting / retrying from player The gist of the issue is that `fadeOut` was being called *twice* in the quick exit/retry scenarios, causing weirdness with transforms. I've restructured things to ensure it's only called once. --- osu.Game/Screens/Play/Player.cs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 398e8df5c9..91e984f5bc 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -86,6 +86,7 @@ namespace osu.Game.Screens.Play public Action RestartRequested; private bool isRestarting; + private bool noExitTransition; private Bindable mouseWheelDisabled; @@ -297,10 +298,7 @@ namespace osu.Game.Screens.Play { if (!this.IsCurrentScreen()) return; - if (PerformExit(false)) - // The hotkey overlay dims the screen. - // If the operation succeeds, we want to make sure we stay dimmed to keep continuity. - fadeOut(true); + PerformExit(false, true); }, }, }); @@ -318,10 +316,7 @@ namespace osu.Game.Screens.Play { if (!this.IsCurrentScreen()) return; - if (Restart(true)) - // The hotkey overlay dims the screen. - // If the operation succeeds, we want to make sure we stay dimmed to keep continuity. - fadeOut(true); + Restart(true); }, }, }); @@ -600,8 +595,9 @@ namespace osu.Game.Screens.Play /// Whether the pause or fail dialog should be shown before performing an exit. /// If and a dialog is not yet displayed, the exit will be blocked and the relevant dialog will display instead. /// + /// Whether the exit should perform without a transition, because the screen had faded to black already. /// Whether this call resulted in a final exit. - protected bool PerformExit(bool showDialogFirst) + protected bool PerformExit(bool showDialogFirst, bool withoutTransition = false) { bool pauseOrFailDialogVisible = PauseOverlay.State.Value == Visibility.Visible || FailOverlay.State.Value == Visibility.Visible; @@ -639,6 +635,8 @@ namespace osu.Game.Screens.Play // Screen may not be current if a restart has been performed. if (this.IsCurrentScreen()) { + noExitTransition = withoutTransition; + // The actual exit is performed if // - the pause / fail dialog was not requested // - the pause / fail dialog was requested but is already displayed (user showing intention to exit). @@ -709,7 +707,7 @@ namespace osu.Game.Screens.Play RestartRequested?.Invoke(quickRestart); - return PerformExit(false); + return PerformExit(false, quickRestart); } /// @@ -1254,10 +1252,10 @@ namespace osu.Game.Screens.Play ShowUserStatistics = true, }; - private void fadeOut(bool instant = false) + private void fadeOut() { - float fadeOutDuration = instant ? 0 : 250; - this.FadeOut(fadeOutDuration); + if (!noExitTransition) + this.FadeOut(250); if (this.IsCurrentScreen()) { From 3262b6d98931a085cee486cd5cc88142f4d133fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 22:10:45 +0900 Subject: [PATCH 72/86] Refactor to avoid dual-boolean mess --- .../Gameplay/TestSceneStoryboardWithOutro.cs | 2 +- .../Multiplayer/MultiplayerPlayer.cs | 2 +- osu.Game/Screens/Play/Player.cs | 48 ++++++++++++------- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index aff6139c08..4f1a63341a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -223,7 +223,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected partial class OutroPlayer : TestPlayer { - public void ExitViaPause() => PerformExit(true); + public void ExitViaPause() => PerformExitWithConfirmation(); public new FailOverlay FailOverlay => base.FailOverlay; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index e560c5ca5d..a50f3440f5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -158,7 +158,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!string.IsNullOrEmpty(message)) Logger.Log(message, LoggingTarget.Runtime, LogLevel.Important); - Schedule(() => PerformExit(false)); + Schedule(() => PerformExit()); } private void onGameplayStarted() => Scheduler.Add(() => diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 91e984f5bc..ac49b4c42d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Play public Action RestartRequested; private bool isRestarting; - private bool noExitTransition; + private bool skipExitTransition; private Bindable mouseWheelDisabled; @@ -290,7 +290,7 @@ namespace osu.Game.Screens.Play { SaveReplay = async () => await prepareAndImportScoreAsync(true).ConfigureAwait(false), OnRetry = Configuration.AllowUserInteraction ? () => Restart() : null, - OnQuit = () => PerformExit(true), + OnQuit = () => PerformExitWithConfirmation(), }, new HotkeyExitOverlay { @@ -298,7 +298,7 @@ namespace osu.Game.Screens.Play { if (!this.IsCurrentScreen()) return; - PerformExit(false, true); + PerformExit(true); }, }, }); @@ -443,7 +443,7 @@ namespace osu.Game.Screens.Play { HoldToQuit = { - Action = () => PerformExit(true), + Action = () => PerformExitWithConfirmation(), IsPaused = { BindTarget = GameplayClockContainer.IsPaused }, ReplayLoaded = { BindTarget = DrawableRuleset.HasReplayLoaded }, }, @@ -480,7 +480,7 @@ namespace osu.Game.Screens.Play OnResume = Resume, Retries = RestartCount, OnRetry = () => Restart(), - OnQuit = () => PerformExit(true), + OnQuit = () => PerformExitWithConfirmation(), }, }, }; @@ -583,26 +583,24 @@ namespace osu.Game.Screens.Play } /// - /// Attempts to complete a user request to exit gameplay. + /// Attempts to complete a user request to exit gameplay, with confirmation. /// /// /// /// This should only be called in response to a user interaction. Exiting is not guaranteed. /// This will interrupt any pending progression to the results screen, even if the transition has begun. /// + /// + /// This method will show the pause or fail dialog before performing an exit. + /// If a dialog is not yet displayed, the exit will be blocked and the relevant dialog will display instead. /// - /// - /// Whether the pause or fail dialog should be shown before performing an exit. - /// If and a dialog is not yet displayed, the exit will be blocked and the relevant dialog will display instead. - /// - /// Whether the exit should perform without a transition, because the screen had faded to black already. - /// Whether this call resulted in a final exit. - protected bool PerformExit(bool showDialogFirst, bool withoutTransition = false) + /// + protected bool PerformExitWithConfirmation() { bool pauseOrFailDialogVisible = PauseOverlay.State.Value == Visibility.Visible || FailOverlay.State.Value == Visibility.Visible; - if (showDialogFirst && !pauseOrFailDialogVisible) + if (!pauseOrFailDialogVisible) { // if the fail animation is currently in progress, accelerate it (it will show the pause dialog on completion). if (ValidForResume && GameplayState.HasFailed) @@ -621,6 +619,22 @@ namespace osu.Game.Screens.Play } } + return PerformExit(); + } + + /// + /// Attempts to complete a user request to exit gameplay. + /// + /// + /// + /// This should only be called in response to a user interaction. Exiting is not guaranteed. + /// This will interrupt any pending progression to the results screen, even if the transition has begun. + /// + /// + /// Whether the exit should perform without a transition, because the screen had faded to black already. + /// Whether this call resulted in a final exit. + protected bool PerformExit(bool skipTransition = false) + { // Matching osu!stable behaviour, if the results screen is pending and the user requests an exit, // show the results instead. if (GameplayState.HasPassed && !isRestarting) @@ -635,7 +649,7 @@ namespace osu.Game.Screens.Play // Screen may not be current if a restart has been performed. if (this.IsCurrentScreen()) { - noExitTransition = withoutTransition; + skipExitTransition = skipTransition; // The actual exit is performed if // - the pause / fail dialog was not requested @@ -707,7 +721,7 @@ namespace osu.Game.Screens.Play RestartRequested?.Invoke(quickRestart); - return PerformExit(false, quickRestart); + return PerformExit(quickRestart); } /// @@ -1254,7 +1268,7 @@ namespace osu.Game.Screens.Play private void fadeOut() { - if (!noExitTransition) + if (!skipExitTransition) this.FadeOut(250); if (this.IsCurrentScreen()) From d1b5d31ea66fc3c8aa777675e935fa103564a38f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 22:23:42 +0900 Subject: [PATCH 73/86] Add explicit parameter in --- osu.Game/Screens/Play/Player.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ac49b4c42d..8d7665a0d9 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -298,7 +298,7 @@ namespace osu.Game.Screens.Play { if (!this.IsCurrentScreen()) return; - PerformExit(true); + PerformExit(skipTransition: true); }, }, }); @@ -721,7 +721,7 @@ namespace osu.Game.Screens.Play RestartRequested?.Invoke(quickRestart); - return PerformExit(quickRestart); + return PerformExit(skipTransition: quickRestart); } /// From 8df7a6f2f31226dab8e1a1d300968b527e24c00b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Nov 2024 14:24:11 +0100 Subject: [PATCH 74/86] Adjust test to have better assertions --- .../Beatmaps/IO/LegacyBeatmapExporterTest.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs b/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs index 7973867114..9947def06d 100644 --- a/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.IO; using NUnit.Framework; using osu.Framework.Allocation; @@ -30,9 +29,9 @@ namespace osu.Game.Tests.Beatmaps.IO // Ensure importer encoding is correct AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"decimal-timing-beatmap.olz")); - AddAssert("timing point has decimal offset", () => Math.Abs(beatmap.Beatmap.ControlPointInfo.TimingPoints[0].Time - 284.725) < 0.001); - AddAssert("kiai has decimal offset", () => Math.Abs(beatmap.Beatmap.ControlPointInfo.EffectPoints[0].Time - 28520.019) < 0.001); - AddAssert("hit object has decimal offset", () => Math.Abs(beatmap.Beatmap.HitObjects[0].StartTime - 28520.019) < 0.001); + AddAssert("timing point has decimal offset", () => beatmap.Beatmap.ControlPointInfo.TimingPoints[0].Time, () => Is.EqualTo(284.725).Within(0.001)); + AddAssert("kiai has decimal offset", () => beatmap.Beatmap.ControlPointInfo.EffectPoints[0].Time, () => Is.EqualTo(28520.019).Within(0.001)); + AddAssert("hit object has decimal offset", () => beatmap.Beatmap.HitObjects[0].StartTime, () => Is.EqualTo(28520.019).Within(0.001)); // Ensure exporter legacy conversion is correct AddStep("export", () => @@ -44,9 +43,9 @@ namespace osu.Game.Tests.Beatmaps.IO }); AddStep("import beatmap again", () => beatmap = importBeatmapFromStream(outStream)); - AddAssert("timing point has truncated offset", () => Math.Abs(beatmap.Beatmap.ControlPointInfo.TimingPoints[0].Time - 284) < 0.001); - AddAssert("kiai is snapped", () => Math.Abs(beatmap.Beatmap.ControlPointInfo.EffectPoints[0].Time - 28519) < 0.001); - AddAssert("hit object is snapped", () => Math.Abs(beatmap.Beatmap.HitObjects[0].StartTime - 28519) < 0.001); + AddAssert("timing point has truncated offset", () => beatmap.Beatmap.ControlPointInfo.TimingPoints[0].Time, () => Is.EqualTo(284).Within(0.001)); + AddAssert("kiai is snapped", () => beatmap.Beatmap.ControlPointInfo.EffectPoints[0].Time, () => Is.EqualTo(28519).Within(0.001)); + AddAssert("hit object is snapped", () => beatmap.Beatmap.HitObjects[0].StartTime, () => Is.EqualTo(28519).Within(0.001)); } private IWorkingBeatmap importBeatmapFromStream(Stream stream) From 5d3f55fe4d237592e8f1aaad91ffa280e7ecf3ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Nov 2024 15:05:46 +0100 Subject: [PATCH 75/86] Fill out xmldoc --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8d7665a0d9..6ab7d81f83 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -594,7 +594,7 @@ namespace osu.Game.Screens.Play /// This method will show the pause or fail dialog before performing an exit. /// If a dialog is not yet displayed, the exit will be blocked and the relevant dialog will display instead. /// - /// + /// Whether this call resulted in a final exit. protected bool PerformExitWithConfirmation() { bool pauseOrFailDialogVisible = From 2cbb09573d7cb8e8506d14e95f89886586ec42f8 Mon Sep 17 00:00:00 2001 From: Hiviexd Date: Thu, 14 Nov 2024 15:38:10 +0100 Subject: [PATCH 76/86] fix constant algorithm scroll speed --- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 64ea9d88cd..4185b67f4c 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -84,8 +84,11 @@ namespace osu.Game.Rulesets.Taiko.UI protected virtual double ComputeTimeRange() { - // Adjust when we're using constant algorithm to not be sluggish. - double multiplier = VisualisationMethod == ScrollVisualisationMethod.Constant ? 4 * Beatmap.Difficulty.SliderMultiplier : 1; + // Using the constant algorithm results in a sluggish scroll speed that's equal to 60 BPM. + // We need to adjust it to the expected default scroll speed (BPM * base SV multiplier). + double multiplier = VisualisationMethod == ScrollVisualisationMethod.Constant + ? (Beatmap.BeatmapInfo.BPM * Beatmap.Difficulty.SliderMultiplier) / 60 + : 1; return PlayfieldAdjustmentContainer.ComputeTimeRange() / multiplier; } From 1a31e56d4acfecc55a73b0852c08dea06b8bf7b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 23:59:55 +0900 Subject: [PATCH 77/86] Fix double restart call still existing --- osu.Game/Screens/Play/Player.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 6ab7d81f83..e9722350bd 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -719,9 +719,14 @@ namespace osu.Game.Screens.Play // stopping here is to ensure music doesn't become audible after exiting back to PlayerLoader. musicController.Stop(); - RestartRequested?.Invoke(quickRestart); + if (RestartRequested != null) + { + skipExitTransition = quickRestart; + RestartRequested?.Invoke(quickRestart); + return true; + } - return PerformExit(skipTransition: quickRestart); + return PerformExit(quickRestart); } /// From ef9296f3ada2fe29537373795c3fd98415454193 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Thu, 14 Nov 2024 14:12:21 -0500 Subject: [PATCH 78/86] Allow Autopilot and Grow/Deflate compatibility --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 3 +-- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 8e9b14edeb..b45b4fea13 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -32,8 +32,7 @@ namespace osu.Game.Rulesets.Osu.Mods typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel), - typeof(ModTouchDevice), - typeof(OsuModBloom) + typeof(ModTouchDevice) }; private OsuInputManager inputManager = null!; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index 442465bf29..c674074dc6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; protected const float MIN_SIZE = 1; protected const float TRANSITION_DURATION = 100; - public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight), typeof(OsuModNoScope), typeof(OsuModObjectScaleTween), typeof(ModTouchDevice), typeof(OsuModAutopilot) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight), typeof(OsuModNoScope), typeof(ModTouchDevice) }; protected readonly BindableNumber CurrentCombo = new BindableInt(); protected readonly IBindable IsBreakTime = new Bindable(); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs index a851ba0bbd..1df344648a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods protected virtual float EndScale => 1; - public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween), typeof(OsuModDepth), typeof(OsuModBloom) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween), typeof(OsuModDepth) }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { From 4c147327dfc2555cb21d33e68a170d68213a69b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Nov 2024 10:19:34 +0100 Subject: [PATCH 79/86] Fix code quality --- .../Edit/Blueprints/Components/EditBodyPiece.cs | 3 --- .../Edit/Blueprints/Components/EditNotePiece.cs | 1 - .../Edit/Blueprints/HoldNoteSelectionBlueprint.cs | 2 -- 3 files changed, 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs index 652baccaa6..5cfcf00b33 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs @@ -6,9 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Rulesets.Mania.Skinning.Default; -using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs index 1f9f26b5e2..f68004db28 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs @@ -9,7 +9,6 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 8c8d58654e..c0d2be229f 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -4,9 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; From 0c4c9bd2376df86af8168658f44f4412810f5cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Nov 2024 10:27:07 +0100 Subject: [PATCH 80/86] Fix incorrect selection hold note blueprint display when upscroll active --- .../Edit/Blueprints/HoldNoteSelectionBlueprint.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index c0d2be229f..aa21d53f8b 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -30,6 +30,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IPositionSnapProvider? positionSnapProvider { get; set; } + private EditBodyPiece body = null!; private EditHoldNoteEndPiece head = null!; private EditHoldNoteEndPiece tail = null!; @@ -45,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { InternalChildren = new Drawable[] { - new EditBodyPiece + body = new EditBodyPiece { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomCentre, @@ -113,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints foreach (var child in InternalChildren) child.Anchor = Origin; - head.Scale = tail.Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Down ? 1 : -1); + head.Scale = tail.Scale = body.Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Down ? 1 : -1); } public override Quad SelectionQuad => ScreenSpaceDrawQuad; From e5480f8346bc9170b79618db5cfb809f6de808bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Nov 2024 10:37:56 +0100 Subject: [PATCH 81/86] Adjust appearance of placement blueprints to be closer to previous --- .../Blueprints/HoldNotePlacementBlueprint.cs | 27 +++++++++++++++---- .../Edit/Blueprints/NotePlacementBlueprint.cs | 21 ++++++++++++--- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index 991b7f476c..13cfc5f691 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -4,8 +4,10 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; @@ -17,9 +19,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { public partial class HoldNotePlacementBlueprint : ManiaPlacementBlueprint { - private readonly EditBodyPiece bodyPiece; - private readonly EditNotePiece headPiece; - private readonly EditNotePiece tailPiece; + private EditBodyPiece bodyPiece = null!; + private Circle headPiece = null!; + private Circle tailPiece = null!; [Resolved] private IScrollingInfo scrollingInfo { get; set; } = null!; @@ -28,14 +30,29 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints public HoldNotePlacementBlueprint() : base(new HoldNote()) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) { RelativeSizeAxes = Axes.Both; InternalChildren = new Drawable[] { bodyPiece = new EditBodyPiece { Origin = Anchor.TopCentre }, - headPiece = new EditNotePiece { Origin = Anchor.Centre }, - tailPiece = new EditNotePiece { Origin = Anchor.Centre } + headPiece = new Circle + { + Origin = Anchor.Centre, + Colour = colours.Yellow, + Height = 10 + }, + tailPiece = new Circle + { + Origin = Anchor.Centre, + Colour = colours.Yellow, + Height = 10 + }, }; } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs index b3ec3ef3e4..422215db57 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; using osuTK.Input; @@ -12,14 +14,25 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { public partial class NotePlacementBlueprint : ManiaPlacementBlueprint { - private readonly EditNotePiece piece; + private Circle piece = null!; public NotePlacementBlueprint() : base(new Note()) { - RelativeSizeAxes = Axes.Both; + } - InternalChild = piece = new EditNotePiece { Origin = Anchor.Centre }; + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + Masking = true; + + InternalChild = piece = new Circle + { + Origin = Anchor.Centre, + Colour = colours.Yellow, + Height = 10 + }; } public override void UpdateTimeAndPosition(SnapResult result) From 4ae13cfd6052f71de28bbffe70788201b11549d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Nov 2024 10:58:06 +0100 Subject: [PATCH 82/86] Remove unused resolved property --- .../Edit/Blueprints/HoldNoteSelectionBlueprint.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index aa21d53f8b..915706c044 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; -using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; @@ -18,9 +17,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { public partial class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint { - [Resolved] - private OsuColour colours { get; set; } = null!; - [Resolved] private IEditorChangeHandler? changeHandler { get; set; } From 80761139076ae46e50ff493fed72189c947ec227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Nov 2024 15:25:16 +0100 Subject: [PATCH 83/86] Revert velopack Because even if you somehow bypass the breakage that bricks your install by installing the missing libssl-1.0 or whatever (https://github.com/ppy/osu/issues/30648#issuecomment-2478856055), there's another brick upstream (https://github.com/ppy/osu/issues/30648#issuecomment-2478926177, https://github.com/velopack/velopack/issues/355). Can we even downgrade like this? No idea. How does one test this? No idea. At this point I am like a headless chicken, screaming into the void trying to restore any semblance of order into my crumbling universe. If this is tried and linux is still broken with the libssl garbage, then the `osulazer-2024.1115.1-linux-x64-*.nupkg` assets should be pulled from the release, which should be enough to stop the game from auto-updating. Maybe the appimage itself can stay up and people can upgrade manually if they so desire. Or maybe not. Who knows. --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 2cdbaddf72..904f5edf2b 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,7 +26,7 @@ - + From c578c46a0737844da057b5323e772e9cdb3d04dd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 16 Nov 2024 02:03:48 +0900 Subject: [PATCH 84/86] Update Velopack to 0.0.915 --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 904f5edf2b..d06c4dd41b 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,7 +26,7 @@ - + From a5fab23e4486f06da9b670a7aeeac13caf874f1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Nov 2024 23:35:30 +0900 Subject: [PATCH 85/86] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 757e5659d0..0ebb6be7a1 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 25639f84f4..d4ef9a263f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 0a1f589c80a5461b9085609b466a303e1a9b5b07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Nov 2024 11:50:08 +0900 Subject: [PATCH 86/86] Fix black layer not fading fast enough when exiting quickly from quick restart --- osu.Game/Screens/Play/PlayerLoader.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index cba7bb3b8a..aedc268d70 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -331,6 +331,9 @@ namespace osu.Game.Screens.Play cancelLoad(); ContentOut(); + quickRestartBlackLayer?.FadeOut(100, Easing.OutQuint).Expire(); + quickRestartBlackLayer = null; + // Ensure the screen doesn't expire until all the outwards fade operations have completed. this.Delay(CONTENT_OUT_DURATION).FadeOut();